From: adam Date: Thu, 18 Mar 2004 08:39:54 +0000 (+0000) Subject: import X-Git-Url: http://git.megacz.com/?a=commitdiff_plain;h=53ae1e68bebff56f89414676e3303c553e6708c7;p=org.ibex.mail.git import darcs-hash:20040318083954-5007d-73270f6e707abb6388f00423c6ff6ee74d36cd40.gz --- 53ae1e68bebff56f89414676e3303c553e6708c7 diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..c5bd6cc --- /dev/null +++ b/doc/README @@ -0,0 +1,17 @@ +============================================================================== +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 +Resin. This gives us immediate access to Resin's advanced features +including: + + - Sophisticated configuration file management in XML + - Expression Language expansion within configuration files + - JNI-driven SSL + - Thread pooling + - Shared configuration of virtual hosts between web-apps and mail-apps + - Authentication infrastructure + diff --git a/doc/ref/rfc2821.txt.pdf b/doc/ref/rfc2821.txt.pdf new file mode 100644 index 0000000..a3f8a17 Binary files /dev/null and b/doc/ref/rfc2821.txt.pdf differ diff --git a/doc/ref/rfc2822.txt.pdf b/doc/ref/rfc2822.txt.pdf new file mode 100644 index 0000000..9ec30bc Binary files /dev/null and b/doc/ref/rfc2822.txt.pdf differ diff --git a/doc/ref/rfc3282.txt.pdf b/doc/ref/rfc3282.txt.pdf new file mode 100644 index 0000000..86b655d Binary files /dev/null and b/doc/ref/rfc3282.txt.pdf differ diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java new file mode 100644 index 0000000..e01ef8e --- /dev/null +++ b/src/org/ibex/mail/Message.java @@ -0,0 +1,140 @@ +package org.ibex.mail; +// FIXME MIME: RFC2045, 2046, 2049 +// NOTE: always use Win32 line endings +// hard line limit: 998 chars +// soft line limit (suggested): 78 chars +// header fields: ascii 33-126 (but no colon) +// field body: anything ASCII except CRLF +// folded headers: can insert CRLF anywhere that whitespace appears (before the whitespace) +// body needs CRLF; one or the other alone is not acceptable +// date/time parsing: see 3.3 + +// FEATURE: PGP-signature-parsing +// 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 final String allHeaders; // pristine headers + public final Hashtable headers; // hash of headers (not including resent's and traces) + + // parsed header fields + public final Date date; + public final Address to; + public final Address from; // if multiple From entries, this is sender + public final Address replyto; // if none provided, this is equal to sender + public final String subject; + public final String messageid; + public final Address[] cc; + public final Address[] bcc; + public final Hashtable[] resent; + public final Trace[] traces; + + // envelope fields + public final Address envelopeFrom; + public final Address[] envelopeTo; + + public static class StoredMessage extends Message { + public final int uid; + public boolean deleted = false; + public boolean read = false; + public boolean answered = false; + } + + public static class Address { + public final String user; + public final String host; + public final String description; + public Address(String user, String host, String description) { + this.user = user; this.host = host; this.description = description; + } + public Address(String s) { + s = s.trim(); + String descrip = null; + if (s.indexOf('<') != -1) { + if (s.indexOf('>') == -1) { /* FIXME */ } + descrip = s.substring(0, s.indexOf('<')) + s.substring(s.indexOf('>') + 1); + s = s.substring(s.indexOf('<') + 1, s.indexOf('>')); + } + if (s.indexOf('@') == -1) { /* FIXME */ } + description = descrip; + user = s.substring(0, s.indexOf('@')); + host = s.substring(s.indexOf('@')+1); + } + } + + public class Trace { + final String returnPath = null; + final Element[] elements; + public class Element { + final String fromDomain; + final String fromIP; + final String toDomain; + final String forWhom; + final Date date; + } + } + + 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) { + String key = null; + StringBuffer all = new StringBuffer(); + for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) { + all.append(s); + all.append("\r\n"); + if (Character.isSpace(s.charAt(0))) { + if (lastKey == null) { /* FIXME */ } + headers.put(lastKey, headers.get(lastKey) + s); + continue; + } + if (s.indexOf(':') == -1) { /* FIXME */ } + + key = s.substring(0, s.indexOf(':')); + String val = s.substring(0, s.indexOf(':') + 1); + while(Character.isSpace(val.charAt(0))) val = val.substring(1); + + if (headers.get(key) != null) + if (key.startsWith("Resent-")) { + // FIXME: multi-resent headers + } else if (key.startsWith("Return-Path:")) { + // FIXME: parse traces, see RFC2821, section 4.4 + } else if (key.startsWith("Recieved:")) { + // FIXME: parse traces, see RFC2821, section 4.4 + } else { + // just append it to the previous one; valid for Comments/Keywords + val = headers.get(key) + " " + val; + } + + headers.put(key, val); + } + pristeneHeaders = all.toString(); + StringBuffer body = new StringBuffer(); + for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) body.append(s); + this.body = body.toString(); + } + + // http://www.jwz.org/doc/mid.html + public static String generateFreshMessageId() { + StringBuffer ret = new StringBuffer(); + ret.append('<'); + ret.append(Base36.encode(System.currentTimeMillis())); + ret.append('.'); + ret.append(Base36.encode(random.nextLong())); + ret.append('.'); + try { ret.append(InetAddress.getLocalHost().getHostName()); } catch (UnknownHostException e) { /* DELIBERATE */ } + ret.append('>'); + return ret.toString(); + } + + private static final Random = new Random(); +} diff --git a/src/org/ibex/mail/filter/Anonymizer.java b/src/org/ibex/mail/filter/Anonymizer.java new file mode 100644 index 0000000..6544d41 --- /dev/null +++ b/src/org/ibex/mail/filter/Anonymizer.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** anonymizes mail messages */ +public class Anonymizer extends Filter { +} diff --git a/src/org/ibex/mail/filter/DCC.java b/src/org/ibex/mail/filter/DCC.java new file mode 100644 index 0000000..6f1eb0f --- /dev/null +++ b/src/org/ibex/mail/filter/DCC.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** implements the Distributed Checksum Clearinghouse spam filtering protocol */ +public class DCC extends SpamFilter { +} diff --git a/src/org/ibex/mail/filter/HTML2Text.java b/src/org/ibex/mail/filter/HTML2Text.java new file mode 100644 index 0000000..d45a468 --- /dev/null +++ b/src/org/ibex/mail/filter/HTML2Text.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** converts text/html parts into nice ASCII text for mutt/gnus/pine/etc */ +public class HTML2Text extends Filter { +} diff --git a/src/org/ibex/mail/filter/PGP.java b/src/org/ibex/mail/filter/PGP.java new file mode 100644 index 0000000..35a04ce --- /dev/null +++ b/src/org/ibex/mail/filter/PGP.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** checks PGP signatures on incoming mail */ +public class PGP extends Filter { +} diff --git a/src/org/ibex/mail/filter/ReturnReciept.java b/src/org/ibex/mail/filter/ReturnReciept.java new file mode 100644 index 0000000..33603d2 --- /dev/null +++ b/src/org/ibex/mail/filter/ReturnReciept.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** sticks a 1x1 gif in outbound messages so we can tell when the message is read */ +public class ReturnReciept extends Filter { +} diff --git a/src/org/ibex/mail/filter/RewriteFilter.java b/src/org/ibex/mail/filter/RewriteFilter.java new file mode 100644 index 0000000..039f5f1 --- /dev/null +++ b/src/org/ibex/mail/filter/RewriteFilter.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** a flexible, regexp-based email address rewriting engine */ +public class RewriteFilter extends Filter { +} diff --git a/src/org/ibex/mail/filter/SingleUse.java b/src/org/ibex/mail/filter/SingleUse.java new file mode 100644 index 0000000..37f317f --- /dev/null +++ b/src/org/ibex/mail/filter/SingleUse.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** tags outgoing messages with a single-use email address */ +public class SingleUse extends Filter { +} diff --git a/src/org/ibex/mail/filter/SpamAssassin.java b/src/org/ibex/mail/filter/SpamAssassin.java new file mode 100644 index 0000000..5a427b1 --- /dev/null +++ b/src/org/ibex/mail/filter/SpamAssassin.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** invokes SpamAssassin */ +public class SpamAssassin extends SpamFilter { +} diff --git a/src/org/ibex/mail/filter/SpamFilter.java b/src/org/ibex/mail/filter/SpamFilter.java new file mode 100644 index 0000000..bc075d7 --- /dev/null +++ b/src/org/ibex/mail/filter/SpamFilter.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** base class for spam filters */ +public class SpamFilter extends Filter { +} diff --git a/src/org/ibex/mail/filter/VipulsRazor.java b/src/org/ibex/mail/filter/VipulsRazor.java new file mode 100644 index 0000000..f2479d5 --- /dev/null +++ b/src/org/ibex/mail/filter/VipulsRazor.java @@ -0,0 +1,4 @@ +package org.ibex.mail.filter; +/** invokes Vipul's Razor */ +public class VipulsRazor extends SpamFilter { +} diff --git a/src/org/ibex/mail/protocol/Fax.java b/src/org/ibex/mail/protocol/Fax.java new file mode 100644 index 0000000..56271f3 --- /dev/null +++ b/src/org/ibex/mail/protocol/Fax.java @@ -0,0 +1,4 @@ +package org.ibex.mail.protocol; +/** Fax send/recieve/gateway */ +public class Fax extends MessageProtocol { +} diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java new file mode 100644 index 0000000..a504848 --- /dev/null +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -0,0 +1,34 @@ +package org.ibex.mail.protocol; +import java.net.*; +import java.io.*; + +class IMAPException extends IOException { +} + +interface IMAP { +} + + +public class IMAP extends MessageProtocol { + + public static void main(String[] args) throws Exception { + ServerSocket ss = new ServerSocket(143); + while(true) { + System.out.println("listening"); + final Socket s = ss.accept(); + System.out.println("connected"); + new Thread() { + public void run() { + try { + service(s); + } catch (Exception e) { + e.printStackTrace(); + } + } + }.start(); + } + } + + public static void service(Socket s) throws IOException { + } +} diff --git a/src/org/ibex/mail/protocol/MessageProtocol.java b/src/org/ibex/mail/protocol/MessageProtocol.java new file mode 100644 index 0000000..7542c7b --- /dev/null +++ b/src/org/ibex/mail/protocol/MessageProtocol.java @@ -0,0 +1,7 @@ +package org.ibex.mail.protocol; +/** base class for over-the-wire protocols used to send, recieve, and serve messages */ +import com.caucho.server.connection.*; +import com.caucho.server.port.*; + +public class MessageProtocol extends com.caucho.server.port.Protocol { +} diff --git a/src/org/ibex/mail/protocol/NNTP.java b/src/org/ibex/mail/protocol/NNTP.java new file mode 100644 index 0000000..150c8d0 --- /dev/null +++ b/src/org/ibex/mail/protocol/NNTP.java @@ -0,0 +1,4 @@ +package org.ibex.mail.protocol; +/** NNTP send/recieve */ +public class NNTP extends MessageProtocol { +} diff --git a/src/org/ibex/mail/protocol/POP3.java b/src/org/ibex/mail/protocol/POP3.java new file mode 100644 index 0000000..e34311c --- /dev/null +++ b/src/org/ibex/mail/protocol/POP3.java @@ -0,0 +1,90 @@ +package org.ibex.mail.protocol; +import java.net.*; +import java.io.*; + +// Next step: implement both sides using the POP interface + +class POPException extends IOException { +} + +interface POP { + public abstract void userpass(String user, String pass) throws POPException; + public abstract void apop(String user, String digest) throws POPException; + public abstract BufferedReader top(int m, int maxlines); + public abstract BufferedReader retr(int m); + public abstract long stat(); // top 32 bits is number of messages, bottom 32 is total size + public abstract long[] list(); // top 32 bits is message number, bottom 32 is size + public abstract long list(int m); + public abstract void dele(int m); + public abstract void noop(int m); + public abstract void rset(int m); + public abstract String uidl(int m); + public abstract String[] uidl(); // FIXME, also needs message number +} + +public class POP3 extends MessageProtocol { + + public static void main(String[] args) throws Exception { + ServerSocket ss = new ServerSocket(110); + while(true) { + System.out.println("listening"); + final Socket s = ss.accept(); + System.out.println("connected"); + new Thread() { + public void run() { + try { + service(s); + } catch (Exception e) { + e.printStackTrace(); + } + } + }.start(); + } + } + + public static void service(Socket s) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream())); + pw.print("+OK POP3 server ready\r\n"); + pw.flush(); + String user = null; + String pass = null; + while(true) { + String command = br.readLine().trim(); + System.out.println("command: " + command); + if (command.toUpperCase().startsWith("QUIT ")) { + s.close(); + return; + } else if (command.toUpperCase().startsWith("USER ")) { + user = command.substring(5).trim(); + pw.print("+OK now give me your password\r\n"); + pw.flush(); + } else if (command.toUpperCase().startsWith("PASS ")) { + if (user == null) { + pw.print("-ERR I need your password first\r\n"); + pw.flush(); + } else { + pass = command.substring(5).trim(); + break; + } + } + } + System.out.println("login from " + user + " / " + pass); + String server = user.substring(user.indexOf('@') + 1); + user = user.substring(0, user.indexOf('@')); + Socket pop = new Socket(InetAddress.getByName(server), 117); + + BufferedReader br2 = new BufferedReader(new InputStreamReader(pop.getInputStream())); + PrintWriter pw2 = new PrintWriter(new OutputStreamWriter(pop.getOutputStream())); + + System.out.println("pop said " + br2.readLine()); + pw2.print("USER " + user + "\r\n"); + pw2.flush(); + System.out.println("pop said " + br2.readLine()); + pw2.print("PASS " + pass + "\r\n"); + pw2.flush(); + System.out.println("pop said " + br2.readLine()); + + s.close(); + } +} diff --git a/src/org/ibex/mail/protocol/RSS.java b/src/org/ibex/mail/protocol/RSS.java new file mode 100644 index 0000000..2eef456 --- /dev/null +++ b/src/org/ibex/mail/protocol/RSS.java @@ -0,0 +1,4 @@ +package org.ibex.mail.protocol; +/** RSS send/recieve/gateway */ +public class RSS extends MessageProtocol { +} diff --git a/src/org/ibex/mail/protocol/SMS.java b/src/org/ibex/mail/protocol/SMS.java new file mode 100644 index 0000000..6cd3dc0 --- /dev/null +++ b/src/org/ibex/mail/protocol/SMS.java @@ -0,0 +1,4 @@ +package org.ibex.mail.protocol; +/** SMS send/recieve/gateway */ +public class SMS extends MessageProtocol { +} diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java new file mode 100644 index 0000000..970cc1d --- /dev/null +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -0,0 +1,117 @@ +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 static class Outgoing { + // recommended retry interval is 30 minutes + // give up after 4-5 days + // should keep per-host success/failure so we don't retry on every message + // exponential backoff on retry time? + // check DNS resolvability as soon as domain is provided + // only use implicit A-record if there are no MX-records + // use null-sender for error messages (don't send errors to the null addr) + // to prevent mail loops, drop messages with >100 Recieved headers + private final Queue queue = new Queue(); + public static void send(Message m) { } + public static void enqueue(Message m) { } + public static void bounce(Message m, String reason) { } + private void runq() { + MessageStore store = MessageStore.root.slash("smtp").slash("outgoing"); + int[] outgoing = store.list(); + for(int i=0; iserver which accepts requests sent via SMTP */ +public class XMLRPC extends Target { +}