From 65846109db6d650c6cd00775df5c03c54e7f86c1 Mon Sep 17 00:00:00 2001 From: adam Date: Sun, 2 May 2004 07:36:50 +0000 Subject: [PATCH] added tons of stuff, including js support darcs-hash:20040502073650-5007d-d7d14fa7487b0a3380a57f5dfcf8f1f3f0c96a51.gz --- doc/README | 99 +++++++++++++++++++++- src/org/ibex/mail/MailException.java | 12 +++ src/org/ibex/mail/Message.java | 25 +++--- src/org/ibex/mail/protocol/IMAP.java | 4 - src/org/ibex/mail/protocol/Incoming.java | 12 +++ src/org/ibex/mail/protocol/SMTP.java | 23 +++--- src/org/ibex/mail/store/MessageStore.java | 128 ++++++++++++++++++++--------- src/org/ibex/mail/target/Script.java | 126 ++++++++++++++++++++++++++++ src/org/ibex/mail/target/Target.java | 3 + 9 files changed, 361 insertions(+), 71 deletions(-) create mode 100644 src/org/ibex/mail/MailException.java create mode 100644 src/org/ibex/mail/protocol/Incoming.java create mode 100644 src/org/ibex/mail/target/Script.java diff --git a/doc/README b/doc/README index c5bd6cc..e26737a 100644 --- a/doc/README +++ b/doc/README @@ -1,7 +1,6 @@ ============================================================================== org.ibex.mail - A mail server offering the same level of flexibility as Java Servlets. The org.ibex.mail server infrastructure is designed to run within @@ -15,3 +14,101 @@ including: - Shared configuration of virtual hosts between web-apps and mail-apps - Authentication infrastructure + +______________________________________________________________________________ +JavaScript API + +Each script is invoked with the variable 'm' bound to a message to be +handled. The script MUST return either a Filter or a Target which +will process the message next. This is a crucial property that +prevents mail from being lost -- a script can never "lose" a message +except by explicitly sending it to the target 'ibex.mail.target.dead'. +All methods which create new messages (such as ibex.mail.message.clone()) +Also take a target -- the newly-created message is immediately directed +to the target; the current script does not get access to it. + +The following properties are available from ibex.mail: + + ibex.mail. + + my. -- this can be read from but only written to if it is null + mailbox -- a mail storage object; subproperties are submailboxes; also qualifies as a Target + db -- a (read/write) properties file for the current user + + freshMessageId -- returns a fresh message id every time it is read + + + props[path] -- reads a java-style properties file from [path] and allows read-write access + script[path] -- reads a script from 'path', parses it as a JS script, and passes the message to that script + + filter. + anonymize + html2text + pgp + singleUseAddress + returnReciept + dcc + vipul + spamAssassin + + target. + dead -- drops the message on the floor; it vanishes without a trace + dir[path] -- uses the directory [path] as a naive file-based storage area + send -- sends the outgoing message via smtp + bounce(message) -- bounces the message + vacation + darcs(repo) + procmail(procmailrc) + lmtp(path) + sms(phone#) + fax(phone#) + nntp(group) + + message. + create -- creates a fresh Message object when read from + clone(m, t) -- makes a copy of message 'm' and sends it to target 't' + pipe(cmdline) -- pipes the message (headers and body) to the shell command 'cmdline' + + crypto. + base36 + .decode + .encode + + + +The following tables describe the keys on objects of various types. +The type of an object is not visible from the JavaScript world; it is +provided here only for documentation purposes. + +Note that, for example, m.headers.date is not the same as m.date; the +former is a string; the latter is a Date object (with subfields) + + Message + allHeaders [String] -- one massive string holding all the headers + headers [Hash] -- the *string* value of each header, keyed on header name + subject [String] -- the Subject: header (for convenience) + date [Date] -- the message's date + to [Address] -- the To: header as an Address object + from [Address] -- the From: header as an Address object + envelopeTo [Address] -- the SMTP "RCPT TO:" recipient + envelopeFrom [Address] -- the SMTP "MAIL FROM:" sender + replyto [Address] -- the ReplyTo: header as an Address object + messageid [String] -- the MessageId: header; if none is present one will be created + cc [Array] -- an array of Adress objects, one for each person cc'd + bcc [Array] -- an array of Adress objects, one for each person bcc'd + resent *FIXME* + traces [Array] -- an array of Trace objects + + deleted [boolean] -- the message's deleted flag + read [boolean] -- the message's read flag + answered [boolean] -- the message's answered flag + + Address + user [String] -- the 'user' part of 'foo ' + host [String] -- the 'host.com' part of 'foo ' + description [String] -- the 'foo' part of 'foo ' + + Trace + returnPath [String] -- FIXME + FIXME + diff --git a/src/org/ibex/mail/MailException.java b/src/org/ibex/mail/MailException.java new file mode 100644 index 0000000..b869042 --- /dev/null +++ b/src/org/ibex/mail/MailException.java @@ -0,0 +1,12 @@ + +public class MailException extends Exception { + + public static class MailboxFull extends MailException { } + public static class RelayingDenied extends MailException { } + public static class IOException extends MailException { + // FIXME: fill in stack trace + final IOException ioe; + public IOException(java.io.IOException ioe) { this.ioe = ioe; } + } + +} diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 22d94dd..3effcfa 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -1,4 +1,5 @@ package org.ibex.mail; +import org.ibex.crypto.*; // FIXME MIME: RFC2045, 2046, 2049 // NOTE: always use Win32 line endings // hard line limit: 998 chars @@ -13,7 +14,8 @@ package org.ibex.mail; // FEATURE: mailing list header parsing // FEATURE: delivery status notification (and the sneaky variety) // FEATURE: threading as in http://www.jwz.org/doc/threading.html -public class Message { + +public class Message extends JSReflection { public final String allHeaders; // pristine headers public final Hashtable headers; // hash of headers (not including resent's and traces) @@ -39,9 +41,14 @@ public class Message { public boolean deleted = false; public boolean read = false; public boolean answered = false; + public String dumpStoredForm() { throw new Error("StoredMessage.dumpStoredForm() not implemented"); }; } - public static class Address { + public static class Address extends JSReflection { + public String coerceToString() { + if (description == null || description.equals("")) return user +"@"+ host; + return description + " " + "<" + user +"@"+ host + ">"; + } public final String user; public final String host; public final String description; @@ -75,18 +82,8 @@ public class Message { } } - public static class Base36 { - public static String encode(long l) { - StringBuffer ret = new StringBuffer(); - while (l > 0) { - if ((l % 36) < 10) ret.append((char)(((int)'0') + (int)(l % 36))); - else ret.append((char)(((int)'A') + (int)((l % 36) - 10))); - l /= 36; - } - } - } - - public Message(ReadStream rs) { + // FIXME: support dotTerminatedLikeSMTP + public Message(ReadStream rs, boolean dotTermiantedLikeSMTP) { String key = null; StringBuffer all = new StringBuffer(); for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) { diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java index a504848..fc72869 100644 --- a/src/org/ibex/mail/protocol/IMAP.java +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -5,10 +5,6 @@ import java.io.*; class IMAPException extends IOException { } -interface IMAP { -} - - public class IMAP extends MessageProtocol { public static void main(String[] args) throws Exception { diff --git a/src/org/ibex/mail/protocol/Incoming.java b/src/org/ibex/mail/protocol/Incoming.java new file mode 100644 index 0000000..42c5f9a --- /dev/null +++ b/src/org/ibex/mail/protocol/Incoming.java @@ -0,0 +1,12 @@ +package org.ibex.mail.protocol; + +public class Incoming { + + protected void accept(Message m) throws IOException { + // currently, we write all inbound messages to the transcript + MessageStore.transcript.add(m); + + // FIXME: figure out where the message goes next + } + +} diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java index 0074285..82838a5 100644 --- a/src/org/ibex/mail/protocol/SMTP.java +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -2,7 +2,7 @@ package org.ibex.mail.protocol; public class SMTP extends MessageProtocol { public SMTP() { setProtocolName("SMTP"); } - public ServerRequest createRequest(Connection conn) { return new Request((TcpConnection)conn); } + public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); } public static class Outgoing { // recommended retry interval is 30 minutes @@ -28,9 +28,9 @@ public class SMTP extends MessageProtocol { } } - private class Incoming implements ServerRequest { + private class Listener extends Incoming implements ServerRequest { TcpConnection conn; - public Incoming(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); } + public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); } public void init() { } public boolean handleRequest() throws IOException { @@ -86,15 +86,16 @@ public class SMTP extends MessageProtocol { ws.println("354 Enter message, ending with \".\" on a line by itself"); StringBuffer data = new StringBuffer(); // move this into the RFC2822 class - while(true) { - String line = rs.readLine(); - if (line.equals(".")) break; - if (line.startsWith("..")) line = line.substring(1); - data.append(line); + boolean good = false; + try { + good = true; + Message m = new Message(line, true); + Target.default.accept(m); + } finally { + //ws.println("251 user not local; will forward"); + if (good) ws.println("250 OK message accepted for delivery"); + else { /* FIXME */ } } - // FIXME: commit message to disk here - ws.println("250 OK message accepted for delivery"); - //ws.println("251 user not local; will forward"); } else if (command.toUpperCase().startsWith("HELP")) { ws.println("214 sorry, you are beyond help. please see a trained professional."); diff --git a/src/org/ibex/mail/store/MessageStore.java b/src/org/ibex/mail/store/MessageStore.java index d2aec48..2076e19 100644 --- a/src/org/ibex/mail/store/MessageStore.java +++ b/src/org/ibex/mail/store/MessageStore.java @@ -6,53 +6,99 @@ import java.net.*; // FIXME: appallingly inefficient public class MessageStore { - private final String STORAGE_ROOT = System.getProperty("org.ibex.mail.MessageStore.ROOT", "/var/org.ibex.mail/"); - public final MessageStore root = new MessageStore(STORAGE_ROOT); - - private String path; - private MessageStore(String path) throws IOException { new File(this.path = path).mkdirs(); } - public MessageStore slash(String name) { return new MessageStore(path + "/" + name); } - - public int[] list() { - String[] names = new File(path).list(); - int[] ret = new int[names.length]; - for(int i=0, j=0; j