FileDocCategorySizeDatePackage
SMTPHandler.javaAPI DocApache James 2.3.125611Fri Jan 12 12:56:26 GMT 2007org.apache.james.smtpserver

SMTPHandler

public class SMTPHandler extends org.apache.avalon.framework.logger.AbstractLogEnabled implements org.apache.avalon.cornerstone.services.connection.ConnectionHandler, SMTPSession, org.apache.avalon.excalibur.pool.Poolable
Provides SMTP functionality by carrying out the server side of the SMTP interaction.
version
CVS $Revision: 494012 $ $Date: 2007-01-08 11:23:58 +0100 (Mo, 08 Jan 2007) $

Fields Summary
private static final byte
COMMAND_MODE
The constants to indicate the current processing mode of the session
private static final byte
RESPONSE_MODE
private static final byte
MESSAGE_RECEIVED_MODE
private static final byte
MESSAGE_ABORT_MODE
private static final String
SOFTWARE_TYPE
SMTP Server identification string used in SMTP headers
private static final Random
random
Static Random instance used to generate SMTP ids
private static final org.apache.mailet.dates.RFC822DateFormat
rfc822DateFormat
Static RFC822DateFormat used to generate date headers
String
curCommandName
The name of the currently parsed command
String
curCommandArgument
The value of the currently parsed command
SMTPHandlerChain
handlerChain
The SMTPHandlerChain object set by SMTPServer
private byte
mode
The mode of the current session
private org.apache.mailet.Mail
mail
The MailImpl object set by the DATA command
private boolean
sessionEnded
The session termination status
private Thread
handlerThread
The thread executing this handler
private Socket
socket
The TCP/IP socket over which the SMTP dialogue is occurring.
private InputStream
in
The incoming stream of bytes coming from the socket.
private PrintWriter
out
The writer to which outgoing messages are written.
private org.apache.james.util.CRLFTerminatedReader
inReader
A Reader wrapper for the incoming stream of bytes coming from the socket.
private String
remoteHost
The remote host name obtained by lookup on the socket.
private String
remoteIP
The remote IP address of the socket.
private String
authenticatedUser
The user name of the authenticated user associated with this SMTP transaction.
private boolean
authRequired
whether or not authorization is required for this connection
private boolean
relayingAllowed
whether or not this connection can relay without authentication
private boolean
heloEhloEnforcement
Whether the remote Server must send HELO/EHLO
private boolean
blocklisted
TEMPORARY: is the sending address blocklisted
private String
smtpID
The id associated with this particular SMTP interaction.
private SMTPHandlerConfigurationData
theConfigData
The per-service configuration data that applies to all handlers
private HashMap
state
The hash map that holds variables for the SMTP message transfer in progress. This hash map should only be used to store variable set in a particular set of sequential MAIL-RCPT-DATA commands, as described in RFC 2821. Per connection values should be stored as member variables in this class.
private org.apache.james.util.watchdog.Watchdog
theWatchdog
The watchdog being used by this handler to deal with idle timeouts.
private org.apache.james.util.watchdog.WatchdogTarget
theWatchdogTarget
The watchdog target that idles out this handler.
private StringBuffer
responseBuffer
The per-handler response buffer used to marshal responses.
Constructors Summary
Methods Summary
public voidabortMessage()

see
org.apache.james.smtpserver.SMTPSession#abortMessage()

        mode = MESSAGE_ABORT_MODE;
    
public java.lang.StringclearResponseBuffer()

see
org.apache.james.smtpserver.SMTPSession#clearResponseBuffer()

        String responseString = responseBuffer.toString();
        responseBuffer.delete(0,responseBuffer.length());
        return responseString;
    
public voidendSession()

see
org.apache.james.smtpserver.SMTPSession#endSession()

        sessionEnded = true;
    
public java.lang.StringgetCommandArgument()

see
org.apache.james.smtpserver.SMTPSession#getCommandArgument()

        return curCommandArgument;
    
public java.lang.StringgetCommandName()

see
org.apache.james.smtpserver.SMTPSession#getCommandName()

        return curCommandName;
    
public SMTPHandlerConfigurationDatagetConfigurationData()

see
org.apache.james.smtpserver.SMTPSession#getConfigurationData()

        return theConfigData;
    
public java.io.InputStreamgetInputStream()

see
org.apache.james.smtpserver.SMTPSession#getInputStream()

        return in;
    
public org.apache.mailet.MailgetMail()

