dumbster/ 0000755 0001750 0001750 00000000000 11614775125 012606 5 ustar thinker thinker dumbster/src/ 0000755 0001750 0001750 00000000000 10142251036 013356 5 ustar thinker thinker dumbster/src/com/ 0000755 0001750 0001750 00000000000 10142251036 014134 5 ustar thinker thinker dumbster/src/com/dumbster/ 0000755 0001750 0001750 00000000000 10142251036 015761 5 ustar thinker thinker dumbster/src/com/dumbster/smtp/ 0000755 0001750 0001750 00000000000 10220226124 016741 5 ustar thinker thinker dumbster/src/com/dumbster/smtp/SimpleSmtpServer.java 0000644 0001750 0001750 00000017222 10220227122 023073 0 ustar thinker thinker /* * Dumbster - a dummy SMTP server * Copyright 2004 Jason Paul Kitchen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dumbster.smtp; import java.net.ServerSocket; import java.net.Socket; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.IOException; /** * Dummy SMTP server for testing purposes. * * @todo constructor allowing user to pass preinitialized ServerSocket */ public class SimpleSmtpServer implements Runnable { /** * Stores all of the email received since this instance started up. */ private List receivedMail; /** * Default SMTP port is 25. */ public static final int DEFAULT_SMTP_PORT = 25; /** * Indicates whether this server is stopped or not. */ private volatile boolean stopped = true; /** * Handle to the server socket this server listens to. */ private ServerSocket serverSocket; /** * Port the server listens on - set to the default SMTP port initially. */ private int port = DEFAULT_SMTP_PORT; /** * Timeout listening on server socket. */ private static final int TIMEOUT = 500; /** * Constructor. * @param port port number */ public SimpleSmtpServer(int port) { receivedMail = new ArrayList(); this.port = port; } /** * Main loop of the SMTP server. */ public void run() { stopped = false; try { try { serverSocket = new ServerSocket(port); serverSocket.setSoTimeout(TIMEOUT); // Block for maximum of 1.5 seconds } finally { synchronized (this) { // Notify when server socket has been created notifyAll(); } } // Server: loop until stopped while (!isStopped()) { // Start server socket and listen for client connections Socket socket = null; try { socket = serverSocket.accept(); } catch (Exception e) { if (socket != null) { socket.close(); } continue; // Non-blocking socket timeout occurred: try accept() again } // Get the input and output streams BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream()); synchronized (this) { /* * We synchronize over the handle method and the list update because the client call completes inside * the handle method and we have to prevent the client from reading the list until we've updated it. * For higher concurrency, we could just change handle to return void and update the list inside the method * to limit the duration that we hold the lock. */ List msgs = handleTransaction(out, input); receivedMail.addAll(msgs); } socket.close(); } } catch (Exception e) { /** @todo Should throw an appropriate exception here. */ e.printStackTrace(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * Check if the server has been placed in a stopped state. Allows another thread to * stop the server safely. * @return true if the server has been sent a stop signal, false otherwise */ public synchronized boolean isStopped() { return stopped; } /** * Stops the server. Server is shutdown after processing of the current request is complete. */ public synchronized void stop() { // Mark us closed stopped = true; try { // Kick the server accept loop serverSocket.close(); } catch (IOException e) { // Ignore } } /** * Handle an SMTP transaction, i.e. all activity between initial connect and QUIT command. * * @param out output stream * @param input input stream * @return List of SmtpMessage * @throws IOException */ private List handleTransaction(PrintWriter out, BufferedReader input) throws IOException { // Initialize the state machine SmtpState smtpState = SmtpState.CONNECT; SmtpRequest smtpRequest = new SmtpRequest(SmtpActionType.CONNECT, "", smtpState); // Execute the connection request SmtpResponse smtpResponse = smtpRequest.execute(); // Send initial response sendResponse(out, smtpResponse); smtpState = smtpResponse.getNextState(); List msgList = new ArrayList(); SmtpMessage msg = new SmtpMessage(); while (smtpState != SmtpState.CONNECT) { String line = input.readLine(); if (line == null) { break; } // Create request from client input and current state SmtpRequest request = SmtpRequest.createRequest(line, smtpState); // Execute request and create response object SmtpResponse response = request.execute(); // Move to next internal state smtpState = response.getNextState(); // Send reponse to client sendResponse(out, response); // Store input in message String params = request.getParams(); msg.store(response, params); // If message reception is complete save it if (smtpState == SmtpState.QUIT) { msgList.add(msg); msg = new SmtpMessage(); } } return msgList; } /** * Send response to client. * @param out socket output stream * @param smtpResponse response object */ private static void sendResponse(PrintWriter out, SmtpResponse smtpResponse) { if (smtpResponse.getCode() > 0) { int code = smtpResponse.getCode(); String message = smtpResponse.getMessage(); out.print(code + " " + message + "\r\n"); out.flush(); } } /** * Get email received by this instance since start up. * @return List of String */ public synchronized Iterator getReceivedEmail() { return receivedMail.iterator(); } /** * Get the number of messages received. * @return size of received email list */ public synchronized int getReceivedEmailSize() { return receivedMail.size(); } /** * Creates an instance of SimpleSmtpServer and starts it. Will listen on the default port. * @return a reference to the SMTP server */ public static SimpleSmtpServer start() { return start(DEFAULT_SMTP_PORT); } /** * Creates an instance of SimpleSmtpServer and starts it. * @param port port number the server should listen to * @return a reference to the SMTP server */ public static SimpleSmtpServer start(int port) { SimpleSmtpServer server = new SimpleSmtpServer(port); Thread t = new Thread(server); t.start(); // Block until the server socket is created synchronized (server) { try { server.wait(); } catch (InterruptedException e) { // Ignore don't care. } } return server; } } dumbster/src/com/dumbster/smtp/SmtpActionType.java 0000644 0001750 0001750 00000012235 10176500446 022546 0 ustar thinker thinker /* * Dumbster - a dummy SMTP server * Copyright 2004 Jason Paul Kitchen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dumbster.smtp; /** * Represents an SMTP action or command. */ public class SmtpActionType { /** Internal value for the action type. */ private byte value; /** Internal representation of the CONNECT action. */ private static final byte CONNECT_BYTE = (byte) 1; /** Internal representation of the EHLO action. */ private static final byte EHLO_BYTE = (byte) 2; /** Internal representation of the MAIL FROM action. */ private static final byte MAIL_BYTE = (byte) 3; /** Internal representation of the RCPT action. */ private static final byte RCPT_BYTE = (byte) 4; /** Internal representation of the DATA action. */ private static final byte DATA_BYTE = (byte) 5; /** Internal representation of the DATA END (.) action. */ private static final byte DATA_END_BYTE = (byte) 6; /** Internal representation of the QUIT action. */ private static final byte QUIT_BYTE = (byte) 7; /** Internal representation of an unrecognized action: body text gets this action type. */ private static final byte UNREC_BYTE = (byte) 8; /** Internal representation of the blank line action: separates headers and body text. */ private static final byte BLANK_LINE_BYTE = (byte) 9; /** Internal representation of the stateless RSET action. */ private static final byte RSET_BYTE = (byte) -1; /** Internal representation of the stateless VRFY action. */ private static final byte VRFY_BYTE = (byte) -2; /** Internal representation of the stateless EXPN action. */ private static final byte EXPN_BYTE = (byte) -3; /** Internal representation of the stateless HELP action. */ private static final byte HELP_BYTE = (byte) -4; /** Internal representation of the stateless NOOP action. */ private static final byte NOOP_BYTE = (byte) -5; /** CONNECT action. */ public static final SmtpActionType CONNECT = new SmtpActionType(CONNECT_BYTE); /** EHLO action. */ public static final SmtpActionType EHLO = new SmtpActionType(EHLO_BYTE); /** MAIL action. */ public static final SmtpActionType MAIL = new SmtpActionType(MAIL_BYTE); /** RCPT action. */ public static final SmtpActionType RCPT = new SmtpActionType(RCPT_BYTE); /** DATA action. */ public static final SmtpActionType DATA = new SmtpActionType(DATA_BYTE); /** "." action. */ public static final SmtpActionType DATA_END = new SmtpActionType(DATA_END_BYTE); /** Body text action. */ public static final SmtpActionType UNRECOG = new SmtpActionType(UNREC_BYTE); /** QUIT action. */ public static final SmtpActionType QUIT = new SmtpActionType(QUIT_BYTE); /** Header/body separator action. */ public static final SmtpActionType BLANK_LINE = new SmtpActionType(BLANK_LINE_BYTE); /** Stateless RSET action. */ public static final SmtpActionType RSET = new SmtpActionType(RSET_BYTE); /** Stateless VRFY action. */ public static final SmtpActionType VRFY = new SmtpActionType(VRFY_BYTE); /** Stateless EXPN action. */ public static final SmtpActionType EXPN = new SmtpActionType(EXPN_BYTE); /** Stateless HELP action. */ public static final SmtpActionType HELP = new SmtpActionType(HELP_BYTE); /** Stateless NOOP action. */ public static final SmtpActionType NOOP = new SmtpActionType(NOOP_BYTE); /** * Create a new SMTP action type. Private to ensure no invalid values. * @param value one of the _BYTE values */ private SmtpActionType(byte value) { this.value = value; } /** * Indicates whether the action is stateless or not. * @return true iff the action is stateless */ public boolean isStateless() { return value < 0; } /** * String representation of this SMTP action type. * @return a String */ public String toString() { switch(value) { case CONNECT_BYTE: return "Connect"; case EHLO_BYTE: return "EHLO"; case MAIL_BYTE: return "MAIL"; case RCPT_BYTE: return "RCPT"; case DATA_BYTE: return "DATA"; case DATA_END_BYTE: return "."; case QUIT_BYTE: return "QUIT"; case RSET_BYTE: return "RSET"; case VRFY_BYTE: return "VRFY"; case EXPN_BYTE: return "EXPN"; case HELP_BYTE: return "HELP"; case NOOP_BYTE: return "NOOP"; case UNREC_BYTE: return "Unrecognized command / data"; case BLANK_LINE_BYTE: return "Blank line"; default: return "Unknown"; } } } dumbster/src/com/dumbster/smtp/SmtpMessage.java 0000644 0001750 0001750 00000010153 10176501060 022041 0 ustar thinker thinker /* * Dumbster - a dummy SMTP server * Copyright 2004 Jason Paul Kitchen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dumbster.smtp; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ArrayList; import java.util.Set; /** * Container for a complete SMTP message - headers and message body. */ public class SmtpMessage { /** Headers: Map of List of String hashed on header name. */ private Map headers; /** Message body. */ private StringBuffer body; /** * Constructor. Initializes headers Map and body buffer. */ public SmtpMessage() { headers = new HashMap(10); body = new StringBuffer(); } /** * Update the headers or body depending on the SmtpResponse object and line of input. * @param response SmtpResponse object * @param params remainder of input line after SMTP command has been removed */ public void store(SmtpResponse response, String params) { if (params != null) { if (SmtpState.DATA_HDR.equals(response.getNextState())) { int headerNameEnd = params.indexOf(':'); if (headerNameEnd >= 0) { String name = params.substring(0, headerNameEnd).trim(); String value = params.substring(headerNameEnd+1).trim(); addHeader(name, value); } } else if (SmtpState.DATA_BODY == response.getNextState()) { body.append(params); } } } /** * Get an Iterator over the header names. * @return an Iterator over the set of header names (String) */ public Iterator getHeaderNames() { Set nameSet = headers.keySet(); return nameSet.iterator(); } /** * Get the value(s) associated with the given header name. * @param name header name * @return value(s) associated with the header name */ public String[] getHeaderValues(String name) { List values = (List)headers.get(name); if (values == null) { return new String[0]; } else { return (String[])values.toArray(new String[0]); } } /** * Get the first values associated with a given header name. * @param name header name * @return first value associated with the header name */ public String getHeaderValue(String name) { List values = (List)headers.get(name); if (values == null) { return null; } else { Iterator iterator = values.iterator(); return (String)iterator.next(); } } /** * Get the message body. * @return message body */ public String getBody() { return body.toString(); } /** * Adds a header to the Map. * @param name header name * @param value header value */ private void addHeader(String name, String value) { List valueList = (List)headers.get(name); if (valueList == null) { valueList = new ArrayList(1); headers.put(name, valueList); } valueList.add(value); } /** * String representation of the SmtpMessage. * @return a String */ public String toString() { StringBuffer msg = new StringBuffer(); for(Iterator i = headers.keySet().iterator(); i.hasNext();) { String name = (String)i.next(); List values = (List)headers.get(name); for(Iterator j = values.iterator(); j.hasNext();) { String value = (String)j.next(); msg.append(name); msg.append(": "); msg.append(value); msg.append('\n'); } } msg.append('\n'); msg.append(body); msg.append('\n'); return msg.toString(); } } dumbster/src/com/dumbster/smtp/SmtpRequest.java 0000644 0001750 0001750 00000023302 10146233704 022111 0 ustar thinker thinker /* * Dumbster - a dummy SMTP server * Copyright 2004 Jason Paul Kitchen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.dumbster.smtp; /** * Contains an SMTP client request. Handles state transitions using the following state transition table. *
* -----------+------------------------------------------------------------------------------------------------- * | State * Action +-------------+-----------+-----------+--------------+---------------+---------------+------------ * | CONNECT | GREET | MAIL | RCPT | DATA_HDR | DATA_BODY | QUIT * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------ * connect | 220/GREET | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * ehlo | 503/CONNECT | 250/MAIL | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * mail | 503/CONNECT | 503/GREET | 250/RCPT | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/RCPT * rcpt | 503/CONNECT | 503/GREET | 503/MAIL | 250/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * data | 503/CONNECT | 503/GREET | 503/MAIL | 354/DATA_HDR | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * data_end | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 250/QUIT | 250/QUIT | 503/QUIT * unrecog | 500/CONNECT | 500/GREET | 500/MAIL | 500/RCPT | ---/DATA_HDR | ---/DATA_BODY | 500/QUIT * quit | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/CONNECT * blank_line | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT * rset | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET * vrfy | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT * expn | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT * help | 211/CONNECT | 211/GREET | 211/MAIL | 211/RCPT | 211/DATA_HDR | 211/DATA_BODY | 211/QUIT * noop | 250/CONNECT | 250/GREET | 250/MAIL | 250/RCPT | 250|DATA_HDR | 250/DATA_BODY | 250/QUIT **/ public class SmtpRequest { /** SMTP action received from client. */ private SmtpActionType action; /** Current state of the SMTP state table. */ private SmtpState state; /** Additional information passed from the client with the SMTP action. */ private String params; /** * Create a new SMTP client request. * @param actionType type of action/command * @param params remainder of command line once command is removed * @param state current SMTP server state */ public SmtpRequest(SmtpActionType actionType, String params, SmtpState state) { this.action = actionType; this.state = state; this.params = params; } /** * Execute the SMTP request returning a response. This method models the state transition table for the SMTP server. * @return reponse to the request */ public SmtpResponse execute() { SmtpResponse response = null; if (action.isStateless()) { if (SmtpActionType.EXPN == action || SmtpActionType.VRFY == action) { response = new SmtpResponse(252, "Not supported", this.state); } else if (SmtpActionType.HELP == action) { response = new SmtpResponse(211, "No help available", this.state); } else if (SmtpActionType.NOOP == action) { response = new SmtpResponse(250, "OK", this.state); } else if (SmtpActionType.VRFY == action) { response = new SmtpResponse(252, "Not supported", this.state); } else if (SmtpActionType.RSET == action) { response = new SmtpResponse(250, "OK", SmtpState.GREET); } else { response = new SmtpResponse(500, "Command not recognized", this.state); } } else { // Stateful commands if (SmtpActionType.CONNECT == action) { if (SmtpState.CONNECT == state) { response = new SmtpResponse(220, "localhost Dumbster SMTP service ready", SmtpState.GREET); } else { response = new SmtpResponse(503, "Bad sequence of commands: "+action, this.state); } } else if (SmtpActionType.EHLO == action) { if (SmtpState.GREET == state) { response = new SmtpResponse(250, "OK", SmtpState.MAIL); } else { response = new SmtpResponse(503, "Bad sequence of commands: "+action, this.state); } } else if (SmtpActionType.MAIL == action) { if (SmtpState.MAIL == state || SmtpState.QUIT == state) { response = new SmtpResponse(250, "OK", SmtpState.RCPT); } else { response = new SmtpResponse(503, "Bad sequence of commands: "+action, this.state); } } else if (SmtpActionType.RCPT == action) { if (SmtpState.RCPT == state) { response = new SmtpResponse(250, "OK", this.state); } else { response = new SmtpResponse(503, "Bad sequence of commands: "+action, this.state); } } else if (SmtpActionType.DATA == action) { if (SmtpState.RCPT == state) { response = new SmtpResponse(354, "Start mail input; end with