1 package org.ibex.mail.protocol;
2 public class SMTP extends MessageProtocol {
4 public SMTP() { setProtocolName("SMTP"); }
5 public ServerRequest createRequest(Connection conn) { return new Request((TcpConnection)conn); }
7 public static class Outgoing {
8 // recommended retry interval is 30 minutes
9 // give up after 4-5 days
10 // should keep per-host success/failure so we don't retry on every message
11 // exponential backoff on retry time?
12 // check DNS resolvability as soon as domain is provided
13 // only use implicit A-record if there are no MX-records
14 // use null-sender for error messages (don't send errors to the null addr)
15 // to prevent mail loops, drop messages with >100 Recieved headers
16 private final Queue queue = new Queue();
17 public static void send(Message m) { }
18 public static void enqueue(Message m) { }
19 public static void bounce(Message m, String reason) { }
21 MessageStore store = MessageStore.root.slash("smtp").slash("outgoing");
22 int[] outgoing = store.list();
23 for(int i=0; i<outgoing.length; i++) queue.append(store.get(outgoing[i]));
25 Message next = queue.dequeue(true);
31 private class Incoming implements ServerRequest {
33 public Incoming(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
34 public void init() { }
36 public boolean handleRequest() throws IOException {
37 ReadStream rs = conn.getReadStream();
38 WriteStream ws = conn.getWriteStream();
39 ws.setNewLineString("\r\n");
41 ws.println("220 " + conn.getVirtualHost() + " ESMTP " + this.getClass().getName());
44 Vector to = new Vector();
45 // 551 = no, i won't forward that
47 // see 4.4 for trace info
49 String command = rs.readLine();
50 // FIXME: validate the HELO domain argument
51 // (double check against other end of connection? must not reject though)
52 if (hello.toUpperCase().startsWith("HELO")) {
53 ws.println("250 HELO " + conn.getVirtualHost());
57 } else if (hello.toUpperCase().startsWith("EHLO")) {
58 ws.pritnln("250-" + conn.getVirtualHost());
59 ws.println("250-SIZE");
60 ws.println("250 PIPELINING");
64 } else if (command.toUpperCase().startsWith("RSET")) {
67 ws.println("250 reset ok");
69 } else if (command.toUpperCase().startsWith("MAIL FROM:")) {
70 command = command.substring(10).trim();
71 if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
72 from = RFC2822.Address.parse(command);
74 } else if (command.toUpperCase().startsWith("RCPT TO:")) {
76 ws.println("503 MAIL FROM must precede RCPT TO");
79 command = command.substring(10).trim();
80 if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
81 to.addElement(RFC2822.Address.parse(command));
83 } else if (command.toUpperCase().startsWith("DATA")) {
84 if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); continue; }
85 if (to.size() == null) { ws.println("503 RCPT TO command must precede DATA"); continue; }
86 ws.println("354 Enter message, ending with \".\" on a line by itself");
87 StringBuffer data = new StringBuffer();
88 // move this into the RFC2822 class
90 String line = rs.readLine();
91 if (line.equals(".")) break;
92 if (line.startsWith("..")) line = line.substring(1);
95 // FIXME: commit message to disk here
96 ws.println("250 OK message accepted for delivery");
97 //ws.println("251 user not local; will forward");
99 } else if (command.toUpperCase().startsWith("HELP")) {
100 ws.println("214 sorry, you are beyond help. please see a trained professional.");
102 } else if (command.toUpperCase().startsWith("VRFY")) { // FIXME, see code 252
103 } else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available");
104 } else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK");
105 } else if (command.toUpperCase().startsWith("QUIT")) {
106 ws.println("221 " + conn.getVirtualHost() + " closing connection");
110 ws.println("500 unrecognized command");
114 return false; // FIXME: what does this mean?