see
org.apache.james.smtpserver.SMTPSession#getMail()

        return mail;
    
public java.lang.StringgetRemoteHost()

see
org.apache.james.smtpserver.SMTPSession#getRemoteHost()

        return remoteHost;
    
public java.lang.StringgetRemoteIPAddress()

see
org.apache.james.smtpserver.SMTPSession#getRemoteIPAddress()

        return remoteIP;
    
public java.lang.StringBuffergetResponseBuffer()

see
org.apache.james.smtpserver.SMTPSession#getResponseBuffer()

        return responseBuffer;
    
public java.lang.StringgetSessionID()

see
org.apache.james.smtpserver.SMTPSession#getSessionID()

        return smtpID;
    
public java.util.HashMapgetState()

see
org.apache.james.smtpserver.SMTPSession#getState()

        return state;
    
public java.lang.StringgetUser()

see
org.apache.james.smtpserver.SMTPSession#getUser()

        return authenticatedUser;
    
public org.apache.james.util.watchdog.WatchdoggetWatchdog()

see
org.apache.james.smtpserver.SMTPSession#getWatchdog()

        return theWatchdog;
    
org.apache.james.util.watchdog.WatchdogTargetgetWatchdogTarget()
Gets the Watchdog Target that should be used by Watchdogs managing this connection.

return
the WatchdogTarget

        return theWatchdogTarget;
    
public voidhandleConnection(java.net.Socket connection)

