import
authoradam <adam@megacz.com>
Thu, 18 Mar 2004 08:39:54 +0000 (08:39 +0000)
committeradam <adam@megacz.com>
Thu, 18 Mar 2004 08:39:54 +0000 (08:39 +0000)
darcs-hash:20040318083954-5007d-73270f6e707abb6388f00423c6ff6ee74d36cd40.gz

34 files changed:
doc/README [new file with mode: 0644]
doc/ref/rfc2821.txt.pdf [new file with mode: 0644]
doc/ref/rfc2822.txt.pdf [new file with mode: 0644]
doc/ref/rfc3282.txt.pdf [new file with mode: 0644]
src/org/ibex/mail/Message.java [new file with mode: 0644]
src/org/ibex/mail/filter/Anonymizer.java [new file with mode: 0644]
src/org/ibex/mail/filter/DCC.java [new file with mode: 0644]
src/org/ibex/mail/filter/HTML2Text.java [new file with mode: 0644]
src/org/ibex/mail/filter/PGP.java [new file with mode: 0644]
src/org/ibex/mail/filter/ReturnReciept.java [new file with mode: 0644]
src/org/ibex/mail/filter/RewriteFilter.java [new file with mode: 0644]
src/org/ibex/mail/filter/SingleUse.java [new file with mode: 0644]
src/org/ibex/mail/filter/SpamAssassin.java [new file with mode: 0644]
src/org/ibex/mail/filter/SpamFilter.java [new file with mode: 0644]
src/org/ibex/mail/filter/VipulsRazor.java [new file with mode: 0644]
src/org/ibex/mail/protocol/Fax.java [new file with mode: 0644]
src/org/ibex/mail/protocol/IMAP.java [new file with mode: 0644]
src/org/ibex/mail/protocol/MessageProtocol.java [new file with mode: 0644]
src/org/ibex/mail/protocol/NNTP.java [new file with mode: 0644]
src/org/ibex/mail/protocol/POP3.java [new file with mode: 0644]
src/org/ibex/mail/protocol/RSS.java [new file with mode: 0644]
src/org/ibex/mail/protocol/SMS.java [new file with mode: 0644]
src/org/ibex/mail/protocol/SMTP.java [new file with mode: 0644]
src/org/ibex/mail/protocol/WebMail.java [new file with mode: 0644]
src/org/ibex/mail/store/MessageStore.java [new file with mode: 0644]
src/org/ibex/mail/target/Bounce.java [new file with mode: 0644]
src/org/ibex/mail/target/Darcs.java [new file with mode: 0644]
src/org/ibex/mail/target/LMTP.java [new file with mode: 0644]
src/org/ibex/mail/target/List.java [new file with mode: 0644]
src/org/ibex/mail/target/Pipe.java [new file with mode: 0644]
src/org/ibex/mail/target/Procmail.java [new file with mode: 0644]
src/org/ibex/mail/target/Target.java [new file with mode: 0644]
src/org/ibex/mail/target/Vacation.java [new file with mode: 0644]
src/org/ibex/mail/target/XMLRPC.java [new file with mode: 0644]

