// soft line limit (suggested): 78 chars / hard line limit: 998 chars
// folded headers: can insert CRLF anywhere that whitespace appears (before the whitespace)
-// date/time parsing: see 3.3
+// date/time parsing: see spec, 3.3
// FEATURE: MIME RFC2045, 2046, 2049
// FEATURE: PGP-signature-parsing
public final Address envelopeFrom;
public final Address[] envelopeTo;
- public final Date arrival = null; // when the message first arrived at this machine
+ public final Date arrival; // when the message first arrived at this machine
public void dump(OutputStream os) throws IOException {
Writer w = new OutputStreamWriter(os);
w.flush();
}
+ /*
public static class StoredMessage extends Message {
public int uid;
public boolean deleted = false;
public boolean answered = false;
public StoredMessage(LineReader rs) throws IOException, MailException.Malformed { super(rs); }
}
+ */
public class Trace {
final String returnPath;
}
public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } }
- public Message(Address envelopeFrom, Address envelopeTo, LineReader rs) throws IOException, Malformed {
+ public Message(Address envelopeFrom, Address[] envelopeTo, LineReader rs) throws IOException, MailException.Malformed {
this.envelopeFrom = envelopeFrom;
this.envelopeTo = envelopeTo;
this.arrival = new Date();
}
}
- this.date = headers.get("Date");
+ this.date = (Date)headers.get("Date");
this.to = new Address((String)headers.get("To"));
this.from = new Address((String)headers.get("From"));
this.replyto = new Address((String)headers.get("Reply-To"));
if (headers.get("Cc") != null) {
StringTokenizer st = new StringTokenizer((String)headers.get("Cc"));
this.cc = new Address[st.countTokens()];
- for(int i=0; i<cc.length; i++) cc[i] = st.nextToken();
+ for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(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<bcc.length; i++) bcc[i] = st.nextToken();
+ for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
} else {
this.bcc = new Address[0];
}
public String summary() {
return
- " Subject: " + m.subject + "\n" +
- " EnvelopeFrom: " + m.envelopeFrom + "\n" +
- " EnvelopeTo: " + m.envelopeTo + "\n" +
- " MessageId: " + m.messageid;
+ " Subject: " + subject + "\n" +
+ " EnvelopeFrom: " + envelopeFrom + "\n" +
+ " EnvelopeTo: " + envelopeTo + "\n" +
+ " MessageId: " + messageid;
}
public Message bounce(String reason) {
convdir = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
convdir += File.separatorChar + "conversation";
new File(convdir).mkdirs();
- SMTP smtp = new SMTP();
+ final SMTP smtp = new SMTP();
int port = Integer.parseInt(System.getProperty("ibex.mail.port", "25"));
Log.info(SMTP.class, "binding to port " + port + "...");
ServerSocket ss = new ServerSocket(port);
public static class Outgoing {
private static final HashSet deadHosts = new HashSet();
private static final org.ibex.util.Queue queue = new org.ibex.util.Queue(100);
- private static FileSystem store = FileSystem.root.slash("outgoing");
- public static void send(Message m) {
+ public static void send(Message m) throws IOException {
if (m.traces.length >= 100) {
- Log.warn("Message with " + m.traces.length + " trace hops; silently dropping\n" + m.summary());
+ Log.warn(SMTP.Outgoing.class,
+ "Message with " + m.traces.length + " trace hops; silently dropping\n" + m.summary());
return;
}
synchronized(Outgoing.class) {
- store.add(m);
+ FileSystem.root.slash("outgoing").add(m);
queue.append(m);
Outgoing.class.notify();
}
}
- private static boolean attempt(Message m) {
- InetAddress[] mx = getMailExchangerIPs(m.envelopeTo.host);
+ // FIXME!!! ignores more than one destination envelope!!!!
+ private static boolean attempt(Message m) throws IOException {
+ InetAddress[] mx = getMailExchangerIPs(m.envelopeTo[0].host);
if (mx.length == 0) {
- Log.warn("could not resolve " + m.envelopeTo.host + "; bouncing it\n" + m.summary());
- send(m.bounce("could not resolve " + m.envelopeTo.host));
+ Log.warn(SMTP.Outgoing.class, "could not resolve " + m.envelopeTo[0].host + "; bouncing it\n" + m.summary());
+ send(m.bounce("could not resolve " + m.envelopeTo[0].host));
return true;
}
if (new Date().getTime() - m.arrival.getTime() > 1000 * 60 * 60 * 24 * 5) {
- Log.warn("could not send message after 5 days; bouncing it\n" + m.summary());
+ 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"));
return true;
}
for(int i=0; i<mx.length; i++) {
if (deadHosts.contains(mx[i])) continue;
- if (attempt(m, mx[i])) { queue.remove(m); return true; }
+ if (attempt(m, mx[i])) { return true; }
}
return false;
}
+ private static void check(String s) throws IOException {
+ if (s.startsWith("4") || s.startsWith("5")) throw new IOException("SMTP Error: " + s); }
private static boolean attempt(Message m, InetAddress mx) {
try {
- String conversationId = getConversation();
+ String vhost = InetAddress.getLocalHost().getHostName();
+ String cid = getConversation();
PrintWriter logf = new PrintWriter(new OutputStreamWriter(new FileOutputStream(convdir+File.separatorChar+cid)));
Log.setThreadAnnotation("[outgoing smtp: " + mx + " / " + cid + "] ");
Log.info(SMTP.Outgoing.class, "connecting...");
Socket s = new Socket(mx, 25);
Log.info(SMTP.Outgoing.class, "connected");
- LineReader r = new LoggedLineReader(new InputStreamReader(conn.getInputStream()), logf);
- PrintWriter w = new LoggedPrintWriter(new OutputStreamWriter(conn.getOutputStream()), logf);
+ LineReader r = new LoggedLineReader(new InputStreamReader(s.getInputStream()), logf);
+ PrintWriter w = new LoggedPrintWriter(new OutputStreamWriter(s.getOutputStream()), logf);
check(r.readLine()); // banner
w.print("HELO " + vhost + "\r\n"); check(r.readLine());
w.print("MAIL FROM: " + m.envelopeFrom + "\r\n"); check(r.readLine());
w.print(".\r\n");
check(r.readLine());
Log.info(SMTP.Outgoing.class, "message accepted by " + mx);
- m.delete();
+ FileSystem.root.slash("outgoing").delete(m);
s.close();
return true;
} catch (Exception e) {
}
static void runq() {
- Log.setThreadAnnotation("[outgoing smtp] ");
- int[] outgoing = store.list();
- Log.info("outgoing thread started; " + outgoing.length + " messages to send");
- for(int i=0; i<outgoing.length; i++) queue.append(store.get(outgoing[i]));
- while(true) {
- int num = queue.size();
- for(int i=0; i<num; i++) {
- Message next = queue.remove(true);
- if (!attempt(next)) queue.append(next);
- }
- synchronized(Outgoing.class) {
- Log.info("outgoing thread going to sleep");
- Outgoing.class.wait(10 * 60 * 1000);
- deadHosts.clear();
- Log.info("outgoing thread woke up; " + queue.size() + " messages in queue");
+ try {
+ Log.setThreadAnnotation("[outgoing smtp] ");
+ int[] outgoing = FileSystem.root.slash("outgoing").list();
+ Log.info(SMTP.Outgoing.class, "outgoing thread started; " + outgoing.length + " messages to send");
+ for(int i=0; i<outgoing.length; i++) queue.append(FileSystem.root.slash("outgoing").get(outgoing[i]));
+ while(true) {
+ int num = queue.size();
+ for(int i=0; i<num; i++) {
+ Message next = (Message)queue.remove(true);
+ boolean good = false;
+ try {
+ good = attempt(next);
+ } catch (IOException e) {
+ Log.error(SMTP.Outgoing.class, e);
+ } finally {
+ if (!good) queue.append(next);
+ }
+ }
+ synchronized(Outgoing.class) {
+ Log.info(SMTP.Outgoing.class, "outgoing thread going to sleep");
+ Outgoing.class.wait(10 * 60 * 1000);
+ deadHosts.clear();
+ Log.info(SMTP.Outgoing.class, "outgoing thread woke up; " + queue.size() + " messages in queue");
+ }
}
+ } catch (Exception e) {
+ Log.error(SMTP.Outgoing.class, e);
}
}
}
return false;
}
- static String getConversation() {
- String time = new SimpleDateFormat("yy.MMM.dd-hh:mm:ss").format(new Date());
- synchronized (SMTP.class) {
- if (lastTime != null && lastTime.equals(time)) {
- time += "." + (++lastCounter);
- } else {
- lastTime = time;
- }
- }
- return time;
- }
-
- private class LoggedLineReader extends LineReader {
- PrintWriter log;
- public LoggedLineReader(Reader r, PrintWriter log) { super(r); this.log = log; }
- public String readLine() throws IOException {
- String s = super.readLine();
- if (s != null) { log.println("C: " + s); log.flush(); }
- return s;
- }
- }
-
- private class LoggedPrintWriter extends PrintWriter {
- PrintWriter log;
- public LoggedPrintWriter(Writer w, PrintWriter log) { super(w); this.log = log; }
- public void println(String s) {
- log.println("S: " + s);
- super.println(s);
- flush();
- }
- }
-
public boolean handleRequest(LineReader rs, PrintWriter ws) throws IOException, MailException {
//ReadStream rs = conn.getReadStream();
//WriteStream ws = conn.getWriteStream();
command = command.substring(9).trim();
if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
Address addr = new Address(command);
- // FIXME: 551 = no, i won't forward that
+ InetAddress[] mx = getMailExchangerIPs(addr.host);
to.addElement(addr);
- ws.println("250 " + addr + " is syntactically correct");
+ if (((InetSocketAddress)conn.getRemoteSocketAddress()).getAddress().isLoopbackAddress()) {
+ ws.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++)
+ if (NetworkInterface.getByInetAddress(mx[i]) != null)
+ good = true;
+ if (!good) {
+ ws.println("551 sorry, " + addr + " is not on this machine");
+ return false;
+ }
+ ws.println("250 " + addr + " is on this machine; I will deliver it");
+ }
} else if (c.startsWith("DATA")) {
if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; }
if (to == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
ws.println("354 Enter message, ending with \".\" on a line by itself");
- StringBuffer data = new StringBuffer();
- // move this into the Message class
- boolean good = false;
try {
- accept(new Message(new DotTerminatedLineReader(rs)));
- } catch (MailException.Malformed mfe) {
- ws.println("501 " + mfe.toString()); break;
- } catch (MailException.MailboxFull mbf) {
- ws.println("452 " + mbf);
- } catch (IOException ioe) {
- ws.println("554 " + ioe.toString()); break;
+ Address[] toArr = new Address[to.size()];
+ to.copyInto(toArr);
+ accept(new Message(from, toArr, new DotTerminatedLineReader(rs)));
+ ws.println("250 message accepted");
+ } catch (MailException.Malformed mfe) { ws.println("501 " + mfe.toString());
+ } catch (MailException.MailboxFull mbf) { ws.println("452 " + mbf);
+ } catch (IOException ioe) { ws.println("554 " + ioe.toString());
}
- ws.println("250 message accepted"); break;
-
+ break;
+
} else if (c.startsWith("HELP")) { ws.println("214 you are beyond help. see a trained professional.");
} else if (c.startsWith("VRFY")) { ws.println("252 We don't VRFY; proceed anyway");
} else if (c.startsWith("EXPN")) { ws.println("550 EXPN not available");
}
}
- public InetAddress[] getMailExchangerIPs(String domainName) {
- InetAddress ret;
+ public static InetAddress[] getMailExchangerIPs(String hostName) {
+ InetAddress[] ret;
try {
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
if (attr == null) {
ret = new InetAddress[1];
try {
- ret[0] = InetAddress.getByName(domainName);
+ ret[0] = InetAddress.getByName(hostName);
return ret;
} catch (UnknownHostException uhe) {
- Log.warn(SMTP.class, "no MX hosts or A record for " + domainName);
+ Log.warn(SMTP.class, "no MX hosts or A record for " + hostName);
return new InetAddress[0];
}
} else {
for(int i=0; ne.hasMore(); i++) ret[i] = (InetAddress)ne.next();
}
} catch (Exception e) {
- Log.warn(SMTP.class, "couldn't find MX host for " + domainName + " due to");
+ Log.warn(SMTP.class, "couldn't find MX host for " + hostName + " due to");
Log.warn(SMTP.class, e);
return new InetAddress[0];
}
return ret;
}
+ private static class LoggedLineReader extends LineReader {
+ PrintWriter log;
+ public LoggedLineReader(Reader r, PrintWriter log) { super(r); this.log = log; }
+ public String readLine() throws IOException {
+ String s = super.readLine();
+ if (s != null) { log.println("C: " + s); log.flush(); }
+ return s;
+ }
+ }
+
+ private static class LoggedPrintWriter extends PrintWriter {
+ PrintWriter log;
+ public LoggedPrintWriter(Writer w, PrintWriter log) { super(w); this.log = log; }
+ public void println(String s) {
+ log.println("S: " + s);
+ super.println(s);
+ flush();
+ }
+ }
+
+ static String getConversation() {
+ String time = new SimpleDateFormat("yy.MMM.dd-hh:mm:ss").format(new Date());
+ synchronized (SMTP.class) {
+ if (lastTime != null && lastTime.equals(time)) {
+ time += "." + (++lastCounter);
+ } else {
+ lastTime = time;
+ }
+ }
+ return time;
+ }
}
package org.ibex.mail.target;
+import org.ibex.mail.*;
import org.ibex.util.*;
import org.ibex.mail.*;
import java.io.*;
private static final String STORAGE_ROOT =
System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
-
- //public final FileBased root = new FileBased(STORAGE_ROOT + File.separatorChar);
+ public static FileBased root = null;
public static Transcript transcript = null;
static {
try {
+ root = new FileBased(STORAGE_ROOT + File.separatorChar);
transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
} catch (Exception e) {
e.printStackTrace();
public int[] list() { throw new Error(this.getClass().getName() + " does not support the list() method"); }
public int add(Message message) throws IOException {
throw new Error(this.getClass().getName() + " does not support the add() method"); }
- public Message.StoredMessage get(int messagenum) throws IOException {
+ public int delete(Message message) throws IOException {
+ throw new Error(this.getClass().getName() + " does not support the delete() method"); }
+ public Message get(int messagenum) throws IOException {
throw new Error(this.getClass().getName() + " does not support the get() method"); }
- public Message.StoredMessage[] query(int maxResults) {
+ public Message[] 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 */
return target;
}
- public Message.StoredMessage get(int messagenum) throws IOException {
+ public Message get(int messagenum) throws IOException {
File f = new File(path + File.separatorChar + messagenum + ".");
if (!f.exists()) throw new FileNotFoundException(f.toString());
- try {
- Message.StoredMessage ret =
- new Message.StoredMessage(new LineReader(new InputStreamReader(new FileInputStream(f))));
+ //try {
+ // FIXME: need to store envelope from/to
+ //Message ret = new Message(new LineReader(new InputStreamReader(new FileInputStream(f))));
// FIXME: set answered/read/etc here
- return ret;
+ //return ret;
+ return null;
+ /*
} catch (MailException.Malformed malf) {
Log.error(this, "This should never happen");
Log.error(this, malf);
return null;
}
+ */
}
// query types: stringmatch (headers, body), header element, deletion status, date range, message size
- public Message.StoredMessage[] query(int maxResults) {
+ public Message[] query(int maxResults) {
throw new RuntimeException("FileBased.query() not implemented yet");
}