conflict merge, bugfixes
[org.ibex.mail.git] / src / org / ibex / mail / protocol / SMTP.java
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.*;
6 import java.net.*;
7 import java.io.*;
8 import java.util.*;
9
10 public class SMTP extends MessageProtocol {
11
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();
16         smtp.handle(sock);
17     }
18
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); }
22
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) { }
36         private void runq() {
37             /*
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]));
41             while(true) {
42                 Message next = queue.remove(true);
43                 // FIXME
44             }
45             */
46         }
47     }
48
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();
53             while(true) {
54                 int c = read();
55                 if (c == -1) throw new EOFException();
56                 if (c == '\n') return ret.toString();
57                 if (c == '\r') continue; //return ret.toString();
58                 ret.append((char)c);
59             }
60         }
61     }
62
63     private class Listener extends Incoming /*implements ServerRequest*/ {
64         //TcpConnection conn;
65         Socket conn;
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() { }
69
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()));
75
76             // FIXME
77             //ws.setNewLineString("\r\n");
78
79             ws.println("220 " + /*conn.getVirtualHost()*/ "megacz.com" + " ESMTP " + this.getClass().getName());
80             ws.flush();
81
82             Message.Address from = null;
83             Vector to = new Vector();
84             // 551 = no, i won't forward that
85             // 452 = mailbox full
86             // see 4.4 for trace info
87             while(true) {
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"));
93                     ws.flush();
94                     from = null;
95                     to = new Vector();
96
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");
101                     ws.flush();
102                     from = null;
103                     to = new Vector();
104
105                 } else if (command.toUpperCase().startsWith("RSET")) {
106                     from = null;
107                     to = new Vector();
108                     ws.println("250 reset ok");
109                     ws.flush();
110
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");
116                     ws.flush();
117
118                 } else if (command.toUpperCase().startsWith("RCPT TO:")) {
119                     if (from == null) {
120                         ws.println("503 MAIL FROM must precede RCPT TO");
121                         ws.flush();
122                         continue;
123                     }
124                     command = command.substring(9).trim();
125                     if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
126                     Message.Address addr = new Message.Address(command);
127                     to.addElement(addr);
128                     ws.println("250 " + addr + " is syntactically correct");
129                     ws.flush();
130
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");
135                     ws.flush();
136                     StringBuffer data = new StringBuffer();
137                     // move this into the Message class
138                     boolean good = false;
139                     try {
140                         good = true;
141                         Message m = new Message(rs, true);
142                         accept(m);
143                     } finally {
144                         //ws.println("251 user not local; will forward");
145                         if (good) { ws.println("250 OK message accepted for delivery"); ws.flush(); }
146                         else { /* FIXME */ }
147                     }
148                     
149                 } else if (command.toUpperCase().startsWith("HELP")) {
150                     ws.println("214 sorry, you are beyond help.  please see a trained professional.");
151                     ws.flush();
152
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");
158                     ws.flush();
159                     break;
160
161                 } else {
162                     ws.println("500 unrecognized command");
163                     ws.flush();
164                 }                    
165             
166             }
167             return false;  // FIXME: what does this mean?
168         }
169     }
170 }