+ public static class Server {
+ public void handleRequest(Connection conn) throws IOException {
+ conn.setTimeout(5 * 60 * 1000);
+ conn.setNewline("\r\n");
+ conn.println("220 " + conn.vhost + " SMTP " + this.getClass().getName());
+ Address from = null;
+ Vector to = new Vector();
+ boolean ehlo = false;
+ String remotehost = null;
+ for(String command = conn.readln(); ; command = conn.readln()) try {
+ if (command == null) return;
+ //Log.warn("**"+conn.getRemoteAddress()+"**", command);
+ String c = command.toUpperCase();
+ if (c.startsWith("HELO")) {
+ remotehost = c.substring(5).trim();
+ conn.println("250 HELO " + conn.vhost);
+ from = null; to = new Vector();
+ } else if (c.startsWith("EHLO")) {
+ remotehost = c.substring(5).trim();
+ conn.println("250 "+conn.vhost+" greets " + remotehost);
+ ehlo = true;
+ from = null; to = new Vector();
+ } else if (c.startsWith("RSET")) { conn.println("250 reset ok"); from = null; to = new Vector();
+ } else if (c.startsWith("HELP")) { conn.println("214 you are beyond help. see a trained professional.");
+ } else if (c.startsWith("VRFY")) { conn.println("502 VRFY not supported");
+ } else if (c.startsWith("EXPN")) { conn.println("502 EXPN not supported");
+ } else if (c.startsWith("NOOP")) { conn.println("250 OK");
+ } else if (c.startsWith("QUIT")) { conn.println("221 " + conn.vhost + " closing connection"); return;
+ } else if (c.startsWith("MAIL FROM:")) {
+ command = command.substring(10).trim();
+ from = command.equals("<>") ? null : new Address(command);
+ conn.println("250 " + from + " is syntactically correct");
+ } else if (c.startsWith("RCPT TO:")) {
+ // some clients are broken and put RCPT first; we will tolerate this
+ command = command.substring(8).trim();
+ if(command.indexOf(' ') != -1) command = command.substring(0, command.indexOf(' '));
+ Address addr = new Address(command);
+ /*
+ Log.warn("**"+conn.getRemoteAddress()+"**",
+ "addr.isLocal(): " + addr.isLocal() + "\n" +
+ "conn.getRemoteAddress().isLoopbackAddress(): " + conn.getRemoteAddress().isLoopbackAddress() + "\n" +
+ "johnw: " + (from!=null&&from.toString().indexOf("johnw")!=-1) + "\n"
+ );
+ */
+ if (addr.isLocal()) {
+ // FEATURE: should check the address further and give 550 if undeliverable
+ conn.println("250 " + addr + " is on this machine; I will deliver it");
+ to.addElement(addr);
+ } else if (conn.getRemoteAddress().isLoopbackAddress() || (from!=null&&from.toString().indexOf("johnw")!=-1)) {
+ conn.println("250 you are connected locally, so I will let you send");
+ to.addElement(addr);
+ } else {
+ conn.println("551 sorry, " + addr + " is not on this machine");
+ }
+ conn.flush();
+ } else if (c.startsWith("DATA")) {
+ //if (from == null) { conn.println("503 MAIL FROM command must precede DATA"); continue; }
+ if (to == null || to.size()==0) { conn.println("503 RCPT TO command must precede DATA"); continue; }
+ if (!graylist.isWhitelisted(conn.getRemoteAddress()) && !conn.getRemoteAddress().isLoopbackAddress()) {
+ long when = graylist.getGrayListTimestamp(conn.getRemoteAddress(), from+"", to+"");
+ if (when == 0 || System.currentTimeMillis() - when > GRAYLIST_MAXWAIT) {
+ graylist.setGrayListTimestamp(conn.getRemoteAddress(), from+"", to+"", System.currentTimeMillis());
+ conn.println("451 you are graylisted; please try back in one hour to be whitelisted");
+ Log.warn(conn.getRemoteAddress().toString(), "451 you are graylisted; please try back in one hour to be whitelisted");
+ conn.flush();
+ continue;
+ } else if (System.currentTimeMillis() - when > GRAYLIST_MINWAIT) {
+ graylist.addWhitelist(conn.getRemoteAddress());
+ conn.println("354 (you have been whitelisted) Enter message, ending with \".\" on a line by itself");
+ Log.warn(conn.getRemoteAddress().toString(), "has been whitelisted");
+ } else {
+ conn.println("451 you are still graylisted (since "+new java.util.Date(when)+")");
+ conn.flush();
+ Log.warn(conn.getRemoteAddress().toString(), "451 you are still graylisted (since "+new java.util.Date(when)+")");
+ continue;
+ }
+ } else {
+ conn.println("354 Enter message, ending with \".\" on a line by itself");
+ }
+ conn.flush();
+ try {
+ StringBuffer buf = new StringBuffer();
+ buf.append("Received: from " + conn.getRemoteHostname() + " (" + remotehost + ")\r\n");
+ buf.append(" by "+conn.vhost+" ("+SMTP.class.getName()+") with "+(ehlo?"ESMTP":"SMTP") + "\r\n");
+ buf.append(" for ");
+ // FIXME: this is leaking BCC addrs
+ // for(int i=0; i<to.size(); i++) buf.append(to.elementAt(i) + " ");
+ buf.append("; " + dateFormat.format(new Date()) + "\r\n");
+ while(true) {
+ String s = conn.readln();
+ if (s == null) throw new RuntimeException("connection closed");
+ if (s.equals(".")) break;
+ if (s.startsWith(".")) s = s.substring(1);
+ buf.append(s + "\r\n");
+ if (MAX_MESSAGE_SIZE != -1 && buf.length() > MAX_MESSAGE_SIZE) {
+ Log.error("**"+conn.getRemoteAddress()+"**",
+ "sorry, this mail server only accepts messages of less than " +
+ ByteSize.toString(MAX_MESSAGE_SIZE));
+ throw new MailException.Malformed("sorry, this mail server only accepts messages of less than " +
+ ByteSize.toString(MAX_MESSAGE_SIZE));
+ }
+ }
+ String body = buf.toString();
+ Message m = null;
+ for(int i=0; i<to.size(); i++) {
+ m = Message.newMessage(new Fountain.StringFountain(body), from, (Address)to.elementAt(i));
+ accept(m);
+ }
+ if (m != null) Log.info(SMTP.class, "accepted message: " + m.summary());
+ conn.println("250 message accepted");
+ conn.flush();
+ from = null; to = new Vector();
+ } catch (Reject.RejectException re) {
+ Log.warn(SMTP.class, "rejecting message due to: " + re.reason + "\n " + re.m.summary());
+ conn.println("501 " + re.reason);
+ } catch (MailException.Malformed mfe) { conn.println("501 " + mfe.toString());
+ } catch (MailException.MailboxFull mbf) { conn.println("452 " + mbf);
+ } catch (Later.LaterException le) { conn.println("453 try again later");
+ }
+ } else { conn.println("500 unrecognized command"); }
+ } catch (Message.Malformed e) { conn.println("501 " + e.toString()); }