almost working
authoradam <adam@megacz.com>
Fri, 7 May 2004 22:13:52 +0000 (22:13 +0000)
committeradam <adam@megacz.com>
Fri, 7 May 2004 22:13:52 +0000 (22:13 +0000)
darcs-hash:20040507221352-5007d-e88f7b3c86d0822f852cb2c536e3d2be6cacb0d1.gz

Makefile
src/org/ibex/mail/Address.java [new file with mode: 0644]
src/org/ibex/mail/MailException.java
src/org/ibex/mail/Message.java
src/org/ibex/mail/protocol/Incoming.java
src/org/ibex/mail/protocol/SMTP.java
src/org/ibex/mail/store/MessageStore.java
src/org/ibex/mail/target/Script.java
src/org/ibex/mail/target/Target.java

index bf021a7..c432e83 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,13 +7,15 @@ dist-clean:
 sources    := $(shell find src -name \*.java)
 sources    += upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java
 
+upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.java: .download_org.ibex.crypto
+
 .download_org.ibex.%:
        @echo -e "\033[1mfetching repository                 org.ibex.$*\033[0m"
        @mkdir -p upstream; cd upstream; rm -rf org.ibex.$*; rm -rf org.ibex.$*_*
        @cd upstream; darcs get --verbose --partial --repo-name=org.ibex.$* http://$*.ibex.org
        @touch $@
 
-.build_org.ibex.%:
+.build_org.ibex.%: .download_org.ibex.%
        @echo -e "\033[1mbuilding repository                 org.ibex.$*\033[0m"
        @cd upstream/org.ibex.$*; make compile
        @touch $@
