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
--- /dev/null
+// 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); }}
+}
--- /dev/null
+// 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;
+ }
+ });
+ }
+}
--- /dev/null
+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");
+ }
+ }
+}
--- /dev/null
+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);
+
+}
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+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); }
+ }
+
+}
--- /dev/null
+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); }
+ }
+ }
+}
--- /dev/null
+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;
+ }
+ }
+
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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; }
+}
--- /dev/null
+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);
+}
+
+
+
+
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;
+ }
}
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);
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);
- }
- }
- }
- */
}
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));
}
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();
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");
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")) {
}
// 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];
}
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
/** 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"); }
}
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.*;
// 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;
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() { }
}
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);
}
}
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();
}
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
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
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;
}
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(); };
}
}
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);
}
} 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();
}
}
}
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.*;
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 */ }
}
}
// 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) {
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++) {
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();
/** 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) {
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) + "." +
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); }
}
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++;
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);
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; }
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+"()");