1 package org.ibex.mail.protocol;
2 import org.ibex.mail.*;
3 import org.ibex.mail.store.*;
4 import org.ibex.mail.target.*;
5 import org.ibex.util.*;
10 public class SMTP extends MessageProtocol {
12 public static void main(String[] s) throws IOException {
13 SMTP smtp = new SMTP();
14 ServerSocket ss = new ServerSocket(1025);
15 Socket sock = ss.accept();
19 public SMTP() {/* setProtocolName("SMTP"); */}
20 public void handle(Socket s) throws IOException { new Listener(s).handleRequest(); }
21 //public ServerRequest createRequest(Connection conn) { return new Listener((TcpConnection)conn); }
23 public static class Outgoing {
24 // recommended retry interval is 30 minutes
25 // give up after 4-5 days
26 // should keep per-host success/failure so we don't retry on every message
27 // exponential backoff on retry time?
28 // check DNS resolvability as soon as domain is provided
29 // only use implicit A-record if there are no MX-records
30 // use null-sender for error messages (don't send errors to the null addr)
31 // to prevent mail loops, drop messages with >100 Recieved headers
32 private final org.ibex.util.Queue queue = new org.ibex.util.Queue(100);
33 public static void send(Message m) { }
34 public static void enqueue(Message m) { }
35 public static void bounce(Message m, String reason) { }
38 MessageStore store = MessageStore.root.slash("smtp").slash("outgoing");
39 int[] outgoing = store.list();
40 for(int i=0; i<outgoing.length; i++) queue.append(store.get(outgoing[i]));
42 Message next = queue.remove(true);
49 public static class LineReader extends InputStreamReader {
50 public LineReader(InputStream r) { super(r); }
51 public String readLine() throws IOException {
52 StringBuffer ret = new StringBuffer();
55 if (c == -1) throw new EOFException();
56 if (c == '\n') return ret.toString();
57 //if (c == '\r') return ret.toString();
63 private class Listener extends Incoming /*implements ServerRequest*/ {
66 //public Listener(TcpConnection conn) { this.conn = conn; conn.getSocket().setSoTimeout(5 * 60 * 1000); }
67 public Listener(Socket conn) throws IOException { this.conn = conn; conn.setSoTimeout(5 * 60 * 1000); }
68 public void init() { }
70 public boolean handleRequest() throws IOException {
71 //ReadStream rs = conn.getReadStream();
72 //WriteStream ws = conn.getWriteStream();
73 LineReader rs = new LineReader(conn.getInputStream());
74 PrintWriter ws = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
77 //ws.setNewLineString("\r\n");
79 ws.println("220 " + /*conn.getVirtualHost()*/ "megacz.com" + " ESMTP " + this.getClass().getName());
82 Message.Address from = null;
83 Vector to = new Vector();
84 // 551 = no, i won't forward that
86 // see 4.4 for trace info
88 String command = rs.readLine();
89 // FIXME: validate the HELO domain argument
90 // (double check against other end of connection? must not reject though)
91 if (command.toUpperCase().startsWith("HELO")) {
92 ws.println("250 HELO " + /*conn.getVirtualHost()*/("megacz.com"));
97 } else if (command.toUpperCase().startsWith("EHLO")) {
98 ws.println("250-" + /*conn.getVirtualHost()*/("megacz.com"));
99 ws.println("250-SIZE");
100 ws.println("250 PIPELINING");
105 } else if (command.toUpperCase().startsWith("RSET")) {
108 ws.println("250 reset ok");
111 } else if (command.toUpperCase().startsWith("MAIL FROM:")) {
112 command = command.substring(10).trim();
113 if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
114 from = new Message.Address(command);
115 ws.println("250 " + from + " is syntactically correct");
118 } else if (command.toUpperCase().startsWith("RCPT TO:")) {
120 ws.println("503 MAIL FROM must precede RCPT TO");
124 command = command.substring(10).trim();
125 if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
126 Message.Address addr = new Message.Address(command);
128 ws.println("250 " + addr + " is syntactically correct");
131 } else if (command.toUpperCase().startsWith("DATA")) {
132 if (from == null) { ws.println("503 MAIL FROM command must precede DATA"); ws.flush(); continue; }
133 if (to == null) { ws.println("503 RCPT TO command must precede DATA"); ws.flush(); continue; }
134 ws.println("354 Enter message, ending with \".\" on a line by itself");
136 StringBuffer data = new StringBuffer();
137 // move this into the Message class
138 boolean good = false;
141 Message m = new Message(rs, true);
144 //ws.println("251 user not local; will forward");
145 if (good) { ws.println("250 OK message accepted for delivery"); ws.flush(); }
149 } else if (command.toUpperCase().startsWith("HELP")) {
150 ws.println("214 sorry, you are beyond help. please see a trained professional.");
153 } else if (command.toUpperCase().startsWith("VRFY")) { // FIXME, see code 252
154 } else if (command.toUpperCase().startsWith("EXPN")) { ws.println("550 EXPN not available"); ws.flush();
155 } else if (command.toUpperCase().startsWith("NOOP")) { ws.println("250 OK"); ws.flush();
156 } else if (command.toUpperCase().startsWith("QUIT")) {
157 ws.println("221 " + /*conn.getVirtualHost()*/("megacz.com") + " closing connection");
162 ws.println("500 unrecognized command");
167 return false; // FIXME: what does this mean?