see
org.apache.avalon.cornerstone.services.connection.ConnectionHandler#handleConnection(Socket)


        try {
            this.socket = connection;
            synchronized (this) {
                handlerThread = Thread.currentThread();
            }
            in = new BufferedInputStream(socket.getInputStream(), 1024);
            // An ASCII encoding can be used because all transmissions other
            // that those in the DATA command are guaranteed
            // to be ASCII
            // inReader = new BufferedReader(new InputStreamReader(in, "ASCII"), 512);
            inReader = new CRLFTerminatedReader(in, "ASCII");
            remoteIP = socket.getInetAddress().getHostAddress();
            remoteHost = socket.getInetAddress().getHostName();
            smtpID = random.nextInt(1024) + "";
            relayingAllowed = theConfigData.isRelayingAllowed(remoteIP);
            authRequired = theConfigData.isAuthRequired(remoteIP);
            heloEhloEnforcement = theConfigData.useHeloEhloEnforcement();
            sessionEnded = false;
            resetState();
        } catch (Exception e) {
            StringBuffer exceptionBuffer =
                new StringBuffer(256)
                    .append("Cannot open connection from ")
                    .append(remoteHost)
                    .append(" (")
                    .append(remoteIP)
                    .append("): ")
                    .append(e.getMessage());
            String exceptionString = exceptionBuffer.toString();
            getLogger().error(exceptionString, e );
            throw new RuntimeException(exceptionString);
        }

        if (getLogger().isInfoEnabled()) {
            StringBuffer infoBuffer =
                new StringBuffer(128)
                        .append("Connection from ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(")");
            getLogger().info(infoBuffer.toString());
        }

        try {

            out = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()), 1024), false);

            // Initially greet the connector
            // Format is:  Sat, 24 Jan 1998 13:16:09 -0500

            responseBuffer.append("220 ")
                          .append(theConfigData.getHelloName())
                          .append(" SMTP Server (")
                          .append(SOFTWARE_TYPE)
                          .append(") ready ")
                          .append(rfc822DateFormat.format(new Date()));
            String responseString = clearResponseBuffer();
            writeLoggedFlushedResponse(responseString);

            //the core in-protocol handling logic
            //run all the connection handlers, if it fast fails, end the session
            //parse the command command, look up for the list of command handlers
            //Execute each of the command handlers. If any command handlers writes
            //response then, End the subsequent command handler processing and
            //start parsing new command. Once the message is received, run all
            //the message handlers. The message handlers can either terminate
            //message or terminate session

            //At the beginning
            //mode = command_mode
            //once the commandHandler writes response, the mode is changed to RESPONSE_MODE.
            //This will cause to skip the subsequent command handlers configured for that command.
            //For instance:
            //There are 2 commandhandlers MailAddressChecker and MailCmdHandler for
            //MAIL command. If MailAddressChecker validation of the MAIL FROM
            //address is successful, the MailCmdHandlers will be executed.
            //Incase it fails, it has to write response. Once we write response
            //there is no need to execute the MailCmdHandler.
            //Next, Once MAIL message is received the DataCmdHandler and any other
            //equivalent commandHandler will call setMail method. this will change
            //he mode to MAIL_RECEIVED_MODE. This mode will trigger the message
            //handlers to be execute. Message handlers can abort message. In that case,
            //message will not spooled.

            //Session started - RUN all connect handlers
            List connectHandlers = handlerChain.getConnectHandlers();
            if(connectHandlers != null) {
                int count = connectHandlers.size();
                for(int i = 0; i < count; i++) {
                    ((ConnectHandler)connectHandlers.get(i)).onConnect(this);
                    if(sessionEnded) {
                        break;
                    }
                }
            }

            theWatchdog.start();
            while(!sessionEnded) {
              //Reset the current command values
              curCommandName = null;
              curCommandArgument = null;
              mode = COMMAND_MODE;

              //parse the command
              String cmdString =  readCommandLine();
              if (cmdString == null) {
                  break;
              }
              int spaceIndex = cmdString.indexOf(" ");
              if (spaceIndex > 0) {
                  curCommandName = cmdString.substring(0, spaceIndex);
                  curCommandArgument = cmdString.substring(spaceIndex + 1);
              } else {
                  curCommandName = cmdString;
              }
              curCommandName = curCommandName.toUpperCase(Locale.US);

              //fetch the command handlers registered to the command
              List commandHandlers = handlerChain.getCommandHandlers(curCommandName);
              if(commandHandlers == null) {
                  //end the session
                  break;
              } else {
                  int count = commandHandlers.size();
                  for(int i = 0; i < count; i++) {
                      ((CommandHandler)commandHandlers.get(i)).onCommand(this);
                      theWatchdog.reset();
                      //if the response is received, stop processing of command handlers
                      if(mode != COMMAND_MODE) {
                          break;
                      }
                  }

              }

              //handle messages
              if(mode == MESSAGE_RECEIVED_MODE) {
                  try {
                      getLogger().debug("executing message handlers");
                      List messageHandlers = handlerChain.getMessageHandlers();
                      int count = messageHandlers.size();
                      for(int i =0; i < count; i++) {
                          ((MessageHandler)messageHandlers.get(i)).onMessage(this);
                          //if the response is received, stop processing of command handlers
                          if(mode == MESSAGE_ABORT_MODE) {
                              break;
                          }
                      }
                  } finally {
                      //do the clean up
                      if(mail != null) {
                          if (mail instanceof Disposable) {
                              ((Disposable) mail).dispose();
                          }
                  
                          // remember the ehlo mode
                          Object currentHeloMode = state.get(CURRENT_HELO_MODE);
                  
                          mail = null;
                          resetState();

                          // start again with the old helo mode
                          if (currentHeloMode != null) {
                              state.put(CURRENT_HELO_MODE,currentHeloMode);
                          }
                      }
                  }
              }
            }
            theWatchdog.stop();
            getLogger().debug("Closing socket.");
        } catch (SocketException se) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer errorBuffer =
                    new StringBuffer(64)
                        .append("Socket to ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(") closed remotely.");
                getLogger().error(errorBuffer.toString(), se );
            }
        } catch ( InterruptedIOException iioe ) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer errorBuffer =
                    new StringBuffer(64)
                        .append("Socket to ")
                        .append(remoteHost)
                        .append(" (")
                        .append(remoteIP)
                        .append(") timeout.");
                getLogger().error( errorBuffer.toString(), iioe );
            }
        } catch ( IOException ioe ) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer errorBuffer =
                    new StringBuffer(256)
                            .append("Exception handling socket to ")
                            .append(remoteHost)
                            .append(" (")
                            .append(remoteIP)
                            .append(") : ")
                            .append(ioe.getMessage());
                getLogger().error( errorBuffer.toString(), ioe );
            }
        } catch (Exception e) {
            if (getLogger().isErrorEnabled()) {
                getLogger().error( "Exception opening socket: "
                                   + e.getMessage(), e );
            }
        } finally {
            //Clear all the session state variables
            resetHandler();
        }
    
voididleClose()
Idle out this connection

        if (getLogger() != null) {
            getLogger().error("SMTP Connection has idled out.");
        }
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (Exception e) {
            // ignored
        }

        synchronized (this) {
            // Interrupt the thread to recover from internal hangs
            if (handlerThread != null) {
                handlerThread.interrupt();
            }
        }
    
public booleanisAuthRequired()

see
org.apache.james.smtpserver.SMTPSession#isAuthRequired()

        return authRequired;
    
