bogus patch
authoradam <adam@megacz.com>
Sat, 3 Jul 2004 18:49:45 +0000 (18:49 +0000)
committeradam <adam@megacz.com>
Sat, 3 Jul 2004 18:49:45 +0000 (18:49 +0000)
darcs-hash:20040703184945-5007d-e0168ad3872fea92ae8be47149427e44517d3461.gz

20 files changed:
Makefile
src/org/ibex/io/Connection.java [new file with mode: 0644]
src/org/ibex/io/Stream.java [new file with mode: 0644]
src/org/ibex/jinetd/Host.java [new file with mode: 0644]
src/org/ibex/jinetd/Listener.java [new file with mode: 0644]
src/org/ibex/jinetd/Loader.java [new file with mode: 0644]
src/org/ibex/jinetd/Main.java [new file with mode: 0644]
src/org/ibex/jinetd/Port.java [new file with mode: 0644]
src/org/ibex/jinetd/Root.java [new file with mode: 0644]
src/org/ibex/jinetd/Watched.java [new file with mode: 0644]
src/org/ibex/jinetd/Watcher.java [new file with mode: 0644]
src/org/ibex/jinetd/Worker.java [new file with mode: 0644]
src/org/ibex/mail/Address.java
src/org/ibex/mail/Main.java
src/org/ibex/mail/Message.java
src/org/ibex/mail/Target.java
src/org/ibex/mail/protocol/IMAP.java
src/org/ibex/mail/protocol/SMTP.java
src/org/ibex/mail/target/FileBasedMailbox.java
src/org/ibex/mail/target/Script.java

index 508dffa..70fe175 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ dist-clean:
        rm -rf .configure* .install* build .compile .build*
 
 sources    := $(shell find src -name \*.java)
-#sources    += upstream/org.ibex.crypto/src/org/ibex/crypto/Base36.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
 