diff --git a/src/org/ibex/mail/Address.java b/src/org/ibex/mail/Address.java
new file mode 100644 (file)
index 0000000..92bdbc8
--- /dev/null
@@ -0,0 +1,31 @@
+package org.ibex.mail;
+import org.ibex.crypto.*;
+import org.ibex.js.*;
+import org.ibex.util.*;
+import org.ibex.mail.protocol.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+
+public class Address extends JSReflection {
+    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) throws Address.Malformed {
+        s = s.trim();
+        if (s.indexOf('<') != -1) {
+            if (s.indexOf('>') == -1) { throw new Malformed("found open-angle-bracket (<) but not close-angle-bracket (>)"); }
+            description = s.substring(0, s.indexOf('<')) + s.substring(s.indexOf('>') + 1);
+            s = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
+        } else {
+            description = "";
+        }
+        if (s.indexOf('@') == -1) { throw new Malformed("no @-sign in email address"); }
+        user = s.substring(0, s.indexOf('@'));
+        host = s.substring(s.indexOf('@')+1);
+    }
+    public String coerceToString() { return toString(); }
+    public String toString() { return description.equals("") ? (user +"@"+ host) : description+" <" + user +"@"+ host + ">"; }
+    public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } }
+}
index 6bb7ef9..1b789b7 100644 (file)
@@ -4,7 +4,10 @@ import java.io.*;
 
 public class MailException extends Exception {
 
+    public MailException() { }
+    public MailException(String s) { super(s); }
     public static class MailboxFull extends MailException { }
+    public static class Malformed extends MailException { public Malformed(String s) { super(s); } }
     public static class RelayingDenied extends MailException { }
     public static class IOException extends MailException {
         // FIXME: fill in stack trace
index 96f0873..8303ea8 100644 (file)
@@ -7,7 +7,6 @@ import java.util.*;
 import java.net.*;
 import java.io.*;
 
-// FIXME MIME: RFC2045, 2046, 2049
 // NOTE: always use Win32 line endings
 // hard line limit: 998 chars
 // soft line limit (suggested): 78 chars
@@ -17,6 +16,7 @@ import java.io.*;
 // body needs CRLF; one or the other alone is not acceptable
 // date/time parsing: see 3.3
 
+// FEATURE: MIME RFC2045, 2046, 2049
 // FEATURE: PGP-signature-parsing
 // FEATURE: mailing list header parsing
 // FEATURE: delivery status notification (and the sneaky variety)
@@ -28,7 +28,6 @@ public class Message extends JSReflection {
     public final Hashtable headers;   // hash of headers (not including resent's and traces)
     public final String body;         // entire body
 
-    // parsed header fields
     public final Date date;
     public final Address to;
     public final Address from;        // if multiple From entries, this is sender
@@ -38,75 +37,69 @@ public class Message extends JSReflection {
     public final Address[] cc;
     public final Address[] bcc;
     public final Hashtable[] resent;
-    public final Trace[] traces;
+    public Trace[] traces;
 
-    // envelope fields
     public final Address envelopeFrom;
     public final Address[] envelopeTo;
 
-    public void dump(OutputStream os) {
-        Log.error(this, "not implemented");
+    public void dump(OutputStream os) throws IOException {
+        Writer w = new OutputStreamWriter(os);
+        w.write(allHeaders);
+        w.write("\r\n");
+        w.write(body);
+        w.flush();
     }
 
     public static class StoredMessage extends Message {
-        public StoredMessage(/*ReadStream rs*/SMTP.LineReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
-            super(rs, dotTerminatedLikeSMTP); uid = -1; }
-        public final int uid;
+        public int uid;
         public boolean deleted = false;
         public boolean read = false;
         public boolean answered = false;
-        public String dumpStoredForm() { throw new Error("StoredMessage.dumpStoredForm() not implemented"); };
-        public static StoredMessage undump(InputStream os) {
-            Log.error(StoredMessage.class, "not implemented");
-            return null;
-        }
+        public StoredMessage(LineReader rs) throws IOException, MailException.Malformed { super(rs); }
     }
 
-    public static class Address extends JSReflection {
-        public String coerceToString() { return toString(); }
-        public String toString() {
-            if (description == null || description.equals("")) return user +"@"+ host;
-            return description + " " + "<" + user +"@"+ host + ">";
-        }
-        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('>'));
+    public class Trace {
+        final String returnPath;
+        final Element[] elements;
+        public Trace(LineReader lr) throws Trace.Malformed, IOException {
+            String retPath = lr.readLine();
+            if (!retPath.startsWith("Return-Path:")) throw new Trace.Malformed("trace did not start with Return-Path header");
+            returnPath = retPath.substring(12).trim();
+            Vec el = new Vec();
+            while(true) {
+                String s = lr.readLine();
+                if (s == null) break;
+                if (!s.startsWith("Received:")) { lr.pushback(s); break; }
+                s = s.substring(9).trim();
+                el.addElement(new Element(s));
             }
-            if (s.indexOf('@') == -1) { /* FIXME */ }
-            description = descrip;
-            user = s.substring(0, s.indexOf('@'));
-            host = s.substring(s.indexOf('@')+1);
+            elements = new Element[el.size()];
+            el.copyInto(elements);
         }
-    }
-
-    public class Trace {
-        String returnPath = null;
-        Element[] elements;
         public class Element {
-            // FIXME final
-            String fromDomain;
-            String fromIP;
-            String toDomain;
-            String forWhom;
-            Date date;
+             String fromDomain;
+             String fromIP;
+             String toDomain;
+             String forWhom;
+             Date date;
+            public Element(String fromDomain, String fromIP, String toDomain, String forWhom, Date date) {
+                this.fromDomain=fromDomain; this.fromIP=fromIP; this.toDomain=toDomain; this.forWhom=forWhom; this.date=date; }
+            public Element(String s) throws Trace.Malformed {
+                StringTokenizer st = new StringTokenizer(s);
+                if (!st.nextToken().equals("FROM")) throw new Trace.Malformed("trace did note have a FROM element: " + s);
+                fromDomain = st.nextToken();
+                if (!st.nextToken().equals("BY")) throw new Trace.Malformed("trace did note have a BY element: " + s);
+                toDomain = st.nextToken();
+                // FIXME not done yet
+            }
         }
+        public class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } }
     }
 
-    // FIXME: support dotTerminatedLikeSMTP
-    public Message(/*ReadStream rs*/SMTP.LineReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
+    public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } }
+    public Message(LineReader rs) throws IOException, Malformed {
         String key = null;
         StringBuffer all = new StringBuffer();
-        String lastKey = null;
         replyto = null;
         subject = null;
         messageid = null;
@@ -124,27 +117,28 @@ public class Message extends JSReflection {
         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);
+            if (s.length() == 0 || Character.isSpace(s.charAt(0))) {
+                if (key == null) throw new Malformed("Message began with a blank line; no headers");
+                headers.put(key, headers.get(key) + s);
                 continue;
             }
-            if (s.indexOf(':') == -1) {
-                /* FIXME */
-                break;
-            }
-
-            lastKey = key = s.substring(0, s.indexOf(':'));
+            if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
+            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                    
+                    rs.pushback(s);
+                    Trace t = new Trace(rs);
+                    Trace[] newTraces = new Trace[traces == null ? 1 : traces.length + 1];
+                    if (traces != null) System.arraycopy(traces, 0, newTraces, 0, traces.length);
+                    traces = newTraces;
+                    traces[traces.length-1] = t;
+
                 } else {
                     // just append it to the previous one; valid for Comments/Keywords
                     val = headers.get(key) + " " + val;
@@ -154,13 +148,8 @@ public class Message extends JSReflection {
         }
 
         allHeaders = all.toString();
-
         StringBuffer body = new StringBuffer();
-        for(String s = rs.readLine();; s = rs.readLine()) {
-            if (s == null || (dotTerminatedLikeSMTP && s.equals("."))) break;
-            body.append(s);
-        }
-
+        for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; else body.append(s + "\r\n"); }
         this.body = body.toString();
     }
 
index f02b657..d46a75e 100644 (file)
@@ -5,10 +5,8 @@ import org.ibex.mail.target.*;
 import java.io.*;
 
 public class Incoming {
-
-    protected void accept(Message m) throws IOException {
-        MessageStore.transcript.add((Message.StoredMessage)m);     // currently, we write all inbound messages to the transcript
+    protected void accept(Message m) throws IOException, MailException {
+        MessageStore.transcript.add(m);     // currently, we write all inbound messages to the transcript
         Target.root.accept(m);
     }
-
 }
index 2b01ed7..ff1a5d7 100644 (file)
@@ -6,20 +6,34 @@ import org.ibex.util.*;
 import java.net.*;
 import java.io.*;
 import java.util.*;
+import java.text.*;
 
 public class SMTP extends MessageProtocol {
 
-    public static void main(String[] s) throws IOException {
+    public static String convdir = null;
+    public static void main(String[] s) throws Exception {
+        String logto = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
+        logto += File.separatorChar + "log";
+        Log.file(logto);
+        convdir = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
+        convdir += File.separatorChar + "conversation";
+        new File(convdir).mkdirs();
         SMTP smtp = new SMTP();
-        ServerSocket ss = new ServerSocket(1025);
-        Socket sock = ss.accept();
-        smtp.handle(sock);
+        int port = Integer.parseInt(System.getProperty("ibex.mail.port", "25"));
+        Log.info(SMTP.class, "binding to port " + port + "...");
+        ServerSocket ss = new ServerSocket(port);
+        Log.info(SMTP.class, "listening for connections...");
+        while(true) {
+            final Socket sock = ss.accept();
+            new Thread() { public void run() { smtp.handle(sock); } }.start();
+        }
     }
 
-    public SMTP() {/* setProtocolName("SMTP"); */}
-    public void handle(Socket s) throws IOException { new Listener(s).handleRequest(); }
+    //public SMTP() { setProtocolName("SMTP"); }
     //public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); }
 
+    public void handle(Socket s) { new Listener(s, "megacz.com").handleRequest(); }
+
     public static class Outgoing {
         //  recommended retry interval is 30 minutes
         //  give up after 4-5 days
@@ -46,125 +60,156 @@ public class SMTP extends MessageProtocol {
         }
     }
 
-    public static class LineReader extends InputStreamReader {
-        public LineReader(InputStream r) { super(r); }
-        public String readLine() throws IOException {
-            StringBuffer ret = new StringBuffer();
-            while(true) {
-                int c = read();
-                if (c == -1) throw new EOFException();
-                if (c == '\n') return ret.toString();
-                if (c == '\r') continue; //return ret.toString();
-                ret.append((char)c);
-            }
-        }
-    }
+    private static String lastTime = null;
+    private static int lastCounter = 0;
 
     private class Listener extends Incoming /*implements ServerRequest*/ {
-        //TcpConnection conn;
         Socket conn;
-        //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
-        public Listener(Socket conn) throws IOException { this.conn = conn; conn.setSoTimeout(5 * 60 * 1000); }
+        String vhost;
         public void init() { }
+        public Listener(Socket conn, String vhost) { this.vhost = vhost; this.conn = conn; }
 
-        public boolean handleRequest() throws IOException {
-            //ReadStream rs = conn.getReadStream();
-            //WriteStream ws = conn.getWriteStream();
-            LineReader rs = new LineReader(conn.getInputStream());
-            PrintWriter ws = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
+        //TcpConnection conn;
+        //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
+        public boolean handleRequest() {
+            try {
+                conn.setSoTimeout(5 * 60 * 1000);
+                StringBuffer logMessage = new StringBuffer();
+                String time = new SimpleDateFormat("yy.MMM.dd-hh:mm:ss").format(new Date());
+                synchronized (SMTP.class) {
+                    if (lastTime != null && lastTime.equals(time)) {
+                        time += "." + (++lastCounter);
+                    } else {
+                        lastTime = time;
+                    }
+                }
+                String conversationId = time;
+                Log.setThreadAnnotation("[conversation/" + conversationId + "] ");
+                InetSocketAddress remote = (InetSocketAddress)conn.getRemoteSocketAddress();
+                Log.info(this, "connection from " + remote.getHostName() + ":" + remote.getPort() +
+                         " (" + remote.getAddress() + ")");
+                PrintWriter logf =
+                    new PrintWriter(new OutputStreamWriter(new FileOutputStream(convdir + File.separatorChar + conversationId)));
+                try {
+                    return handleRequest(new LoggedLineReader(new InputStreamReader(conn.getInputStream()), logf),
+                                         new LoggedPrintWriter(new OutputStreamWriter(conn.getOutputStream()), logf));
+                } catch(Throwable t) {
+                    Log.warn(this, t);
+                } finally {
+                    logf.close();
+                    Log.setThreadAnnotation("");
+                }
+            } catch (Exception e) {
+                Log.error(this, e);
+            }
+            return false;
+        }
 
-            // FIXME
-            //ws.setNewLineString("\r\n");
+        private class LoggedLineReader extends LineReader {
+            PrintWriter log;
+            public LoggedLineReader(Reader r, PrintWriter log) { super(r); this.log = log; }
+            public String readLine() throws IOException {
+                String s = super.readLine();
+                if (s != null) { log.println("C: " + s); log.flush(); }
+                return s;
+            }
+        }
 
-            ws.println("220 " + /*conn.getVirtualHost()*/ "megacz.com" + " ESMTP " + this.getClass().getName());
-            ws.flush();
+        private class LoggedPrintWriter extends PrintWriter {
+            PrintWriter log;
+            public LoggedPrintWriter(Writer w, PrintWriter log) { super(w); this.log = log; }
+            public void println(String s) {
+                log.println("S: " + s);
+                super.println(s);
+                flush();
+            }
+        }
 
-            Message.Address from = null;
+        public boolean handleRequest(LineReader rs, PrintWriter ws) throws IOException, MailException {
+            //ReadStream rs = conn.getReadStream();
+            //WriteStream ws = conn.getWriteStream();
+            //ws.setNewLineString("\r\n");
+            ws.println("220 " + vhost + " 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 (command.toUpperCase().startsWith("HELO")) {
-                    ws.println("250 HELO " + /*conn.getVirtualHost()*/("megacz.com"));
-                    ws.flush();
+                String c = command.toUpperCase();
+                if (c.startsWith("HELO")) {
+                    ws.println("250 HELO " + vhost);
                     from = null;
                     to = new Vector();
 
-                } else if (command.toUpperCase().startsWith("EHLO")) {
-                    ws.println("250-" + /*conn.getVirtualHost()*/("megacz.com"));
+                } else if (c.startsWith("EHLO")) {
+                    ws.println("250-" + vhost);
                     ws.println("250-SIZE");
                     ws.println("250 PIPELINING");
-                    ws.flush();
                     from = null;
                     to = new Vector();
 
-                } else if (command.toUpperCase().startsWith("RSET")) {
+                } else if (c.startsWith("RSET")) {
                     from = null;
                     to = new Vector();
                     ws.println("250 reset ok");
-                    ws.flush();
 
-                } else if (command.toUpperCase().startsWith("MAIL FROM:")) {
+                } else if (c.startsWith("MAIL FROM:")) {
                     command = command.substring(10).trim();
-                    if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
-                    from = new Message.Address(command);
+                    from = new Address(command);
                     ws.println("250 " + from + " is syntactically correct");
-                    ws.flush();
 
-                } else if (command.toUpperCase().startsWith("RCPT TO:")) {
+                } else if (c.startsWith("RCPT TO:")) {
                     if (from == null) {
                         ws.println("503 MAIL FROM must precede RCPT TO");
-                        ws.flush();
                         continue;
                     }
+                    // FIXME: 551 = no, i won't forward that
                     command = command.substring(9).trim();
                     if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
-                    Message.Address addr = new Message.Address(command);
+                    Address addr = new Address(command);
                     to.addElement(addr);
                     ws.println("250 " + addr + " is syntactically correct");
-                    ws.flush();
 
-                } else if (command.toUpperCase().startsWith("DATA")) {
-                    if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); ws.flush(); continue; }
-                    if (to == null) { ws.println("503 RCPT TO command must precede DATA"); ws.flush(); continue; }
+                } else if (c.startsWith("DATA")) {
+                    if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; }
+                    if (to == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
                     ws.println("354 Enter message, ending with \".\" on a line by itself");
-                    ws.flush();
                     StringBuffer data = new StringBuffer();
                     // move this into the Message class
                     boolean good = false;
                     try {
-                        good = true;
-                        Message m = new Message(rs, true);
-                        accept(m);
-                    } finally {
-                        //ws.println("251 user not local; will forward");
-                        if (good) { ws.println("250 OK message accepted for delivery"); ws.flush(); }
-                        else { /* FIXME */ }
+                        accept(new Message(new DotTerminatedLineReader(rs)));
+                    } catch (MailException.Malformed mfe) {
+                        ws.println("501 " + mfe.toString()); break;
+                    } catch (MailException.MailboxFull mbf) {
+                        ws.println("452 " + mbf);
+                    } catch (IOException ioe) {
+                        ws.println("554 " + ioe.toString()); break;
                     }
+                    ws.println("250 message accepted"); break;
                     
-                } else if (command.toUpperCase().startsWith("HELP")) {
-                    ws.println("214 sorry, you are beyond help.  please see a trained professional.");
-                    ws.flush();
-
-                } else if (command.toUpperCase().startsWith("VRFY")) { // FIXME, see code 252
-                } else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available"); ws.flush();
-                } else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK"); ws.flush();
-                } else if (command.toUpperCase().startsWith("QUIT")) {
-                    ws.println("221 " + /*conn.getVirtualHost()*/("megacz.com") + " closing connection");
-                    ws.flush();
-                    break;
-
-                } else {
-                    ws.println("500 unrecognized command");
-                    ws.flush();
+                } else if (c.startsWith("HELP")) { ws.println("214 you are beyond help.  see a trained professional.");
+                } else if (c.startsWith("VRFY")) { ws.println("252 We don't VRFY; proceed anyway");
+                } else if (c.startsWith("EXPN")) { ws.println("550 EXPN not available");
+                } else if (c.startsWith("NOOP")) { ws.println("250 OK");
+                } else if (c.startsWith("QUIT")) { ws.println("221 " + vhost + " closing connection"); break;
+                } else                           { ws.println("500 unrecognized command");
                 }                    
             
             }
-            return false;  // FIXME: what does this mean?
+            return false; // always tell resin to close the connection
+        }
+    }
+
+    private static class DotTerminatedLineReader extends LineReader {
+        private final LineReader r;
+        private boolean done = false;
+        public DotTerminatedLineReader(LineReader r) { super(null); this.r = r; }
+        public String readLine() throws IOException {
+            if (done) return null;
+            String s = r.readLine();
+            if (s.equals(".")) { done = true; return null; }
+            if (s.startsWith(".")) return s.substring(1);
+            return s;
         }
     }
 }
