--- /dev/null
+all: mail.jar
+
+clean: ; rm -rf build
+dist-clean:
+ rm -rf .configure* .install* build .compile .build*
+
+sources := $(shell find src -name \*.java)
+
+.download_org.ibex.%:
+ @echo -e "\033[1mfetching repository org.ibex.$*\033[0m"
+ @mkdir -p upstream; cd upstream; rm -rf org.ibex.$*; rm -rf org.ibex.$*_*
+ @cd upstream; darcs get --verbose --partial --repo-name=org.ibex.$* http://$*.ibex.org
+ @touch $@
+
+.build_org.ibex.%:
+ @echo -e "\033[1mbuilding repository org.ibex.$*\033[0m"
+ @cd upstream/org.ibex.$*; make compile
+ @touch $@
+
+mail.jar: $(sources)
+ @make .download_org.ibex.crypto .build_org.ibex.core
+ @mkdir -p build/class
+ @javac -d build/class -sourcepath upstream/org.ibex.crypto/src/ -classpath upstream/org.ibex.core/build/class $^
+
+package org.ibex.mail;
+import java.net.*;
+import java.io.*;
public class MailException extends Exception {
public static class RelayingDenied extends MailException { }
public static class IOException extends MailException {
// FIXME: fill in stack trace
- final IOException ioe;
+ final java.io.IOException ioe;
public IOException(java.io.IOException ioe) { this.ioe = ioe; }
}
package org.ibex.mail;
import org.ibex.crypto.*;
+import org.ibex.js.*;
+import org.ibex.util.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+
// FIXME MIME: RFC2045, 2046, 2049
// NOTE: always use Win32 line endings
// hard line limit: 998 chars
public final String allHeaders; // pristine headers
public final Hashtable headers; // hash of headers (not including resent's and traces)
+ public final String body; // entire body
// parsed header fields
public final Date date;
public final Address envelopeFrom;
public final Address[] envelopeTo;
+ public void dump(OutputStream os) {
+ Log.error(this, "not implemented");
+ }
+
public static class StoredMessage extends Message {
+ public StoredMessage(/*ReadStream rs*/BufferedReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
+ super(rs, dotTerminatedLikeSMTP); uid = -1; }
public final int uid;
public boolean deleted = false;
public boolean read = false;
public boolean answered = false;
public String dumpStoredForm() { throw new Error("StoredMessage.dumpStoredForm() not implemented"); };
+ public static StoredMessage undump(InputStream os) {
+ Log.error(StoredMessage.class, "not implemented");
+ return null;
+ }
}
public static class Address extends JSReflection {
}
public class Trace {
- final String returnPath = null;
- final Element[] elements;
+ String returnPath = null;
+ Element[] elements;
public class Element {
- final String fromDomain;
- final String fromIP;
- final String toDomain;
- final String forWhom;
- final Date date;
+ // FIXME final
+ String fromDomain;
+ String fromIP;
+ String toDomain;
+ String forWhom;
+ Date date;
}
}
// FIXME: support dotTerminatedLikeSMTP
- public Message(ReadStream rs, boolean dotTermiantedLikeSMTP) {
+ public Message(/*ReadStream rs*/BufferedReader rs, boolean dotTerminatedLikeSMTP) throws IOException {
String key = null;
StringBuffer all = new StringBuffer();
+ String lastKey = null;
+ replyto = null;
+ subject = null;
+ messageid = null;
+ cc = null;
+ bcc = null;
+ resent = null;
+ traces = null;
+ envelopeFrom = null;
+ envelopeTo = null;
+
+ headers = new Hashtable();
+ date = null; // FIXME
+ to = null;
+ from = null;
for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
all.append(s);
all.append("\r\n");
headers.put(key, val);
}
- pristeneHeaders = all.toString();
+ allHeaders = all.toString();
StringBuffer body = new StringBuffer();
for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) body.append(s);
this.body = body.toString();
package org.ibex.mail.filter;
+import org.ibex.mail.*;
/** generic superclass for processing elements that recieve a message and "usually" pass it on */
public class Filter {
+ public Message process(Message m) { return null; }
}
package org.ibex.mail.protocol;
+import org.ibex.mail.*;
+import org.ibex.mail.store.*;
+import org.ibex.mail.target.*;
+import java.io.*;
public class Incoming {
protected void accept(Message m) throws IOException {
- // currently, we write all inbound messages to the transcript
- MessageStore.transcript.add(m);
-
- // FIXME: figure out where the message goes next
+ MessageStore.transcript.add((Message.StoredMessage)m); // currently, we write all inbound messages to the transcript
+ Target.root.accept(m);
}
}
package org.ibex.mail.protocol;
/** base class for over-the-wire protocols used to send, recieve, and serve messages */
-import com.caucho.server.connection.*;
-import com.caucho.server.port.*;
+//import com.caucho.server.connection.*;
+//import com.caucho.server.port.*;
-public class MessageProtocol extends com.caucho.server.port.Protocol {
+public class MessageProtocol/* extends com.caucho.server.port.Protocol*/ {
}
package org.ibex.mail.protocol;
+import org.ibex.mail.*;
+import org.ibex.mail.store.*;
+import org.ibex.mail.target.*;
+import org.ibex.util.*;
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
public class SMTP extends MessageProtocol {
- public SMTP() { setProtocolName("SMTP"); }
- public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); }
+ public SMTP() {/* setProtocolName("SMTP"); */}
+ //public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); }
public static class Outgoing {
// recommended retry interval is 30 minutes
// only use implicit A-record if there are no MX-records
// use null-sender for error messages (don't send errors to the null addr)
// to prevent mail loops, drop messages with >100 Recieved headers
- private final Queue queue = new Queue();
+ private final org.ibex.util.Queue queue = new org.ibex.util.Queue(100);
public static void send(Message m) { }
public static void enqueue(Message m) { }
public static void bounce(Message m, String reason) { }
private void runq() {
+ /*
MessageStore store = MessageStore.root.slash("smtp").slash("outgoing");
int[] outgoing = store.list();
for(int i=0; i<outgoing.length; i++) queue.append(store.get(outgoing[i]));
while(true) {
- Message next = queue.dequeue(true);
+ Message next = queue.remove(true);
// FIXME
}
+ */
}
}
- private class Listener extends Incoming implements ServerRequest {
- TcpConnection conn;
- public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
+ private class Listener extends Incoming /*implements ServerRequest*/ {
+ //TcpConnection conn;
+ Socket conn;
+ //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
+ public Listener(Socket conn) throws IOException { this.conn = conn; conn.setSoTimeout(5 * 60 * 1000); }
public void init() { }
public boolean handleRequest() throws IOException {
- ReadStream rs = conn.getReadStream();
- WriteStream ws = conn.getWriteStream();
- ws.setNewLineString("\r\n");
+ //ReadStream rs = conn.getReadStream();
+ //WriteStream ws = conn.getWriteStream();
+ BufferedReader rs = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ PrintWriter ws = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
+
+ // FIXME
+ //ws.setNewLineString("\r\n");
- ws.println("220 " + conn.getVirtualHost() + " ESMTP " + this.getClass().getName());
+ ws.println("220 " + /*conn.getVirtualHost()*/ "megacz.com" + " ESMTP " + this.getClass().getName());
- Address from = null;
+ Message.Address from = null;
Vector to = new Vector();
// 551 = no, i won't forward that
// 452 = mailbox full
String command = rs.readLine();
// FIXME: validate the HELO domain argument
// (double check against other end of connection? must not reject though)
- if (hello.toUpperCase().startsWith("HELO")) {
- ws.println("250 HELO " + conn.getVirtualHost());
+ if (command.toUpperCase().startsWith("HELO")) {
+ ws.println("250 HELO " + /*conn.getVirtualHost()*/("megacz.com"));
from = null;
to = new Vector();
- } else if (hello.toUpperCase().startsWith("EHLO")) {
- ws.pritnln("250-" + conn.getVirtualHost());
+ } else if (command.toUpperCase().startsWith("EHLO")) {
+ ws.println("250-" + /*conn.getVirtualHost()*/("megacz.com"));
ws.println("250-SIZE");
ws.println("250 PIPELINING");
from = null;
} else if (command.toUpperCase().startsWith("MAIL FROM:")) {
command = command.substring(10).trim();
if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
- from = RFC2822.Address.parse(command);
+ from = new Message.Address(command);
} else if (command.toUpperCase().startsWith("RCPT TO:")) {
if (from == null) {
}
command = command.substring(10).trim();
if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
- to.addElement(RFC2822.Address.parse(command));
+ to.addElement(new Message.Address(command));
} else if (command.toUpperCase().startsWith("DATA")) {
if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; }
- if (to.size() == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
+ if (to == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
ws.println("354 Enter message, ending with \".\" on a line by itself");
StringBuffer data = new StringBuffer();
- // move this into the RFC2822 class
+ // move this into the Message class
boolean good = false;
try {
good = true;
- Message m = new Message(line, true);
- Target.default.accept(m);
+ Message m = new Message(rs, true);
+ accept(m);
} finally {
//ws.println("251 user not local; will forward");
if (good) ws.println("250 OK message accepted for delivery");
} else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available");
} else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK");
} else if (command.toUpperCase().startsWith("QUIT")) {
- ws.println("221 " + conn.getVirtualHost() + " closing connection");
+ ws.println("221 " + /*conn.getVirtualHost()*/("megacz.com") + " closing connection");
break;
} else {
ws.println("500 unrecognized command");
}
-
- return false; // FIXME: what does this mean?
}
+ return false; // FIXME: what does this mean?
}
}
}
package org.ibex.mail.store;
import org.ibex.util.*;
+import org.ibex.mail.*;
import java.io.*;
import java.net.*;
+import java.util.*;
+import java.text.*;
// FIXME: appallingly inefficient
public class MessageStore {
- private final String STORAGE_ROOT = System.getProperty("ibex.mail.root",
- File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
+ private static final String STORAGE_ROOT = System.getProperty("ibex.mail.root",
+ File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
//public final FileBased root = new FileBased(STORAGE_ROOT + File.separatorChar);
- public final FileBased transcript = new FileBased(STORAGE_ROOT + File.separatorChar + "transcript");
+ public static FileBased transcript = null;
+ static {
+ try {
+ transcript = new FileBased(STORAGE_ROOT + File.separatorChar + "transcript");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
- public MessageStore slash(String name) {
+ public MessageStore slash(String name) throws IOException {
throw new Error(this.getClass().getName() + " does not support the slash() method"); }
public int[] list() { throw new Error(this.getClass().getName() + " does not support the list() method"); }
- public int add(StoredMessage message) throws IOException {
+ public int add(Message.StoredMessage message) throws IOException {
throw new Error(this.getClass().getName() + " does not support the add() method"); }
- public StoredMessage get(int messagenum) throws IOException {
+ public Message.StoredMessage get(int messagenum) throws IOException {
throw new Error(this.getClass().getName() + " does not support the get() method"); }
- public StoredMessage[] query(int maxResults) {
+ public Message.StoredMessage[] query(int maxResults) {
throw new Error(this.getClass().getName() + " does not support the query() method"); }
/** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
private static int lastCounter = 0;
/** returns a message identifier */
- public synchronized int add(StoredMessage message) throws IOException {
+ public synchronized int add(Message.StoredMessage message) throws IOException {
File today = new File(path + File.separatorChar + (new SimpleDateFormat("yyyyy.MMMMM.dd").format(new Date())));
today.mkdirs();
}
}
- File target = new File(today.getPath() + File.separatorChar() + time + ".txt");
+ File target = new File(today.getPath() + File.separatorChar + time + ".txt");
String msg = message.dumpStoredForm();
OutputStream os = new FileOutputStream(target);
os.write(msg.getBytes("UTF-8")); // FIXME: right?
}
}
- public static FileBased extends MessageStore {
+ public static class FileBased extends MessageStore {
private String path;
private FileBased(String path) throws IOException { new File(this.path = path).mkdirs(); }
- public FileBased slash(String name) { return new FileBased(path + "/" + name); }
+ public FileBased slash(String name) throws IOException { return new FileBased(path + "/" + name); }
public int[] list() {
String[] names = new File(path).list();
int[] ret = new int[names.length];
for(int i=0, j=0; j<ret.length; i++, j++) {
try {
- ret[j] = Integer.parseInt(names[i].substring(0, names[i].length - 1));
+ ret[j] = Integer.parseInt(names[i].substring(0, names[i].length() - 1));
} catch (NumberFormatException nfe) {
- Log.warn(FileBased.class, "NumberFormatException: " + names[i].substring(0, names[i].length - 1));
+ Log.warn(FileBased.class, "NumberFormatException: " + names[i].substring(0, names[i].length() - 1));
j--;
int[] newret = new int[ret.length - 1];
- System.arrayCopy(ret, 0, newret, 0, newret.length);
+ System.arraycopy(ret, 0, newret, 0, newret.length);
ret = newret;
}
}
}
/** returns a message identifier */
- public synchronized int add(StoredMessage message) throws IOException {
+ public synchronized int add(Message.StoredMessage message) throws IOException {
int[] all = list();
int max = 0;
for(int i=0; i<all.length; i++) max = Math.max(max, all[i]);
FileOutputStream fo = new FileOutputStream(f);
message.dump(fo);
fo.close();
- f.renameTo(path + File.separatorChar + max + ".");
+ f.renameTo(new File(path + File.separatorChar + max + "."));
return target;
}
- public StoredMessage get(int messagenum) throws IOException {
+ public Message.StoredMessage get(int messagenum) throws IOException {
File f = new File(path + File.separatorChar + messagenum + ".");
- if (!f.exists()) throw new FileNotFoundException(f);
- return StoredMessage.undump(new FileInputStream(f));
+ if (!f.exists()) throw new FileNotFoundException(f.toString());
+ return Message.StoredMessage.undump(new FileInputStream(f));
}
// query types: stringmatch (headers, body), header element, deletion status, date range, message size
- public StoredMessage[] query(int maxResults) {
+ public Message.StoredMessage[] query(int maxResults) {
throw new RuntimeException("FileBased.query() not implemented yet");
}
// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
package org.ibex.mail;
import org.ibex.js.*;
+import org.ibex.util.*;
+import org.ibex.mail.filter.*;
+import org.ibex.mail.target.*;
+import org.ibex.mail.store.*;
+import java.io.*;
+import java.util.*;
-public class Script {
+public class Script extends Target {
- public static final JS root =
- new Script(System.getProperty("ibex.mail.conf", File.separatorChar + "etc" + File.separatorChar + "org.ibex.mail.conf"));
+ public static Script root = null;
+ static {
+ try {
+ root = new Script(System.getProperty("ibex.mail.conf", File.separatorChar + "etc" + File.separatorChar + "org.ibex.mail.conf"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
final JS js;
- public Script(String filePath) { js = JS.fromReader(CONF, 0, new InputStreamReader(new FileInputStream(CONF))); }
+ public Script(String filePath) throws JSExn, IOException {
+ js = JS.fromReader(filePath, 0, new InputStreamReader(new FileInputStream(filePath))); }
public void accept(Message m) throws IOException {
- // currently, we write all inbound messages to the transcript
- MessageStore.transcript.add(m);
- Object ret = js.call(m);
- if (ret instanceof Target) {
- ((Target)ret).accept(m);
- } else if (ret instanceof Filter) {
- ((Filter)f).accept
- } else {
- throw new IOException("configuration script returned a " + ret.getClass().getName());
+ try {
+ // currently, we write all inbound messages to the transcript
+ // FIXME
+ //MessageStore.transcript.add(m);
+ Object ret = js.call(m, null, null, null, 1);
+ if (ret instanceof Target) {
+ ((Target)ret).accept(m);
+ } else if (ret instanceof Filter) {
+ // FIXME: return value?
+ ((Filter)ret).process(m);
+ } else {
+ throw new IOException("configuration script returned a " + ret.getClass().getName());
+ }
+ } catch (JSExn e) {
+ e.printStackTrace();
}
}
// FIXME: this should extend org.ibex.core.Ibex
- public static class ScriptEnvironment extends JS {
+ public static class ScriptEnv extends JS {
// FIXME: duplicated code with org.ibex.core.Ibex; lift?
/** lets us put multi-level get/put/call keys all in the same method */
return ScriptEnv.this.callMethod(this.key + "." + method, a0, a1, a2, rest, nargs);
}
}
- private Cache subCache = new Cache(20);
- private Sub getSub(String s) {
- Sub ret = (Sub)subCache.get(s);
- if (ret == null) subCache.put(s, ret = new Sub(s));
- return ret;
- }
+ private Sub getSub(String s) { return new Sub(s); }
public Object get(Object name) throws JSExn {
- if (name instanceof String && ((String)name).length() == 0) return rr;
- //#switch(name)
- case "math": return ibexMath;
- case "string": return ibexString;
- case "date": return METHOD;
- case "regexp": return METHOD;
- case "log": return getSub("log");
- case "log.debug": return METHOD;
- case "log.info": return METHOD;
- case "log.warn": return METHOD;
- case "log.error": return METHOD;
- //#end
+ if (name.equals("math")) { return ibexMath; }
+ if (name.equals("string")) { return ibexString; }
+ if (name.equals("date")) { return METHOD; }
+ if (name.equals("regexp")) { return METHOD; }
+ if (name.equals("log")) { return getSub("log"); }
+ if (name.equals("log.debug")) { return METHOD; }
+ if (name.equals("log.info")) { return METHOD; }
+ if (name.equals("log.warn")) { return METHOD; }
+ if (name.equals("log.error")) { return METHOD; }
return super.get(name);
}
public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
try {
- //#switch(name)
- case "date": return new JSDate(a, b, c, rest, nargs);
- case "log.debug": JS.debug(a== null ? "**null**" : a.toString()); return null;
- case "log.info": JS.info(a== null ? "**null**" : a.toString()); return null;
- case "log.warn": JS.warn(a== null ? "**null**" : a.toString()); return null;
- case "log.error": JS.error(a== null ? "**null**" : a.toString()); return null;
- //#end
+ if (name.equals("date")) { return new JSDate(a, b, c, rest, nargs); }
+ 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; }
+ if (name.equals("log.error")) { JS.error(a== null ? "**null**" : a.toString()); return null; }
switch (nargs) {
case 1:
- //#switch(name)
- case "regexp": return new JSRegexp(a, null);
- //#end
+ if (name.equals("regexp")) { return new JSRegexp(a, null); }
break;
case 2:
- //#switch(name)
- case "regexp": return new JSRegexp(a, b);
- //#end
+ if (name.equals("regexp")) { return new JSRegexp(a, b); }
}
} catch (RuntimeException e) {
// FIXME: maybe JSExn should take a second argument, Exception
public static final JSMath ibexMath = new JSMath() {
private JS gs = new JSScope.Global();
- public Object get(Object key) throws JSExn {
- //#switch(key)
- case "isNaN": return gs.get("isNaN");
- case "isFinite": return gs.get("isFinite");
- case "NaN": return gs.get("NaN");
- case "Infinity": return gs.get("Infinity");
- //#end
- return super.get(key);
+ public Object get(Object name) throws JSExn {
+ if (name.equals("isNaN")) { return gs.get("isNaN"); }
+ if (name.equals("isFinite")) { return gs.get("isFinite"); }
+ if (name.equals("NaN")) { return gs.get("NaN"); }
+ if (name.equals("Infinity")) { return gs.get("Infinity"); }
+ return super.get(name);
}
};
public static final JS ibexString = new JS() {
private JS gs = new JSScope.Global();
public void put(Object key, Object val) { }
- public Object get(Object key) throws JSExn {
- //#switch(key)
- case "parseInt": return gs.get("parseInt");
- case "parseFloat": return gs.get("parseFloat");
- case "decodeURI": return gs.get("decodeURI");
- case "decodeURIComponent": return gs.get("decodeURIComponent");
- case "encodeURI": return gs.get("encodeURI");
- case "encodeURIComponent": return gs.get("encodeURIComponent");
- case "escape": return gs.get("escape");
- case "unescape": return gs.get("unescape");
- case "fromCharCode": return gs.get("stringFromCharCode");
- //#end
+ public Object get(Object name) throws JSExn {
+ if (name.equals("parseInt")) { return gs.get("parseInt"); }
+ if (name.equals("parseFloat")) { return gs.get("parseFloat"); }
+ if (name.equals("decodeURI")) { return gs.get("decodeURI"); }
+ if (name.equals("decodeURIComponent")) { return gs.get("decodeURIComponent"); }
+ if (name.equals("encodeURI")) { return gs.get("encodeURI"); }
+ if (name.equals("encodeURIComponent")) { return gs.get("encodeURIComponent"); }
+ if (name.equals("escape")) { return gs.get("escape"); }
+ if (name.equals("unescape")) { return gs.get("unescape"); }
+ if (name.equals("fromCharCode")) { return gs.get("stringFromCharCode"); }
return null;
}
};
package org.ibex.mail.target;
+import org.ibex.mail.*;
+import java.io.*;
+import org.ibex.js.*;
/** base class for mail message "destinations" */
public class Target {
- public static final Target default = Script.root;
- public void accept(Message m) { /* FIXME */ }
+ public static final Target root = Script.root;
+ public void accept(Message m) throws IOException { /* FIXME */ }
}