diff --git a/doc/README b/doc/README
new file mode 100644 (file)
index 0000000..c5bd6cc
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..e01ef8e
--- /dev/null
@@ -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 (file)
index 0000000..6544d41
--- /dev/null
@@ -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 (file)
index 0000000..6f1eb0f
--- /dev/null
@@ -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 (file)
index 0000000..d45a468
--- /dev/null
@@ -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 (file)
index 0000000..35a04ce
--- /dev/null
@@ -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 (file)
index 0000000..33603d2
--- /dev/null
@@ -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 (file)
index 0000000..039f5f1
--- /dev/null
@@ -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 (file)
index 0000000..37f317f
--- /dev/null
@@ -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 (file)
index 0000000..5a427b1
--- /dev/null
@@ -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 (file)
index 0000000..bc075d7
--- /dev/null
@@ -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 (file)
index 0000000..f2479d5
--- /dev/null
@@ -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 (file)
index 0000000..56271f3
--- /dev/null
@@ -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 (file)
index 0000000..a504848
--- /dev/null
@@ -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 (file)
index 0000000..7542c7b
--- /dev/null
@@ -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 (file)
index 0000000..150c8d0
--- /dev/null
@@ -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 (file)
index 0000000..e34311c
--- /dev/null
@@ -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 (file)
index 0000000..2eef456
--- /dev/null
@@ -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 (file)
index 0000000..6cd3dc0
--- /dev/null
@@ -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 (file)
index 0000000..970cc1d
--- /dev/null
@@ -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; i<outgoing.length; i++) queue.append(store.get(outgoing[i]));
+            while(true) {
+                Message next = queue.dequeue(true);
+                // FIXME
+            }
+        }
+    }
+
+    private class Incoming implements ServerRequest {
+        TcpConnection conn;
+        public Request(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
+        public void init() { }
+
+        public boolean handleRequest() throws IOException {
+            ReadStream rs = conn.getReadStream();
+            WriteStream ws = conn.getWriteStream();
+            ws.setNewLineString("\r\n");
+
+            ws.println("220 " + conn.getVirtualHost() + " ESMTP " + this.getClass().getName());
+
+            Address from = null;
+            Vector to = new Vector();
+            // 551 = no, i won't forward that
+            // 452 = mailbox full
+            // see 4.4 for trace info
+            while(true) {
+                String command = rs.readLine();
+                // FIXME: validate the HELO domain argument
+                //   (double check against other end of connection? must not reject though)
+                if (hello.toUpperCase().startsWith("HELO")) {
+                    ws.println("250 HELO " + conn.getVirtualHost());
+                    from = null;
+                    to = new Vector();
+
+                } else if (hello.toUpperCase().startsWith("EHLO")) {
+                    ws.pritnln("250-" + conn.getVirtualHost());
+                    ws.println("250-SIZE");
+                    ws.println("250 PIPELINING");
+                    from = null;
+                    to = new Vector();
+
+                } else if (command.toUpperCase().startsWith("RSET")) {
+                    from = null;
+                    to = new Vector();
+                    ws.println("250 reset ok");
+
+                } else if (command.toUpperCase().startsWith("MAIL FROM:")) {
+                    command = command.substring(10).trim();
+                    if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
+                    from = RFC2822.Address.parse(command);
+
+                } else if (command.toUpperCase().startsWith("RCPT TO:")) {
+                    if (from == null) {
+                        ws.println("503 MAIL FROM must precede RCPT TO");
+                        continue;
+                    }
+                    command = command.substring(10).trim();
+                    if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
+                    to.addElement(RFC2822.Address.parse(command));
+
+                } else if (command.toUpperCase().startsWith("DATA")) {
+                    if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; }
+                    if (to.size() == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
+                    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);
+                    }
+                    // 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.");
+
+                } else if (command.toUpperCase().startsWith("VRFY")) { // FIXME, see code 252
+                } else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available");
+                } else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK");
+                } else if (command.toUpperCase().startsWith("QUIT")) {
+                    ws.println("221 " + conn.getVirtualHost() + " closing connection");
+                    break;
+
+                } else {
+                    ws.println("500 unrecognized command");
+                }                    
+            
+            
+            return false;  // FIXME: what does this mean?
+        }
+    }
+}
diff --git a/src/org/ibex/mail/protocol/WebMail.java b/src/org/ibex/mail/protocol/WebMail.java
new file mode 100644 (file)
index 0000000..8217193
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.protocol;
+/** a nice WebMail GUI */
+public class WebMail extends MessageProtocol {
+}
diff --git a/src/org/ibex/mail/store/MessageStore.java b/src/org/ibex/mail/store/MessageStore.java
new file mode 100644 (file)
index 0000000..d2aec48
--- /dev/null
@@ -0,0 +1,58 @@
+package org.ibex.mail.store;
+import org.ibex.util.*;
+import java.io.*;
+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<ret.length; i++, j++) {
+            try {
+                ret[j] = Integer.parseInt(names[i].substring(0, names[i].length - 1));
+            } catch (NumberFormatException nfe) {
+                Log.warn(MessageStore.class, "NumberFormatException: " + names[i].substring(0, names[i].length - 1));
+                j--;
+                int[] newret = new int[ret.length - 1];
+                System.arrayCopy(ret, 0, newret, 0, newret.length);
+                ret = newret;
+            }
+        }
+        return ret;
+    }
+
+    /** returns a message identifier */
+    public synchronized int add(StoredMessage message) throws IOException {
+        int[] all = list();
+        int max = 0;
+        for(int i=0; i<all.length; i++) max = Math.max(max, all[i]);
+        int target = max++;
+        File f = new File(path + File.separatorChar + max + ".-");
+        FileOutputStream fo = new FileOutputStream(f);
+        message.dump(fo);
+        fo.close();
+        f.renameTo(path + File.separatorChar + max + ".");
+        return target;
+    }
+
+    public StoredMessage get(int messagenum) throws IOException {
+        File f = new File(path + File.separatorChar + messagenum + ".");        
+        if (!f.exists()) throw new FileNotFoundException(f);
+        return StoredMessage.undump(new FileInputStream(f));
+    }
+
+    // query types: stringmatch (headers, body), header element, deletion status, date range, message size
+    public StoredMessage[] query(int maxResults) {
+        throw new RuntimeException("MessageStore.query() not implemented yet");
+    }
+
+}
diff --git a/src/org/ibex/mail/target/Bounce.java b/src/org/ibex/mail/target/Bounce.java
new file mode 100644 (file)
index 0000000..37d1090
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** bounces a message */
+public class Bounce extends Target {
+}
diff --git a/src/org/ibex/mail/target/Darcs.java b/src/org/ibex/mail/target/Darcs.java
new file mode 100644 (file)
index 0000000..3b3c0d8
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** simple class to handle darcs patches */
+public class Darcs extends Pipe {
+}
diff --git a/src/org/ibex/mail/target/LMTP.java b/src/org/ibex/mail/target/LMTP.java
new file mode 100644 (file)
index 0000000..3f230d2
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** implementation of the Local Mail Transport Protocol */
+public class LMTP extends Target {
+}
diff --git a/src/org/ibex/mail/target/List.java b/src/org/ibex/mail/target/List.java
new file mode 100644 (file)
index 0000000..b8860fd
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** A full-featured mailing list manager */
+public class List extends Target {
+}
diff --git a/src/org/ibex/mail/target/Pipe.java b/src/org/ibex/mail/target/Pipe.java
new file mode 100644 (file)
index 0000000..5ac2f1c
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** generic pipe-the-message-to-a-file target */
+public class Pipe extends Target {
+}
diff --git a/src/org/ibex/mail/target/Procmail.java b/src/org/ibex/mail/target/Procmail.java
new file mode 100644 (file)
index 0000000..ab16ecc
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** callout to support legacy .procmailrc files */
+public class Procmail extends Pipe {
+}
diff --git a/src/org/ibex/mail/target/Target.java b/src/org/ibex/mail/target/Target.java
new file mode 100644 (file)
index 0000000..97e3eb1
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** base class for mail message "destinations" */
+public class Target {
+}
diff --git a/src/org/ibex/mail/target/Vacation.java b/src/org/ibex/mail/target/Vacation.java
new file mode 100644 (file)
index 0000000..f8e71d9
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** functionality similar to the UNIX vacation command */
+public class Vacation extends Target {
+}
diff --git a/src/org/ibex/mail/target/XMLRPC.java b/src/org/ibex/mail/target/XMLRPC.java
new file mode 100644 (file)
index 0000000..ec6ca7c
--- /dev/null
@@ -0,0 +1,4 @@
+package org.ibex.mail.target;
+/** an XML-RPC <i>server</i> which accepts requests sent via SMTP */
+public class XMLRPC extends Target {
+}