import java.net.*; import java.io.*; import java.util.LinkedList; import java.util.Iterator; /** * Simulates a trivial SMTP server; calls Evolution (or another program) * to enable the user to edit and send the mail. * * Call: java SMTPEvolutionAdapter [port [mailCommand]] */ public class SMTPEvolutionAdapter { // the default port where the smtp server is listening private static final int DEFAULT_PORT = 25; // the default command line for the mail program // %r is replaced by the recipients, %s by the subject, // %c by the ccs, %b by the bccs and %t by the text of the mail private static final String DEFAULT_COMMAND = "evolution mailto:%r?subject=%s&cc=%c&bcc=%b&body=%t"; private String command = null; private ServerSocket theSocket = null; /** * 'main' function. Command line arguments can be: * - the port where the adapter shall listen * - the command to start the mail program * if the command is given, the port must be given, too. */ public static void main (String[] args) { int portNr = DEFAULT_PORT; if (args.length > 0) try { portNr = Integer.parseInt(args[0]); } catch (NumberFormatException e) { System.out.println(args[0] + " is not a valid port."); System.exit(1); } String cmd = DEFAULT_COMMAND; if (args.length > 1) cmd = args[1]; try { SMTPEvolutionAdapter sea = new SMTPEvolutionAdapter(portNr, cmd); System.out.println("SMTPEvolutionAdapter listening on port " + portNr + "."); sea.listen(); } catch (IOException e) { e.printStackTrace(); System.exit(2); } } /** Simple constructor. Creates the ServerSocket. */ public SMTPEvolutionAdapter(int portNr, String cmd) throws IOException { theSocket = new ServerSocket(portNr); command = cmd; } /** * Listens for clients. Each client is handled by an instance of * MailSession in a separate thread, allowing multiple clients to * connect concurrently. */ public void listen() throws IOException { while (true) { Socket clientSocket = theSocket.accept(); (new Thread(new MailSession(clientSocket))).start(); } } /** * Handles an SMTP session with a client. See RFC 821 (SMTP) for details. * At the end of the mail session, the external mail program is called. */ class MailSession implements Runnable { // states of the session; will normally by run through in this order private static final int NOT_CONNECTED = 0; private static final int CONNECTED = 1; private static final int MAIL_CMD = 2; private static final int DATA_HEADER = 3; private static final int DATA_TEXT = 4; // command names, see RFC 821 private static final String MAIL = "MAIL FROM:"; private static final String RCPT = "RCPT TO:"; private static final String NOOP = "NOOP"; private static final String DATA = "DATA"; private static final String HELO = "HELO"; private static final String QUIT = "QUIT"; private static final String RSET = "RSET"; // important informations in the mail private LinkedList recipients = new LinkedList(); private LinkedList ccs = new LinkedList(); private LinkedList bccs = new LinkedList(); private String subject = ""; private String body = ""; private int state = NOT_CONNECTED; // state of session private Socket mySocket = null; // the client socket /** Simple Constructor */ public MailSession(Socket socket) { mySocket = socket; state = CONNECTED; } /** Resets the session, so that no mail information is kept */ private void reset() { recipients.clear(); ccs.clear(); bccs.clear(); subject = ""; body = ""; } /** * Calls the external mail program. * At least for Evolution, special characters must be masked URL-like; * though spaces must also be masked in UTF-8 (%20), not as '+'. */ private void callExternalMailer() throws IOException { // concatenate the recipients String recipientsString = ""; Iterator it = recipients.iterator(); while(it.hasNext()) { recipientsString += it.next(); if (it.hasNext()) recipientsString += ","; } // the same for ccs and bccs String ccsString = ""; it = ccs.iterator(); while(it.hasNext()) { ccsString += it.next(); if (it.hasNext()) ccsString += ","; } String bccsString = ""; it = bccs.iterator(); while(it.hasNext()) { bccsString += it.next(); if (it.hasNext()) bccsString += ","; } // encode special characters, using java.net.URLEncoder try { subject = URLEncoder.encode(subject, "UTF-8"); body = URLEncoder.encode(body, "UTF-8"); } catch (UnsupportedEncodingException e) { // won't happen e.printStackTrace(); } // replace '+' by '%20' (space). Each '+' must mean a space, // since orginal '+' chars were replaced by the URLEncoder // '+' is a special char in regexps and must be masked. The // '\' must then be masked again for the java compiler. subject = subject.replaceAll("\\+", "%20"); body = body.replaceAll("\\+", "%20"); // insert the recipients, subject and body. '%' chars in // subject or body were replaced by the URLEncoder, so no // problem there. String cmd = command.replaceAll("%r", recipientsString); cmd = cmd.replaceAll("%c", ccsString); cmd = cmd.replaceAll("%b", bccsString); cmd = cmd.replaceAll("%s", subject); cmd = cmd.replaceAll("%t", body); //System.out.println(cmd); Runtime.getRuntime().exec(cmd); } /** * Communication with client. */ public void run() { try { BufferedReader in = new BufferedReader( new InputStreamReader(mySocket.getInputStream())); PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(mySocket.getOutputStream()))); out.println("220 SMTPEvolutionAdapter ready"); // each time after print, a flush is necessary to ensure // that the client gets the message out.flush(); String line = null; while (state != NOT_CONNECTED && (line = in.readLine()) != null) { if (state == MAIL_CMD) { // control commands if (line.toUpperCase().startsWith(MAIL)) { // ignore, since 'from' isn't important here out.println("250 OK"); out.flush(); } else if (line.toUpperCase().startsWith(RCPT)) { // trim(): remove spaces at start and end recipients.addLast((line.substring( RCPT.length(), line.length())).trim()); out.println("250 OK"); out.flush(); } else if (line.toUpperCase().startsWith(DATA)) { if (!recipients.isEmpty()) { state = DATA_HEADER; out.println("354 Start mail input; end with " + "."); out.flush(); } else { out.println("503 Recipient missing"); out.flush(); } } else if (line.toUpperCase().startsWith(HELO)) { reset(); out.println("250 SMTPEvolutionAdapter"); out.flush(); } else if (line.toUpperCase().startsWith(RSET)) { reset(); out.println("250 Ok"); out.flush(); } else if (line.toUpperCase().startsWith(NOOP)) { out.println("250 Ok"); out.flush(); } else if (line.toUpperCase().startsWith(QUIT)) { out.println("221 Closing Connection"); out.flush(); in.close(); out.close(); mySocket.close(); state = NOT_CONNECTED; // breaks the loop } else { out.println("500 Syntax Error"); out.flush(); } } else if (state == CONNECTED) { if (line.toUpperCase().startsWith(HELO)) { reset(); out.println("250 SMTPEvolutionAdapter"); out.flush(); state = MAIL_CMD; } else if (line.toUpperCase().startsWith(QUIT)) { out.println("221 Closing Connection"); out.flush(); in.close(); out.close(); mySocket.close(); state = NOT_CONNECTED; // breaks the loop } else if (line.toUpperCase().startsWith(NOOP)) { out.println("250 Ok"); out.flush(); } else if (line.toUpperCase().startsWith(DATA) || line.toUpperCase().startsWith(RCPT) || line.toUpperCase().startsWith(MAIL) || line.toUpperCase().startsWith(RSET)) { out.println("503 Say hello first"); out.flush(); } else { out.println("500 Syntax Error"); out.flush(); } } // else must be DATA_XXXX else if (line.equals(".")) { // end of Data section try { callExternalMailer(); out.println("250 OK"); out.flush(); } catch (IOException e) { e.printStackTrace(); out.println("550 " + e.getMessage()); out.flush(); } reset(); state = MAIL_CMD; } else if (state == DATA_HEADER) { // the only relevant headers are 'Subject', 'CC' // and 'BCC' if (line.startsWith("Subject:")) { subject = line.substring(8, line.length()).trim(); } else if (line.toUpperCase().startsWith("CC:")) { ccs.addLast(line.substring( 3, line.length()).trim()); } else if (line.toUpperCase().startsWith("BCC:")) { bccs.addLast(line.substring( 4, line.length()).trim()); } // empty line means end of headers else if (line.trim().length() == 0) { state = DATA_TEXT; } // all other lines are ignored } else { // DATA_TEXT // remove leading points if (line.startsWith("..")) { line = line.substring(1, line.length()); } body = body + line + "\n"; } } } catch (IOException e) { e.printStackTrace(); } } } }