index 9c452b5..5b15243 100644 (file)
@@ -9,14 +9,14 @@ import java.text.*;
 // FIXME: appallingly inefficient
 public class MessageStore {
 
-    private static final String STORAGE_ROOT = System.getProperty("ibex.mail.root",
-                                                             File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
+    private static final String STORAGE_ROOT =
+        System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
 
     //public final FileBased root = new FileBased(STORAGE_ROOT + File.separatorChar);
-    public static FileBased transcript = null;
+    public static Transcript transcript = null;
     static {
         try {
-            transcript = new FileBased(STORAGE_ROOT + File.separatorChar + "transcript");
+            transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -25,7 +25,7 @@ public class MessageStore {
     public MessageStore slash(String name) throws IOException {
         throw new Error(this.getClass().getName() + " does not support the slash() method"); }
     public int[] list() { throw new Error(this.getClass().getName() + " does not support the list() method"); }
-    public int add(Message.StoredMessage message) throws IOException {
+    public int add(Message message) throws IOException {
         throw new Error(this.getClass().getName() + " does not support the add() method"); }
     public Message.StoredMessage get(int messagenum) throws IOException {
         throw new Error(this.getClass().getName() + " does not support the get() method"); }
@@ -33,18 +33,18 @@ public class MessageStore {
         throw new Error(this.getClass().getName() + " does not support the query() method"); }
 
     /** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
-    public static class Transcript {
+    public static class Transcript extends MessageStore {
         private String path;
         public Transcript(String path) throws IOException { new File(this.path = path).mkdirs(); }
         private static String lastTime = null;
         private static int lastCounter = 0;
 
         /** returns a message identifier */
-        public synchronized int add(Message.StoredMessage message) throws IOException {
-            File today = new File(path + File.separatorChar + (new SimpleDateFormat("yyyyy.MMMMM.dd").format(new Date())));
+        public synchronized int add(Message message) throws IOException {
+            File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
             today.mkdirs();
             
-            String time = new SimpleDateFormat("").format(new Date("hh.mm.ss"));
+            String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
             synchronized (Transcript.class) {
                 if (lastTime != null && lastTime.equals(time)) {
                     time += "." + (++lastCounter);
@@ -54,9 +54,8 @@ public class MessageStore {
             }
             
             File target = new File(today.getPath() + File.separatorChar + time + ".txt");
-            String msg = message.dumpStoredForm();
             OutputStream os = new FileOutputStream(target);
-            os.write(msg.getBytes("UTF-8"));  // FIXME: right?
+            message.dump(os);
             os.close();
             return -1; // FIXME
         }
@@ -85,7 +84,7 @@ public class MessageStore {
         }
 
         /** returns a message identifier */
-        public synchronized int add(Message.StoredMessage message) throws IOException {
+        public synchronized int add(Message message) throws IOException {
             int[] all = list();
             int max = 0;
             for(int i=0; i<all.length; i++) max = Math.max(max, all[i]);
@@ -101,7 +100,15 @@ public class MessageStore {
         public Message.StoredMessage get(int messagenum) throws IOException {
             File f = new File(path + File.separatorChar + messagenum + ".");        
             if (!f.exists()) throw new FileNotFoundException(f.toString());
-            return Message.StoredMessage.undump(new FileInputStream(f));
+            try {
+                Message.StoredMessage ret = new Message.StoredMessage(new LineReader(new InputStreamReader(new FileInputStream(f))));
+                // FIXME: set answered/read/etc here
+                return ret;
+            } catch (MailException.Malformed malf) {
+                Log.error(this, "This should never happen");
+                Log.error(this, malf);
+                return null;
+            }
         }
 
         // query types: stringmatch (headers, body), header element, deletion status, date range, message size
index 4c41be4..b7deee8 100644 (file)
@@ -13,7 +13,8 @@ public class Script extends Target {
     public static Script root = null;
     static {
         try {
-            root = new Script(System.getProperty("ibex.mail.conf", File.separatorChar + "etc" + File.separatorChar + "org.ibex.mail.conf"));
+            root = new Script(System.getProperty("ibex.mail.conf",
+                                                 File.separatorChar + "etc" + File.separatorChar + "org.ibex.mail.conf"));
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -21,13 +22,24 @@ public class Script extends Target {
 
     final JS js;
     public Script(String filePath) throws JSExn, IOException {
-        js = JS.fromReader(filePath, 0, new InputStreamReader(new FileInputStream(filePath))); }
+        js = JS.cloneWithNewParentScope(JS.fromReader(filePath, 0, new InputStreamReader(new FileInputStream(filePath))),
+                                        new ScriptScope(null)); }
+
+    private static class ScriptScope extends JSScope {
+        Message m;
+        ScriptEnv env = new ScriptEnv();
+        public ScriptScope(Message m) { super(null); this.m = m; }
+        public Object get(Object o) {
+            if (o.equals("m")) return m;
+            if (o.equals("ibex")) return env;
+            return null;
+        }
+    }
 
     public void accept(Message m) throws IOException {
         try {
             // currently, we write all inbound messages to the transcript
             // FIXME
-            //MessageStore.transcript.add(m);
             Object ret = js.call(m, null, null, null, 1);
             if (ret instanceof Target) {
                 ((Target)ret).accept(m);
@@ -35,7 +47,8 @@ public class Script extends Target {
                 // FIXME: return value?
                 ((Filter)ret).process(m);
             } else {
-                throw new IOException("configuration script returned a " + ret.getClass().getName());
+                if (ret == null) throw new IOException("configuration script returned null");
+                else throw new IOException("configuration script returned a " + ret.getClass().getName());
             }
         } catch (JSExn e) {
             e.printStackTrace();
index ba69c00..b4425a9 100644 (file)
@@ -6,5 +6,5 @@ import org.ibex.js.*;
 /** base class for mail message "destinations" */
 public class Target {
     public static final Target root = Script.root;
-    public void accept(Message m) throws IOException { /* FIXME */ }
+    public void accept(Message m) throws IOException, MailException { /* FIXME */ }
 }