public booleanisBlockListed()

see
org.apache.james.smtpserver.SMTPSession#isBlockListed()

        return blocklisted;
    
public booleanisRelayingAllowed()

see
org.apache.james.smtpserver.SMTPSession#isRelayingAllowed()

        return relayingAllowed;
    
public booleanisSessionEnded()

see
org.apache.james.smtpserver.SMTPSession#isSessionEnded()

        return sessionEnded;
    
private final voidlogResponseString(java.lang.String responseString)
This method logs at a "DEBUG" level the response string that was sent to the SMTP client. The method is provided largely as syntactic sugar to neaten up the code base. It is declared private and final to encourage compiler inlining.

param
responseString the response string sent to the client

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Sent: " + responseString);
        }
    
public final java.lang.StringreadCommandLine()

see
org.apache.james.smtpserver.SMTPSession#readCommandLine()

        for (;;) try {
            String commandLine = inReader.readLine();
            if (commandLine != null) {
                commandLine = commandLine.trim();
            }
            return commandLine;
        } catch (CRLFTerminatedReader.TerminationException te) {
            writeLoggedFlushedResponse("501 Syntax error at character position " + te.position() + ". CR and LF must be CRLF paired.  See RFC 2821 #2.7.1.");
        } catch (CRLFTerminatedReader.LineLengthExceededException llee) {
            writeLoggedFlushedResponse("500 Line length exceeded. See RFC 2821 #4.5.3.1.");
        }
    
private voidresetHandler()
Resets the handler data to a basic state.

        resetState();

        clearResponseBuffer();
        in = null;
        inReader = null;
        out = null;
        remoteHost = null;
        remoteIP = null;
        authenticatedUser = null;
        smtpID = null;

        if (theWatchdog != null) {
            ContainerUtil.dispose(theWatchdog);
            theWatchdog = null;
        }

        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            if (getLogger().isErrorEnabled()) {
                getLogger().error("Exception closing socket: "
                                  + e.getMessage());
            }
        } finally {
            socket = null;
        }

        synchronized (this) {
            handlerThread = null;
        }

    
public voidresetState()

see
org.apache.james.smtpserver.SMTPSession#resetState()

        ArrayList recipients = (ArrayList)state.get(RCPT_LIST);
        if (recipients != null) {
            recipients.clear();
        }
        state.clear();
    
public voidsetBlockListed(boolean blocklisted)

see
org.apache.james.smtpserver.SMTPSession#setBlockListed(boolean)

        this.blocklisted = blocklisted;
    
voidsetConfigurationData(SMTPHandlerConfigurationData theData)
Set the configuration data for the handler

param
theData the per-service configuration data for this handler


                         
       
        theConfigData = theData;
    
public voidsetHandlerChain(SMTPHandlerChain handlerChain)
Sets the SMTPHandlerChain

param
handlerChain SMTPHandler object

        this.handlerChain = handlerChain;
    
public voidsetMail(org.apache.mailet.Mail mail)

see
org.apache.james.smtpserver.SMTPSession#setMail(Mail)

        this.mail = mail;
        this.mode = MESSAGE_RECEIVED_MODE;
    
public voidsetUser(java.lang.String userID)

see
org.apache.james.smtpserver.SMTPSession#setUser()

        authenticatedUser = userID;
    
voidsetWatchdog(org.apache.james.util.watchdog.Watchdog theWatchdog)
Set the Watchdog for use by this handler.

param
theWatchdog the watchdog

        this.theWatchdog = theWatchdog;
    
public booleanuseHeloEhloEnforcement()

see
org.apache.james.smtpserver.SMTPSession#useHeloEhloEnforcement()

        return heloEhloEnforcement;
    
final voidwriteLoggedFlushedResponse(java.lang.String responseString)
Write and flush a response string. The response is also logged. Should be used for the last line of a multi-line response or for a single line response.

param
responseString the response string sent to the client

        out.println(responseString);
        out.flush();
        logResponseString(responseString);
    
final voidwriteLoggedResponse(java.lang.String responseString)
Write a response string. The response is also logged. Used for multi-line responses.

param
responseString the response string sent to the client

        out.println(responseString);
        logResponseString(responseString);
    
public voidwriteResponse(java.lang.String respString)

see
org.apache.james.smtpserver.SMTPSession#writeResponse(String)

        writeLoggedFlushedResponse(respString);
        //TODO Explain this well
        if(mode == COMMAND_MODE) {
            mode = RESPONSE_MODE;
        }