diff --git a/src/org/ibex/io/Connection.java b/src/org/ibex/io/Connection.java
new file mode 100644 (file)
index 0000000..bb7907b
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [LGPL]
+package org.ibex.io;
+
+import java.net.*;
+import java.io.*;
+import org.ibex.util.*;
+
+/** a stream backed by a socket */
+public class Connection extends Stream {
+    private final Socket s;
+    public final String vhost;
+    public Connection(Socket s, String vhost) { super(s); this.vhost = vhost; this.s = s; }
+    public int getLocalPort()                 { return s.getLocalPort(); }
+    public int getRemotePort()                { return s.getPort(); }
+    public InetAddress getRemoteAddress()     { return ((InetSocketAddress)s.getRemoteSocketAddress()).getAddress(); }
+    public String getRemoteHostname()         { return getRemoteAddress().getHostName(); }
+    public String getVirtualHost()            { return vhost; }
+    public void setTimeout(int ms)            { try { s.setSoTimeout(ms); } catch (Exception e){throw new StreamException(e); }}
+    public void setTcpNoDelay(boolean delay)  { try { s.setTcpNoDelay(delay);}catch(Exception e){throw new StreamException(e); }}
+}
diff --git a/src/org/ibex/io/Stream.java b/src/org/ibex/io/Stream.java
new file mode 100644 (file)
index 0000000..a1bf021
--- /dev/null
@@ -0,0 +1,200 @@
+// Copyright 2004 Adam Megacz, see the COPYING file for licensing [LGPL]
+package org.ibex.io;
+
+import java.io.*;
+import java.net.*;
+import java.util.zip.*;
+import org.ibex.util.*;
+import org.ibex.net.*;
+
+/** plays the role of InputStream, OutputStream, Reader and Writer, with logging and unchecked exceptions */
+public class Stream {
+
+    protected final In in;
+    protected final Out out;
+    private         StringBuffer log = loggingEnabled ? new StringBuffer(16 * 1024) : null;
+    private         String newLine = "\r\n";
+
+    public static boolean loggingEnabled = System.getProperty("ibex.io.stream.logEnabled", "true") != null;
+
+    public  Stream(InputStream in)                   { this.in = new Stream.In(in); this.out = null; }
+    public  Stream(                OutputStream out) { this.in = null;              this.out = new Stream.Out(out); }
+    public  Stream(InputStream in, OutputStream out) { this.in = new Stream.In(in); this.out = new Stream.Out(out); }
+    public  Stream(String s)                         { this(new ByteArrayInputStream(s.getBytes())); }
+    public  Stream(Socket s) {
+        try { this.in = new Stream.In(s.getInputStream());    } catch (IOException e) { throw new StreamException(e); }
+        try { this.out = new Stream.Out(s.getOutputStream()); } catch (IOException e) { throw new StreamException(e); }
+    }
+
+    private static int ioe(Exception e) { throw new StreamException(e); }
+    public static class StreamException extends RuntimeException {
+        public StreamException(Exception e) { super(e); }
+        public StreamException(String s)    { super(s); }
+    }
+    public static class EOF             extends StreamException  { public EOF() { super("End of stream"); } }
+    public static class Closed          extends StreamException  { public Closed(String s) { super(s); } }
+
+
+    public char   peekc()                          { flush(); return in.getc(true); }
+    public char   getc()                           { flush(); char ret = in.getc(false); log(ret); return ret; }
+    public String readln()                         { flush(); String s = in.readln(); log(s); log('\n'); return s; }
+    public int    read(byte[] b, int off, int len) {
+        flush();
+        int ret = in.readBytes(b, off, len);
+        if (log != null) log("\n[read " + ret + " bytes of binary data ]\n");
+        nnl = false;
+        return ret;
+    }
+    public int    read(char[] c, int off, int len) {
+        flush();
+        int ret = in.read(c, off, len);
+        if (log != null && ret != -1) log(new String(c, off, ret));
+        return ret;
+    }
+
+    public void   unread(String s)                 { in.unread(s); }
+
+    public void   println()                        { println(""); }
+    public void   println(String s)                { logWrite(s); out.write(s); out.write(newLine); }
+
+    public void   flush()                          { if (out != null) try { out.w.flush(); } catch(IOException e) { ioe(e); } }
+    public void   close()                          { in.close(); out.close(); }
+    public void   setNewline(String s)             { newLine = s; }
+
+
+    /** dumps the connection log into a file */
+    public  String dumpLog()         { String ret = log.toString(); log = new StringBuffer(16 * 1024); return ret; }
+    private void  log(String s)      { if(log==null) return; if (!nnl) Log.note("\n[read ] "); Log.note(s + "\n"); nnl=false; }
+    private void  logWrite(String s) { if(log==null) return; if (nnl) Log.note("\n"); Log.note("[write] "+s+"\n"); nnl=false; }
+    private void  log(char c)        { if(log==null) return; if (c == '\r') return; if (!nnl) Log.note("[read ] "); Log.note(c+""); nnl = c != '\n'; }
+    private boolean nnl = false;
+
+    private static class Out extends BufferedOutputStream {
+        private Writer w = new BufferedWriter(new OutputStreamWriter(this));
+        public Out(OutputStream out) { super(out); }
+        public void close() { try { super.close(); } catch (Exception e) { Log.error(this, e); } }
+        public void write(String s) { try { w.write(s); } catch (IOException e) { ioe(e); } }
+    }
+
+    private static class In extends InputStream {
+        public Reader reader = new InputStreamReader(this);
+        private final InputStream orig;
+        public In(InputStream in) { orig = in; }
+
+        char[] cbuf = new char[8192];
+        int cstart = 0;
+        int cend = 0;
+
+        byte[] buf = new byte[8192];
+        int start = 0;
+        int end = 0;
+
+        boolean flushing = false;
+
+        public int available() { return flushing ? 0 : (end - start); }
+        public void close() { try { orig.close(); } catch (Exception e) { Log.error(this, e); } }
+
+        public char getc(boolean peek) { try {
+            if (cstart == cend) {
+                cstart = 0;
+                cend = reader.read(cbuf, 0, cbuf.length);
+                if (cend == -1) { cend = cstart; throw new EOF(); }
+            }
+            return peek ? cbuf[cstart] : cbuf[cstart++];
+        } catch (IOException e) { return (char)ioe(e); } }
+
+        public String readln() { try {
+            while(true) {
+                for(int i=cstart; i<cend; i++)
+                    if (cbuf[i] == '\n') {
+                        // this should (in theory) handle CR, LF,
+                        // CRLF, and LFCR properly, assuming that the
+                        // file consistently uses the same ending
+                        // throughout.
+                        int begin = cstart;
+                        int len = i-cstart;
+                        cstart = i+1;
+                        if (cbuf[begin] == '\r') { begin++; len--; }
+                        if (len > 0 && cbuf[begin+len-1] == '\r') { len--; }
+                        return new String(cbuf, begin, len);
+                    }
+                ensurec(256);
+                int numread = reader.read(cbuf, cend, cbuf.length - cend);
+                if (numread == -1) {
+                    if (cstart == cend) return null;
+                    String ret = new String(cbuf, cstart, cend-cstart);
+                    cstart = cend = 0;
+                    return ret;
+                }
+                cend += numread;
+            }
+        } catch (IOException e) { ioe(e); return null; } }
+
+        public int read(char[] c, int pos, int len) { try {
+            if (cstart == cend) {
+                cstart = 0;
+                cend = reader.read(cbuf, 0, cbuf.length);
+                if (cend == -1) { cend = cstart; return -1; }
+            }
+            if (len > cend - cstart) len = cend - cstart;
+            System.arraycopy(cbuf, cstart, c, pos, len);
+            cstart += len;
+            return len;
+        } catch (IOException e) { ioe(e); return -1; } }
+
+        public int readBytes(byte[] b, int pos, int len) { flushchars(); return read(b, pos, len); }
+        public int read() { byte[] b = new byte[1]; if (read(b, 0, 1) == -1) return -1; return (int)b[0]; } 
+        public int read(byte[] b, int pos, int len) { try {
+            if (start == end) {
+                start = 0;
+                end = orig.read(buf, 0, buf.length);
+                if (end == -1) { end = start; return -1; }
+            }
+            if (len > end - start) len = end - start;
+            System.arraycopy(buf, start, b, pos, len);
+            start += len;
+            return len;
+        } catch (IOException e) { ioe(e); return -1; } }
+
+        private void growc(int s){char[] cbuf2=new char[cbuf.length+s*2];System.arraycopy(cbuf,0,cbuf2,0,cbuf.length);cbuf=cbuf2; }
+        private void shiftc() {
+            char[] cbuf2 = new char[cbuf.length];
+            System.arraycopy(cbuf, cstart, cbuf2, 0, cend-cstart);
+            cend -= cstart;
+            cstart = 0;
+            cbuf = cbuf2;
+        }
+        private void ensurec(int space) { if (cend-cstart+space>cbuf.length) growc(space); if (cend+space>cbuf.length) shiftc(); }
+
+        private void growb(int s) { byte[] buf2 = new byte[buf.length+s*2]; System.arraycopy(buf,0,buf2,0,buf.length); buf=buf2; }
+        private void shiftb() { System.arraycopy(buf, start, buf, 0, end-start); end -= start; start = 0; }
+        private void ensureb(int space) { if (end-start+space>buf.length) growb(space); if (end+space>buf.length) shiftb(); }
+        private void ensureb2(int space) { if (end-start+space>buf.length) growb(space); if (start<space) unshiftb(); }
+        private void unshiftb() {
+            System.arraycopy(buf,start,buf,buf.length-(end-start),end-start);start=buf.length-(end-start);end=buf.length; }
+
+        public  void unread(String s) { ensurec(s.length()); s.getChars(0, s.length(), cbuf, cend); cend += s.length(); }
+
+        private void flushchars() {
+            try {
+                flushing = true;
+                for(; reader.ready(); reader.read(cbuf, cend++, 1)) ensurec(1);
+                unreader.write(cbuf, cstart, cend);
+                cstart = cend = 0;
+                unreader.flush();
+            } catch (IOException e) { ioe(e);
+            } finally { flushing = false; }
+        }
+
+        Writer unreader = new OutputStreamWriter(new OutputStream() {
+                public void close() { }
+                public void write(int i) throws IOException { byte[] b = new byte[1]; b[0] = (byte)i; write(b, 0, 1); }
+                public void write(byte[] b) throws IOException { write(b, 0, b.length); }
+                public void write(byte[] b, int p, int l) {
+                    ensureb2(l);
+                    System.arraycopy(b, p, buf, start-l, l);
+                    start -= l;
+                }
+            });
+    }
+}
diff --git a/src/org/ibex/jinetd/Host.java b/src/org/ibex/jinetd/Host.java
new file mode 100644 (file)
index 0000000..ade17d3
--- /dev/null
@@ -0,0 +1,26 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+public class Host extends Loader {
+
+    final String hostname;
+    public Host(String path, String hostname) { super(path); this.hostname = hostname; }
+
+    public void changed(Watched w) {
+        super.changed(w);
+        Log.error(this, "changed(" + w + ")");
+        try {
+            ClassLoader cl = getClassLoader();
+            if (cl == null) return;
+            Class c = cl.loadClass("Main");
+            if (c == null) return;
+            Method m = c.getMethod("main", new Class[] { });
+            m.invoke(null, new Object[] { });
+        } catch (Exception e) {
+            Log.warn(this, "nope");
+        }
+    }
+}
diff --git a/src/org/ibex/jinetd/Listener.java b/src/org/ibex/jinetd/Listener.java
new file mode 100644 (file)
index 0000000..dfc5136
--- /dev/null
@@ -0,0 +1,15 @@
+package org.ibex.jinetd;
+import java.net.*;
+import org.ibex.net.*;
+import org.ibex.io.*;
+
+/**
+ *  jinetd will scan /jinetd/port/<num>/*.jar for a
+ *  class implementing this interface; instances must
+ *  have public constructors
+ */
+public interface Listener {
+    
+    public void accept(Connection c);
+
+}
diff --git a/src/org/ibex/jinetd/Loader.java b/src/org/ibex/jinetd/Loader.java
new file mode 100644 (file)
index 0000000..a94365e
--- /dev/null
@@ -0,0 +1,186 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+
+/** represents a file or directory which is scanned for updates */
+public class Loader extends Watcher {
+
+    public Loader(String path) { super(path); }
+    
+    private TreeClassLoader classloader = new TreeClassLoader();
+    public ClassLoader getClassLoader() {
+        ClassLoader classloader = this.classloader;
+        if (classloader == null) {
+            classloader = this.classloader = new TreeClassLoader();
+            Log.warn(this, "getting classloader...");
+            try {
+                compileSource();
+            } catch (Exception e) {
+                Log.error(this, e);
+            }
+        }
+        return classloader;
+    }
+
+    private void fill(Vec vec, File dir) {
+        if (!dir.exists()) return;
+        if (!dir.isDirectory()) {
+            if (!dir.getPath().endsWith(".java")) return;
+            vec.addElement(dir.getAbsolutePath());
+        } else {
+            String[] list = dir.list();
+            for(int i=0; i<list.length; i++)
+                fill(vec, new File(dir.getAbsolutePath() + File.separatorChar + list[i]));
+        }
+    }
+    private void compileSource() throws Exception {
+        File srcdir = new File(this.path + File.separatorChar + "SRC");
+        if (!srcdir.exists()) return;
+        if (new File("/usr/bin/jikes").exists()) {
+            File bindir = new File(this.path + File.separatorChar + "BIN");  bindir.mkdirs();
+            File libdir = new File(this.path + File.separatorChar + "LIB");
+            String classpath = System.getProperty("java.class.path");
+            String [] l = new File("/jinetd/LIB/").list();
+            for(int i=0; i<l.length; i++) {
+                if (!l[i].endsWith(".jar")) continue;
+                classpath += File.pathSeparatorChar;
+                classpath += "/jinetd/LIB/" + l[i];
+            }
+            String bootclasspath = System.getProperty("sun.boot.class.path", "");
+            Vec args = new Vec();
+            args.addElement("/usr/bin/jikes");
+            args.addElement("+E");
+            args.addElement("-nowarn");
+            args.addElement("-bootclasspath");
+            args.addElement(bootclasspath);
+            args.addElement("-extdirs");
+            args.addElement(libdir.getAbsolutePath());
+            args.addElement("-classpath");
+            args.addElement(classpath);
+            args.addElement("-sourcepath");
+            args.addElement(srcdir.getAbsolutePath());
+            args.addElement("-d");
+            args.addElement(bindir.getAbsolutePath());
+            fill(args, srcdir);
+            String[] all = new String[args.size()];
+            args.copyInto(all);
+            Log.info(this, "invoking jikes");
+            for(int i=0; i<all.length; i++) Log.info(this, "   " + all[i]);
+            final Process jikes = Runtime.getRuntime().exec(all);
+            final BufferedReader out = new BufferedReader(new InputStreamReader(jikes.getInputStream()));
+            final BufferedReader err = new BufferedReader(new InputStreamReader(jikes.getErrorStream()));
+            new Thread() { public void run() {
+                try { for(String s = out.readLine(); s != null; s = out.readLine()) Log.info("jikes[stdout]", s); }
+                catch (Exception e) { Log.warn("jikes", e); } } }.start();
+            new Thread() { public void run() {
+                try { for(String s = err.readLine(); s != null; s = err.readLine()) Log.info("jikes[stderr]", s); }
+                catch (Exception e) { Log.warn("jikes", e); } } }.start();
+            jikes.waitFor();
+        } else {
+            Log.error(this, "ACK! jikes not found, javac not (yet) supported");
+        }
+    }
+
+    // only watch SRC and LIB for changes
+    public Watched slash(String path) {
+        return (path.equals("LIB") ||
+                path.equals("BIN") ||
+                path.equals("SRC") ||
+                path.endsWith(".jar") ) ? super.slash(path) : null; }
+
+
+    // dump the classloader if anything changes
+    public void changed(Watched w) {
+        if (w.path.indexOf("BIN") != -1) return;
+        if (classloader != null) {
+            Log.info(this, "Reloading all classes: " + path);
+            classloader = null;
+        }
+    }
+    
+    private class TreeClassLoader extends ClassLoader {
+        private Hashtable cache = new Hashtable();
+
+        private synchronized Class defineClass(String name) {
+            // first see if it's just sitting there
+            File classFile = slash("BIN").slash(name.replace('.', File.separatorChar) + ".class");
+            if (classFile.exists()) {
+                try {
+                    byte[] b = InputStreamToByteArray.convert(new FileInputStream(classFile));
+                    Log.debug(this, "  loading " + name + " from " + classFile.getAbsolutePath());
+                    return defineClass(b, 0, b.length);
+                } catch (Exception e) {
+                    Log.warn(this, e);
+                }
+            }
+
+            // then scan the jarfiles for it
+            File lib = slash("LIB");
+            if (lib.exists() && lib.isDirectory()) {
+                try {
+                    String[] paths = lib.list();
+                    for(int i=0; i<paths.length; i++) {
+                        if (paths[i].endsWith(".jar")) {
+                            File f = new File(getAbsolutePath()+File.separatorChar+"LIB"+File.separatorChar+paths[i]);
+                            //Log.debug(this, "  scanning " + f.getAbsolutePath());
+                            ZipFile zf = new ZipFile(f);
+                            ZipEntry ze = zf.getEntry(name.replace('.', File.separatorChar) + ".class");
+                            if (ze != null) {
+                                byte[] b = InputStreamToByteArray.convert(zf.getInputStream(ze));
+                                Log.debug(this, "    loading " + name + " from " + f.getAbsolutePath());
+                                return defineClass(b, 0, b.length);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    Log.warn(this, e);
+                }
+
+            // finally scan ourselves
+                try {
+                    String[] paths = list();
+                    for(int i=0; i<paths.length; i++) {
+                        if (paths[i].endsWith(".jar")) {
+                            File f = new File(getAbsolutePath()+File.separatorChar+paths[i]);
+                            //Log.debug(this, "  scanning " + f.getAbsolutePath());
+                            ZipFile zf = new ZipFile(f);
+                            ZipEntry ze = zf.getEntry(name.replace('.', File.separatorChar) + ".class");
+                            if (ze != null) {
+                                byte[] b = InputStreamToByteArray.convert(zf.getInputStream(ze));
+                                Log.debug(this, "    loading " + name + " from " + f.getAbsolutePath());
+                                return defineClass(b, 0, b.length);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    Log.warn(this, e);
+                }
+
+            }
+                
+            // finally, resort to compiling it if we have to
+            //File src = new File(getAbsolutePath() + File.separatorChar + "SRC");
+            // FIXME
+            //if (!sourcebuilt) buildSource();
+            return null;
+        }
+
+        public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
+            try {
+                Class c = findSystemClass(name);
+                if (c != null) { if (resolve) resolveClass(c); return c; }
+            } catch (ClassNotFoundException e) { /* DELIBERATE */ }
+            Class c = (Class)cache.get(name);
+            if (c == null) {
+                //Log.info(this, "looking for class " + name);
+                c = defineClass(name);
+                if (c == null) throw new ClassNotFoundException();
+                cache.put(name, c);
+            }
+            if (resolve) resolveClass(c);
+            return c;
+        }
+    }
+}
diff --git a/src/org/ibex/jinetd/Main.java b/src/org/ibex/jinetd/Main.java
new file mode 100644 (file)
index 0000000..e32a32c
--- /dev/null
@@ -0,0 +1,18 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+
+public class Main {
+
+    public static Root root = new Root(System.getProperty("jinetd.root", "/jinetd"));
+    public static void main(String[] s) throws Exception {
+        Log.color = true;
+        while(true) try {
+            Thread.sleep(1000);
+            //Log.info(Main.class, "scanning...");
+            if (root != null) root.scan();
+        } catch (Exception e) { Log.error(Main.class, e); }
+    }
+
+}
diff --git a/src/org/ibex/jinetd/Port.java b/src/org/ibex/jinetd/Port.java
new file mode 100644 (file)
index 0000000..840cec2
--- /dev/null
@@ -0,0 +1,97 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import org.ibex.io.*;
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import java.util.zip.*;
+
+public class Port extends Loader {
+
+    private final NetworkInterface iface;
+    private final int port;
+    private final Thread listener;
+
+    public Port(String path, NetworkInterface iface, int port) {
+        super(path);
+        this.iface = iface;
+        this.port = port;
+        this.listener = new PortThread();
+        listener.start();
+    }
+
+    public void changed(Watched w) {
+        //Log.warn(this, "Port: noticed change in " + w);
+        super.changed(w);
+    }
+
+    void dispatch(final Connection conn) throws Exception {
+        getClassLoader();
+        String[] list = list();
+        for(int i=0; i<list.length; i++) {
+            if (!list[i].endsWith(".jar")) continue;
+            //Log.warn(this, "checking " + (this.path + File.separatorChar + list[i]));
+            File f = new File(this.path + File.separatorChar + list[i]);
+            ZipInputStream zis = new ZipInputStream(new FileInputStream(f));
+            for(ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
+                String name = ze.getName();
+                if (name.endsWith(".class"))
+                    dispatch(conn, name.substring(0, name.length() - ".class".length()).replace('/', '.'));
+            }
+        }
+        check(conn, new File(getAbsolutePath() + File.separatorChar + "BIN"));
+    }
+
+    void check(Connection conn, File f) throws Exception {
+        //Log.warn(this, "check(" + f.getAbsolutePath() + ")");
+        if (!f.exists()) return;
+        if (!f.isDirectory()) {
+            if (!f.getAbsolutePath().endsWith(".class")) return;
+            String name = f.getAbsolutePath().substring(getAbsolutePath().length() + 5);
+            name = name.substring(0, name.length() - ".class".length()).replace(File.separatorChar, '.');
+            dispatch(conn, name);
+        } else {
+            String[] list = f.list();
+            for(int i=0; i<list.length; i++) check(conn, new File(f.getAbsolutePath() + File.separatorChar + list[i]));
+        }
+    }
+    void dispatch(final Connection conn, String name) throws Exception {
+        //Log.info(this, "attempting class " + name);
+        try {
+            Class c = getClassLoader().loadClass(name);
+            if (c != null) {
+                if (Listener.class.isAssignableFrom(c)) {
+                    Log.error(this, "dispatching connection on port " + port + " to " +
+                              c.getName());
+                    final Listener l = (Listener)c.newInstance();
+                    new Thread() { public void run() {
+                        Log.clearnotes();
+                        try {
+                            l.accept(conn);
+                        } catch (Exception e) {
+                            Log.error(l.getClass(), "Listener threw exception");
+                            Log.error(l.getClass(), e);
+                        }
+                    } }.start();
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            Log.error(this, e);
+        }
+    }
+
+    private class PortThread extends Thread {
+        public void run() {
+            try {
+                Log.warn(this, "Now listening on interface " + iface + ", port " + port);
+                ServerSocket ss = new ServerSocket(port);
+                for(Socket s = ss.accept(); ; s = ss.accept()) try {
+                    Log.warn(this, "accepted connection on port " + port);
+                    dispatch(new Connection(s, "megacz.com"));
+                    Log.warn(this, "done searching for service on port " + port);
+                } catch (Exception e) { Log.warn(Port.class, e); }
+            } catch (Exception e) { Log.error(Port.class, e); }
+        }
+    }
+}
diff --git a/src/org/ibex/jinetd/Root.java b/src/org/ibex/jinetd/Root.java
new file mode 100644 (file)
index 0000000..7454773
--- /dev/null
@@ -0,0 +1,58 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+import java.net.*;
+
+public class Root extends Loader {
+
+    private final Host host;
+    private final Watched port;
+
+    public Root(String path) {
+        super(path);
+        host = new Host(path + File.separatorChar + "host", null);
+        port = new PortDir(path + File.separatorChar + "port");
+    }
+
+    public Watched slash(String part) {
+        if (part.equals("host")) return host;
+        if (part.equals("port")) return port;
+        if (part.equals("LIB")) return super.slash(part);
+        return null;
+    }
+
+    public void changed(Watched w) {
+        if (w.part.equals("host")) {
+            Log.warn(this, "/host changed");
+        } else if (w.part.equals("port")) {
+            Log.warn(this, "/port changed");
+        } else if (w.getAbsolutePath().startsWith("/jinetd/LIB/")) {
+            if (w.lastModifiedAtLastScan != -1) {
+                Log.error(this, "jinetd upgraded; bouncing the JVM....");
+                Log.flush();
+                System.exit(0);
+            }
+        } else {
+            Log.info(this, "unknown directory " + w.part + " changed");
+        }
+    }
+
+    private static class PortDir extends Watched {
+        public PortDir(String path) { super(path); }
+        public Watched slash(String part) {
+            String ipaddr  = part.indexOf(':') == -1 ? null : part.substring(0, part.indexOf(':'));
+            String portnum = part.indexOf(':') == -1 ? part : part.substring(part.indexOf(':') + 1);
+            try {
+                return new Port(this.path + File.separatorChar + part,
+                                ipaddr == null ? null : NetworkInterface.getByInetAddress(InetAddress.getByName(ipaddr)),
+                                portnum.equals("*") ? 0 : Integer.parseInt(portnum));
+            } catch (UnknownHostException e) {  Log.warn(this, "can't resolve host for port directory: " + part);
+            } catch (NumberFormatException e) { Log.warn(this, "invalid port directory: " + part);
+            } catch (Exception e) {             Log.warn(this, "error instantiating Port: " + part);
+            }
+            return null;
+        }
+    }
+
+}
diff --git a/src/org/ibex/jinetd/Watched.java b/src/org/ibex/jinetd/Watched.java
new file mode 100644 (file)
index 0000000..ce3267b
--- /dev/null
@@ -0,0 +1,53 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+
+public class Watched extends File {
+
+    // Instance //////////////////////////////////////////////////////////////////////////////
+    
+    private Hashtable cache = new Hashtable();
+    long lastModifiedAtLastScan = -1;
+    public final String path;
+    public final String part;
+
+    public Watcher watcher() { return ((Watched)all.get(getParent())).watcher(); }
+    public Watched slash(String part) { return get(this.path + File.separatorChar + part); }
+    public void scan() throws IOException {
+        if (!exists())                                { return; }
+        if (lastModifiedAtLastScan != lastModified()) { watcher().changed(this); lastModifiedAtLastScan = lastModified(); }
+        if (!isDirectory())                           { return; }
+        Vec removals = new Vec();
+        for(Iterator i = cache.values().iterator(); i.hasNext();) {
+            Watched w = ((Watched)i.next());
+            if (w.exists()) w.scan();
+            else { watcher().changed(w); removals.addElement(w.path.substring(this.path.length() + 1)); }
+        }
+        for(int i=0; i<removals.size(); i++) cache.remove(removals.elementAt(i));
+        String[] kids = list();        
+        for(int i=0; i<kids.length; i++) {
+            if (cache.get(kids[i]) != null) continue;
+            Watched kid = slash(kids[i]);
+            if (kid == null) continue;
+            cache.put(kids[i], kid);
+            watcher().changed(kid);
+        }
+    }
+
+
+    // Pooling //////////////////////////////////////////////////////////////////////////////
+
+    private static WeakHashMap all = new WeakHashMap();
+    protected Watched(String path) {
+        super(path);
+        this.path = path;
+        this.part = path.substring(path.lastIndexOf(File.separatorChar) + 1);
+        all.put(path, this);
+    }
+    private static Watched get(String path) {
+        Watched ret = (Watched)all.get(path);
+        if (ret == null) ret = new Watched(path);
+        return ret;
+    }
+}
diff --git a/src/org/ibex/jinetd/Watcher.java b/src/org/ibex/jinetd/Watcher.java
new file mode 100644 (file)
index 0000000..86b1958
--- /dev/null
@@ -0,0 +1,10 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import java.io.*;
+import java.util.*;
+
+public abstract class Watcher extends Watched {
+    protected Watcher(String path) { super(path); }
+    public abstract void changed(Watched w);
+    public Watcher watcher() { return this; }
+}
diff --git a/src/org/ibex/jinetd/Worker.java b/src/org/ibex/jinetd/Worker.java
new file mode 100644 (file)
index 0000000..a43cb6e
--- /dev/null
@@ -0,0 +1,15 @@
+package org.ibex.jinetd;
+import org.ibex.util.*;
+import org.ibex.io.*;
+import org.ibex.net.*;
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
+public interface Worker {
+    public void handleRequest(Connection c);
+}
+
+
+
+
index ebf349b..0cbc494 100644 (file)
@@ -26,7 +26,17 @@ public class Address extends JSReflection {
         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 RuntimeException { public Malformed(String s) { super(s); } }
+    public String coerceToString() { return toString(); }
+    public static class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } }
+
+    public boolean isLocal() {
+        InetAddress[] mx = SMTP.getMailExchangerIPs(host);
+        for(int i=0; i<mx.length; i++) {
+            try { if (NetworkInterface.getByInetAddress(mx[i]) != null) { return true; } }
+            catch (Exception e) { /* DELIBERATE */ }
+        }
+        Log.warn(this, "returning false");
+        return false;
+    }
 }
index f607f5f..bafbebb 100644 (file)
@@ -2,53 +2,24 @@ package org.ibex.mail;
 import org.ibex.mail.target.*;
 import org.ibex.mail.protocol.*;
 import org.ibex.util.*;
+import org.ibex.jinetd.*;
+import org.ibex.io.*;
 import java.io.*;
 import java.net.*;
 import java.util.*;
 
-public class Main {
+public class Main implements Listener {
 
-    /*
-    public static class Resin {
-        new extends com.caucho.server.port.Protocol() {
-            public String getProtocolName() { return "imap"; }
-            public com.caucho.server.port.ServerRequest createRequest(com.caucho.server.connection.Connection conn) {
-                try {
-                    return new Listener(conn);
-                } catch (Exception e) {
-                    Log.error(this, e);
-                    return null;
-                }
-            }
+    public void accept(Connection conn) {
+        Log.error(this, "connection port is " + conn.getLocalPort());
+        if (conn.getLocalPort() == 25) {
+            new SMTP.Server().handleRequest(conn);
+        } else if (conn.getLocalPort() == 143) {
+            new IMAP.Listener().handleRequest(conn);
         }
-
-    public SMTP() { }
-    public String getProtocolName() { return "smtp"; }
-    public com.caucho.server.port.ServerRequest createRequest(com.caucho.server.connection.Connection conn) {
-       try {
-           return new Server(conn);
-       } catch (Exception e) {
-           Log.error(this, e);
-           return null;
-       }
-    }
-
+        conn.close();
     }
 
-    public static void main(String[] s) throws Exception {
-
-        // set up logging
-        String logto = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
-        logto += File.separatorChar + "log";
-
-        //Log.file(logto);
-       Log.color = true;
-
-        new IMAPThread().start();
-        new SMTPThread().start();
-
-    }
-    */
     public static class BogusAuthenticator implements IMAP.Server.Authenticator {
         final Mailbox root =
            FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT, true).slash("user", true).slash("megacz", true);
@@ -57,56 +28,5 @@ public class Main {
             return null;
         }
     }
-    /*
-    private static class IMAPThread extends Thread {
-        final int port = Integer.parseInt(System.getProperty("ibex.mail.imap.port", "143"));
-        public void run() {
-            try {
-                Log.info(this, "binding to port " + port + "...");
-                ServerSocket ss = new ServerSocket(port);
-                Log.info(this, "listening for connections...");
-                for(;;) {
-                    final Socket sock = ss.accept();
-                    new Thread() {
-                        public void run() {
-                            try {
-                                new IMAP.Listener(sock, "megacz.com", new BogusAuthenticator()).handle();
-                            } catch (Exception e) {
-                                Log.warn(this, e);
-                            }
-                        }
-                    }.start();
-                }
-            } catch (Exception e) {
-                Log.warn(this, e);
-            }
-        }
-    }
 
-    private static class SMTPThread extends Thread {
-        final int port = Integer.parseInt(System.getProperty("ibex.mail.smtp.port", "25"));
-        public void run() {
-            try {
-                Log.info(this, "binding to port " + port + "...");
-                ServerSocket ss = new ServerSocket(port);
-                Log.info(this, "listening for connections...");
-                while(true) {
-                    final Socket sock = ss.accept();
-                    final SMTP.Server smtp = new SMTP.Server(sock, "megacz.com");
-                    new Thread() {
-                        public void run() {
-                            try {
-                                smtp.handle();
-                            } catch (Exception e) {
-                                Log.warn(this, e);
-                            }
-                        }
-                    }.start();
-                }
-            } catch (Exception e) {
-                Log.warn(this, e);
-            }
-        }
-    }
-    */
 }
index bb9c69c..536944e 100644 (file)
@@ -52,28 +52,29 @@ public class Message extends org.ibex.js.JSReflection {
     
     public int size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); }
     public String toString() { return allHeaders + "\r\n" + body; }
+
     public void dump(Stream s) {
-        s.out.setNewline("\r\n");
-        s.out.println("X-org.ibex.mail.headers.envelope.From: " + envelopeFrom);
-        s.out.println("X-org.ibex.mail.headers.envelope.To: " + envelopeTo);
-        s.out.println(allHeaders);
-        s.out.println();
-        s.out.println(body);
-        s.out.flush();
+        s.setNewline("\r\n");
+        s.println("X-org.ibex.mail.headers.envelope.From: " + envelopeFrom);
+        s.println("X-org.ibex.mail.headers.envelope.To: " + envelopeTo);
+        s.println(allHeaders);
+        s.println();
+        s.println(body);
+        s.flush();
     }
 
     public class Trace {
         final String returnPath;
         final Element[] elements;
         public Trace(Stream stream) throws Trace.Malformed {
-            String retPath = stream.in.readln();
+            String retPath = stream.readln();
             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 = stream.in.readln();
+                String s = stream.readln();
                 if (s == null) break;
-                if (!s.startsWith("Received:")) { stream.in.unread(s); break; }
+                if (!s.startsWith("Received:")) { stream.unread(s); break; }
                 s = s.substring(9).trim();
                 el.addElement(new Element(s));
             }
@@ -100,11 +101,11 @@ public class Message extends org.ibex.js.JSReflection {
         public class Malformed extends Message.Malformed { public Malformed(String s) { super(s); } }
     }
 
-    public static class Malformed extends RuntimeException { public Malformed(String s) { super(s); } }
+    public static class Malformed extends Exception { public Malformed(String s) { super(s); } }
 
-    public Message(Address from, Address to, String s, Date arrival) { this(from, to, new Stream(s), arrival); }
-    public Message(Address from, Address to, Stream in) { this(from, to, in, new Date()); }
-    public Message(Address envelopeFrom, Address envelopeTo, Stream stream, Date arrival) {
+    public Message(Address from, Address to, String s, Date arrival) throws Malformed {this(from,to,new Stream(s), arrival); }
+    public Message(Address from, Address to, Stream in) throws Malformed { this(from, to, in, new Date()); }
+    public Message(Address envelopeFrom, Address envelopeTo, Stream stream, Date arrival) throws Malformed {
         this.arrival = arrival;
         this.headers = new CaseInsensitiveHash();
         Vec envelopeToHeader = new Vec();
@@ -115,7 +116,7 @@ public class Message extends org.ibex.js.JSReflection {
         String subject = null, messageid = null;
         Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec();
         int lines = 0;
-        for(String s = stream.in.readln(); s != null && !s.equals(""); s = stream.in.readln()) {
+        for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) {
             all.append(s);
             lines++;
             all.append("\r\n");
@@ -135,7 +136,7 @@ public class Message extends org.ibex.js.JSReflection {
                 if (resent.size() == 0 || key.startsWith("Resent-From")) resent.addElement(new Hashtable());
                 ((Hashtable)resent.lastElement()).put(key.substring(7), val);
             } else if (key.startsWith("Return-Path")) {
-                stream.in.unread(s); traces.addElement(new Trace(stream));
+                stream.unread(s); traces.addElement(new Trace(stream));
             } else if (key.equals("X-org.ibex.mail.headers.envelope.From")) {
                 if (envelopeFrom == null) envelopeFrom = new Address(val);
             } else if (key.equals("X-org.ibex.mail.headers.envelope.To")) {
@@ -148,27 +149,27 @@ public class Message extends org.ibex.js.JSReflection {
         }
         
         // FIXME what if all are null?
-        this.to           = headers.get("To") == null   ? envelopeTo    : new Address((String)headers.get("To"));
-        this.from         = headers.get("From") == null ? envelopeFrom  : new Address((String)headers.get("From"));
+        this.to           = headers.get("To") == null   ? envelopeTo    : Address.parse((String)headers.get("To"));
+        this.from         = headers.get("From") == null ? envelopeFrom  : Address.parse((String)headers.get("From"));
         this.envelopeFrom = envelopeFrom == null        ? this.from     : envelopeFrom;
         this.envelopeTo   = envelopeTo == null          ? this.to       : envelopeTo;
         
         this.date      = new Date(); // FIXME (Date)headers.get("Date");
-        this.replyto   = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To"));
+        this.replyto   = headers.get("Reply-To") == null ? null : Address.parse((String)headers.get("Reply-To"));
         this.subject   = (String)headers.get("Subject");
         this.messageid = (String)headers.get("Message-Id");
         if (headers.get("Cc") != null) {
             // FIXME: tokenize better
             StringTokenizer st = new StringTokenizer((String)headers.get("Cc"), ",");
             this.cc = new Address[st.countTokens()];
-            for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(st.nextToken());
+            for(int i=0; i<this.cc.length; i++) this.cc[i] = Address.parse(st.nextToken());
         } else {
             this.cc = new Address[0];
         }
         if (headers.get("Bcc") != null) {
             StringTokenizer st = new StringTokenizer((String)headers.get("Bcc"));
             this.bcc = new Address[st.countTokens()];
-            for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
+            for(int i=0; i<this.bcc.length; i++) this.bcc[i] = Address.parse(st.nextToken());
         } else {
             this.bcc = new Address[0];
         }
@@ -176,7 +177,7 @@ public class Message extends org.ibex.js.JSReflection {
         traces.copyInto(this.traces = new Trace[traces.size()]);
         allHeaders = all.toString();
         StringBuffer body = new StringBuffer();
-        for(String s = stream.in.readln();; s = stream.in.readln()) {
+        for(String s = stream.readln();; s = stream.readln()) {
             if (s == null) break;
             lines++;
             body.append(s);       // FIXME: we're assuming all mail messages fit in memory
index b9c4363..346861c 100644 (file)
@@ -5,7 +5,5 @@ import org.ibex.js.*;
 /** base class for mail message "destinations" */
 public class Target extends JS {
     public static final Target root = Script.root();
-    public void accept(Message m) throws IOException, MailException {
-        throw new MailException("Target.accept() unimplemented");
-    }
+    public void accept(Message m) throws IOException, MailException { throw new MailException("Target.accept() unimplemented"); }
 }
index 2291826..346f007 100644 (file)
@@ -1,6 +1,7 @@
 package org.ibex.mail.protocol;
 import org.ibex.io.*;
 import org.ibex.jinetd.Listener;
+import org.ibex.jinetd.Worker;
 import org.ibex.net.*;
 import org.ibex.mail.*;
 import org.ibex.util.*;
@@ -32,13 +33,7 @@ import java.io.*;
 // FEATURE: STARTTLS
 // FEATURE: asynchronous client notifications (need to re-read RFC)
 
-public class IMAP implements Listener {
-
-    public void accept(Connection c) {
-        Log.error(this, "IMAP got a connection!");
-        new Listener().handleRequest(c);
-        c.close();
-    }
+public class IMAP {
 
     public IMAP() { }
     public static final float version = (float)0.1;
@@ -163,7 +158,9 @@ public class IMAP implements Listener {
         public void delete(String m0) { delete(mailbox(m0,false)); }
         public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(false); else throw new Bad("can't delete inbox"); }
         public void create(String m) { mailbox(m, true); }
-        public void append(String m,int f,Date a,String b){mailbox(m,false).add(new Message(null,null,b,a),f|Mailbox.Flag.RECENT);}
+        public void append(String m,int f,Date a,String b) { try {
+            mailbox(m,false).add(new Message(null,null,b,a),f|Mailbox.Flag.RECENT);
+        } catch (Message.Malformed e) { throw new No(e.getMessage()); } }
         public void check() { }
         public void noop() { }
         public void logout() { }
@@ -213,7 +210,10 @@ public class IMAP implements Listener {
         }
         public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) {
             for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) {
-                client.fetch(it.num(), it.flags(), it.cur().size(), it.cur(), it.uid());
+                Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
+                                            RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null;
+                int size = message == null ? 0 : message.size();
+                client.fetch(it.num(), it.flags(), size, message, it.uid());
                 it.recent(false);
             }
         }
@@ -235,13 +235,13 @@ public class IMAP implements Listener {
         void newline() { parser.newline(); }
         Query query() { return parser.query(); }
         public void handleRequest(Connection conn) {
-            parser = new Parser(conn);
             this.conn = conn;
+            parser = new Parser(conn);
             api = new IMAP.MailboxWrapper(new Main.BogusAuthenticator(), this);
             conn.setTimeout(30 * 60 * 1000);
             println("* OK " + conn.vhost + " " + IMAP.class.getName() + " IMAP4rev1 [RFC3501] v" + version + " server ready");
             for(String tag = null;; newline()) try {
-                conn.out.flush();
+                conn.flush();
                 boolean uid = false;
                 tag = null; Parser.Token tok = token(); if (tok == null) return; tag = tok.astring();
                 String command = token().atom();
@@ -321,8 +321,8 @@ public class IMAP implements Listener {
                 }
                 println(tag+" OK "+command+" Completed. " +
                         (commandKey == LOGIN ? ("[CAPABILITY "+Printer.join(" ", api.capability())+"]") : ""));
-            } catch (Server.Bad b) { println(tag==null ? "* BAD Invalid tag":(tag + " Bad " + b.toString())); b.printStackTrace();
-            } catch (Server.No n)  { println(tag==null?"* BAD Invalid tag":(tag+" No "  + n.toString())); n.printStackTrace(); }
+            } catch (Server.Bad b) { println(tag==null ? "* BAD Invalid tag":(tag + " Bad " + b.toString())); Log.warn(this,b);
+            } catch (Server.No n)  { println(tag==null?"* BAD Invalid tag":(tag+" No "  + n.toString())); Log.warn(this,n); }
         }
 
         private Parser.Token[] lastfetch = null; // hack
@@ -348,7 +348,7 @@ public class IMAP implements Listener {
         private void fetch(Object o, Parser.Token[] t, int num, int flags, int size, boolean uid, int muid) {
             Query q   = o instanceof Query ? (Query)o : null;
             Message m = o instanceof Message ? (Message)o : null;
-            boolean e = m != null;
+            boolean e = q == null;
 
             lastfetch = t;
             int spec = 0;                              // spec; see constants for flags
@@ -487,11 +487,9 @@ public class IMAP implements Listener {
         private static final int SEARCH = 25;      static { commands.put("SEARCH", new Integer(SEARCH)); }
     }
 
-    public static class Parser extends Stream.In {
+    public static class Parser {
         private Stream stream;
-        public Parser(Stream from) { super(from.in); this.stream = from; }
-        public char peekc() { int i = read(); unread(((char)i)+""); return (char)i; }
-        public char getc() { return (char)read(); }
+        public Parser(Stream from) { this.stream = from; }
         public Token token(String s) { return new Token(s); }
         protected Query query() {
             String s = null;
@@ -645,9 +643,9 @@ public class IMAP implements Listener {
         }
 
         public void newline() {
-           while (peekc() == '\r' || peekc() == '\n' || peekc() == ' ') {
-               for(char c = peekc(); c == ' ';) { getc(); c = peekc(); };
-               for(char c = peekc(); c == '\r' || c == '\n';) { getc(); c = peekc(); };
+           while (stream.peekc() == '\r' || stream.peekc() == '\n' || stream.peekc() == ' ') {
+               for(char c = stream.peekc(); c == ' ';) { stream.getc(); c = stream.peekc(); };
+               for(char c = stream.peekc(); c == '\r' || c == '\n';) { stream.getc(); c = stream.peekc(); };
            }
         }
 
@@ -655,21 +653,21 @@ public class IMAP implements Listener {
         public Token token(boolean freak) {
             Vec toks = new Vec();
             StringBuffer sb = new StringBuffer();
-            char c = getc(); while (c == ' ') c = getc();
+            char c = stream.getc(); while (c == ' ') c = stream.getc();
             if (c == '\r' || c == '\n') { if (freak) bad("unexpected end of line"); return null; }
             else if (c == '{') {
-                while(peekc() != '}') sb.append(getc());
-                stream.out.println("+ Ready when you are...");
+                while(stream.peekc() != '}') sb.append(stream.getc());
+                stream.println("+ Ready when you are...");
                 int octets = Integer.parseInt(sb.toString());
-                while(peekc() == ' ') getc();   // whitespace
-                while (getc() != '\n' && getc() != '\r') { }
+                while(stream.peekc() == ' ') stream.getc();   // whitespace
+                while (stream.getc() != '\n' && stream.getc() != '\r') { }
                 byte[] bytes = new byte[octets];
-                read(bytes, 0, bytes.length);
+                stream.read(bytes, 0, bytes.length);
                 return new Token(new String(bytes), true);
             } else if (c == '\"') {
                 while(true) {
-                    c = getc();
-                    if (c == '\\') sb.append(getc());
+                    c = stream.getc();
+                    if (c == '\\') sb.append(stream.getc());
                     else if (c == '\"') break;
                     else sb.append(c);
                 }
@@ -686,11 +684,11 @@ public class IMAP implements Listener {
                 
             } else while(true) {
                 sb.append(c);
-                c = peekc();
+                c = stream.peekc();
                 if (c == ' ' || c == '\"' || c == '(' || c == ')' || c == '[' || c == ']' ||
                     c == '{' || c == '\n' || c == '\r')
                     return new Token(sb.toString(), false);
-                getc();
+                stream.getc();
             }
         }
     }
index d59b43c..0509c28 100644 (file)
@@ -1,6 +1,7 @@
 package org.ibex.mail.protocol;
 import org.ibex.mail.*;
 import org.ibex.mail.target.*;
+import org.ibex.jinetd.Worker;
 import org.ibex.util.*;
 import org.ibex.io.*;
 import org.ibex.net.*;
@@ -24,65 +25,62 @@ public class SMTP {
     public static class Server implements Worker {
         public void handleRequest(Connection conn) {
             conn.setTimeout(5 * 60 * 1000);
-            conn.out.println("220 " + conn.vhost + " SMTP " + this.getClass().getName());
+            conn.println("220 " + conn.vhost + " SMTP " + this.getClass().getName());
             Address from = null;
             Vector to = new Vector();
-            for(String command = conn.in.readln(); ; command = conn.in.readln()) {
+            for(String command = conn.readln(); ; command = conn.readln()) try {
                 String c = command.toUpperCase();
-                if (c.startsWith("HELO"))        { conn.out.println("250 HELO " + conn.vhost); from = null; to = new Vector();
-                } else if (c.startsWith("EHLO")) { conn.out.println("250");                    from = null; to = new Vector();
-                } else if (c.startsWith("RSET")) { conn.out.println("250 reset ok");           from = null; to = new Vector();
-                } else if (c.startsWith("HELP")) { conn.out.println("214 you are beyond help.  see a trained professional.");
-                } else if (c.startsWith("VRFY")) { conn.out.println("252 We don't VRFY; proceed anyway");
-                } else if (c.startsWith("EXPN")) { conn.out.println("550 EXPN not available");
-                } else if (c.startsWith("NOOP")) { conn.out.println("250 OK");
-                } else if (c.startsWith("QUIT")) { conn.out.println("221 " + conn.vhost + " closing connection"); return;
+                if (c.startsWith("HELO"))        { conn.println("250 HELO " + conn.vhost); from = null; to = new Vector();
+                } else if (c.startsWith("EHLO")) { conn.println("250");                    from = null; to = new Vector();
+                } else if (c.startsWith("RSET")) { conn.println("250 reset ok");           from = null; to = new Vector();
+                } else if (c.startsWith("HELP")) { conn.println("214 you are beyond help.  see a trained professional.");
+                } else if (c.startsWith("VRFY")) { conn.println("252 We don't VRFY; proceed anyway");
+                } else if (c.startsWith("EXPN")) { conn.println("550 EXPN not available");
+                } else if (c.startsWith("NOOP")) { conn.println("250 OK");
+                } else if (c.startsWith("QUIT")) { conn.println("221 " + conn.vhost + " closing connection"); return;
                 } else if (c.startsWith("MAIL FROM:")) {
-                    conn.out.println("250 " + (from = new Address(command.substring(10).trim())) + " is syntactically correct");
+                    conn.println("250 " + (from = new Address(command.substring(10).trim())) + " is syntactically correct");
                 } else if (c.startsWith("RCPT TO:")) {
-                    if (from == null) { conn.out.println("503 MAIL FROM must precede RCPT TO"); continue; }
+                    if (from == null) { conn.println("503 MAIL FROM must precede RCPT TO"); continue; }
                     command = command.substring(8).trim();
                     if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
                     Address addr = new Address(command);
-                    InetAddress[] mx = getMailExchangerIPs(addr.host);
+                    if (addr.isLocal()) conn.println("250 " + addr + " is on this machine; I will deliver it");
+                    else if (conn.getRemoteAddress().isLoopbackAddress())
+                        conn.println("250 you are connected locally, so I will let you send");
+                    else { conn.println("551 sorry, " + addr + " is not on this machine"); }
                     to.addElement(addr);
-                    if (conn.getRemoteAddress().isLoopbackAddress()) {
-                        conn.out.println("250 you are connected locally, so I will let you send");
-                    } else {
-                        boolean good = false;
-                        for(int i=0; !good && i<mx.length; i++) {
-                            try {
-                                if (NetworkInterface.getByInetAddress(mx[i]) != null) good = true;
-                            } catch (Exception e) { /* DELIBERATE */ }
-                        }
-                        if (!good) { conn.out.println("551 sorry, " + addr + " is not on this machine"); return; }
-                        conn.out.println("250 " + addr + " is on this machine; I will deliver it");
-                    }
                 } else if (c.startsWith("DATA")) {
-                    if (from == null) { conn.out.println("503 MAIL FROM command must precede DATA"); continue; }
-                    if (to == null) { conn.out.println("503 RCPT TO command must precede DATA"); continue; }
-                    conn.out.println("354 Enter message, ending with \".\" on a line by itself");
+                    if (from == null) { conn.println("503 MAIL FROM command must precede DATA"); continue; }
+                    if (to == null) { conn.println("503 RCPT TO command must precede DATA"); continue; }
+                    conn.println("354 Enter message, ending with \".\" on a line by itself");
+                    conn.flush();
                     try {
                         StringBuffer buf = new StringBuffer();
-                        for(String s = conn.in.readln(); ; s = conn.in.readln()) {
+                        while(true) {
+                            String s = conn.readln();
                             if (s == null) throw new RuntimeException("connection closed");
                             if (s.equals(".")) break;
                             if (s.startsWith(".")) s = s.substring(1);
-                            buf.append(s);
+                            buf.append(s + "\r\n");
                         }
                         String body = buf.toString();
+                        Message m = null;
                         for(int i=0; i<to.size(); i++) {
-                           Message m = new Message(from, (Address)to.elementAt(i), new Stream(body));
-                            Target.root.accept(m);
+                           m = new Message(from, (Address)to.elementAt(i), new Stream(body));
+                            if (!m.envelopeTo.isLocal()) Outgoing.accept(m);
+                            else                         Target.root.accept(m);
                        }
-                        conn.out.println("250 message accepted");
+                        if (m != null) Log.error(SMTP.class, "accepted message: " + m.summary());
+                        conn.println("250 message accepted");
+                        conn.flush();
                         from = null; to = new Vector();
-                    } catch (MailException.Malformed mfe) {   conn.out.println("501 " + mfe.toString());
-                    } catch (MailException.MailboxFull mbf) { conn.out.println("452 " + mbf);
-                    } catch (IOException ioe) {               conn.out.println("554 " + ioe.toString());
+                    } catch (MailException.Malformed mfe) {   conn.println("501 " + mfe.toString());
+                    } catch (MailException.MailboxFull mbf) { conn.println("452 " + mbf);
+                    } catch (IOException ioe) {               conn.println("554 " + ioe.toString());
                     }
-                } else                           { conn.out.println("500 unrecognized command"); }                    
-            }
+                } else                           { conn.println("500 unrecognized command"); }                    
+            } catch (Message.Malformed e) { conn.println("501 " + e.toString()); /* FIXME could be wrong code */ }
         }
     }
 
@@ -90,8 +88,10 @@ public class SMTP {
     // Outgoing Mail Thread //////////////////////////////////////////////////////////////////////////////
 
     public static class Outgoing {
+
         private static final HashSet deadHosts = new HashSet();
-        public static void send(Message m) throws IOException {
+        public static void accept(Message m) throws IOException {
+            Log.error("[outgoing]", "accept():\n" + m.summary());
             if (m.traces.length >= 100)
                 Log.warn(SMTP.Outgoing.class, "Message with " + m.traces.length + " trace hops; dropping\n" + m.summary());
             else synchronized(Outgoing.class) {
@@ -104,12 +104,12 @@ public class SMTP {
             InetAddress[] mx = getMailExchangerIPs(m.envelopeTo.host);
             if (mx.length == 0) {
                 Log.warn(SMTP.Outgoing.class, "could not resolve " + m.envelopeTo.host + "; bouncing it\n" + m.summary());
-                send(m.bounce("could not resolve " + m.envelopeTo.host));
+                accept(m.bounce("could not resolve " + m.envelopeTo.host));
                 return true;
             }
             if (new Date().getTime() - m.arrival.getTime() > 1000 * 60 * 60 * 24 * 5) {
                 Log.warn(SMTP.Outgoing.class, "could not send message after 5 days; bouncing it\n" + m.summary());
-                send(m.bounce("could not send for 5 days"));
+                accept(m.bounce("could not send for 5 days"));
                 return true;
             }
             for(int i=0; i<mx.length; i++) {
@@ -123,17 +123,17 @@ public class SMTP {
         private static boolean attempt(final Message m, final InetAddress mx) {
             boolean accepted = false;
             try {
-                Log.info(SMTP.Outgoing.class, "connecting...");
+                Log.info(SMTP.Outgoing.class, "connecting to " + mx + "...");
                 Connection conn = new Connection(new Socket(mx, 25), InetAddress.getLocalHost().getHostName());
                 Log.info(SMTP.Outgoing.class, "connected");
-                check(conn.in.readln());  // banner
-                conn.out.println("HELO " + conn.vhost);            check(conn.in.readln());
-                conn.out.println("MAIL FROM: " + m.envelopeFrom);  check(conn.in.readln());
-                conn.out.println("RCPT TO: " + m.envelopeTo);      check(conn.in.readln());
-                conn.out.println("DATA");                          check(conn.in.readln());
-                conn.out.println(m.body);
-                conn.out.println(".");
-                check(conn.in.readln());
+                check(conn.readln());  // banner
+                conn.println("HELO " + conn.vhost);            check(conn.readln());
+                conn.println("MAIL FROM: " + m.envelopeFrom.user + "@" + m.envelopeFrom.host);  check(conn.readln());
+                conn.println("RCPT TO: "   + m.envelopeTo.user + "@" + m.envelopeTo.host);      check(conn.readln());
+                conn.println("DATA");                          check(conn.readln());
+                conn.println(m.toString());
+                conn.println(".");
+                check(conn.readln());
                 Log.info(SMTP.Outgoing.class, "message accepted by " + mx);
                 accepted = true;
                 conn.close();
index cf47fe3..a7eccd4 100644 (file)
@@ -14,6 +14,8 @@ import java.text.*;
 /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
 public class FileBasedMailbox extends Mailbox.Default {
 
+    public String toString() { return "[FileBasedMailbox " + path + "]"; }
+
     private static final char slash = File.separatorChar;
     private static final Hashtable instances = new Hashtable();
     public static FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
@@ -87,6 +89,7 @@ public class FileBasedMailbox extends Mailbox.Default {
 
     public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
     public synchronized void add(Message message, int flags) {
+        Log.error(this, "adding message to ["+toString()+"]:\n" + message.summary());
         try {
             int num = new File(path).list(filter).length;
            String name = path + slash + uidNext(true) + "." +
@@ -102,6 +105,7 @@ public class FileBasedMailbox extends Mailbox.Default {
             message.dump(new Stream(fo));
             fo.close();
             f.renameTo(target);
+            Log.error(this, " done writing to " + target);
         } catch (IOException e) { throw new MailException.IOException(e); }
     }
 
@@ -116,7 +120,8 @@ public class FileBasedMailbox extends Mailbox.Default {
             try {
                 File file = new File(path + File.separatorChar + names[cur]);
                 return new Message(null, null, new Stream(new FileInputStream(file)));
-            } catch (IOException e) { throw new MailException.IOException(e); }
+            } catch (IOException e) { throw new MailException.IOException(e);
+            } catch (Message.Malformed e) { throw new MailException(e.getMessage()); }
         }
         public boolean next() {
             cur++;
index c742cc4..d7328b4 100644 (file)
@@ -41,7 +41,9 @@ public class Script extends Target {
     public synchronized void accept(Message m) throws IOException, MailException {
         this.m = m;
         try {
+            Log.error(this, "invoking config...");
             Object ret = js.call(m, null, null, null, 1);
+            Log.error(this, "config returned " + ret);
             if (ret == null) throw new IOException("configuration script returned null");
             if (ret instanceof Target)      ((Target)ret).accept(m);
             //else if (ret instanceof Filter) ((Filter)ret).process(m);
@@ -93,17 +95,51 @@ public class Script extends Target {
             if (name.equals("log.warn")) { return METHOD; }
             if (name.equals("log.error")) { return METHOD; }
             if (name.equals("mail")) { return getSub("mail"); }
+            if (name.equals("mail.forward")) { return METHOD; }
+            if (name.equals("mail.send")) { return METHOD; }
             if (name.equals("mail.my")) { return getSub("mail.my"); }
-            if (name.equals("mail.my.prefs")) { return prefs; }
+            if (name.equals("mail.my.prefs")) {
+                try {
+                    return new org.ibex.js.Directory(new File("/etc/org.ibex.mail.prefs"));
+                } catch (IOException e) {
+                    throw new JSExn(e.toString());
+                }
+            }
             if (name.equals("mail.my.mailbox")) {
                return FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT, true).slash("user", true).slash("megacz", true).slash("newmail", true);
            }
             return super.get(name);
         }
 
-        public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
+        public Object callMethod(Object name, final Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
             try {
                 if (name.equals("date")) { return new JSDate(a, b, c, rest, nargs); }
+                if (name.equals("mail.send")) {
+                    JS m = (JS)a;
+                    StringBuffer headers = new StringBuffer();
+                    String body = "";
+                    for(Enumeration e = m.keys(); e.hasMoreElements();) {
+                        String key = (String)e.nextElement();
+                        String val = m.get(key).toString();
+                        if (key.equals("body")) body = val;
+                        else headers.append(key + ": " + val + "\r\n");
+                    }
+                    Message message = new Message(null, null, new org.ibex.io.Stream(headers.toString() + "\r\n" + body));
+                    org.ibex.mail.protocol.SMTP.Outgoing.accept(message);
+                    return T;
+                }
+                if (name.equals("mail.forward")) { return new Target() {
+                        public void accept(Message m) throws MailException {
+                            try {
+                                Message m2 = new Message(m.envelopeFrom,
+                                                         new Address(a.toString()),
+                                                         new org.ibex.io.Stream(m.toString()));
+                                org.ibex.mail.protocol.SMTP.Outgoing.accept(m2);
+                            } catch (Exception e) {
+                                throw new MailException(e.toString());
+                            }
+                        }
+                    }; }
                 if (name.equals("log.debug")) {    JS.debug(a== null ? "**null**" : a.toString()); return null; }
                 if (name.equals("log.info")) {     JS.info(a== null ? "**null**" : a.toString()); return null; }
                 if (name.equals("log.warn")) {     JS.warn(a== null ? "**null**" : a.toString()); return null; }
@@ -115,8 +151,9 @@ public class Script extends Target {
                 case 2:
                     if (name.equals("regexp")) {return new JSRegexp(a, b); }
                 }
-            } catch (RuntimeException e) {
+            } catch (Exception e) {
                 Log.warn(this, "ibex."+name+"() threw: " + e);
+                if (e instanceof JSExn) throw ((JSExn)e);
                 throw new JSExn("invalid argument for ibex object method "+name+"()");
             }
             throw new JSExn("invalid number of arguments ("+nargs+") for ibex object method "+name+"()");