Fields Summary |
---|
private static final byte | COMMAND_MODEThe 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_TYPESMTP Server identification string used in SMTP headers |
private static final Random | randomStatic Random instance used to generate SMTP ids |
private static final org.apache.mailet.dates.RFC822DateFormat | rfc822DateFormatStatic RFC822DateFormat used to generate date headers |
String | curCommandNameThe name of the currently parsed command |
String | curCommandArgumentThe value of the currently parsed command |
SMTPHandlerChain | handlerChainThe SMTPHandlerChain object set by SMTPServer |
private byte | modeThe mode of the current session |
private org.apache.mailet.Mail | mailThe MailImpl object set by the DATA command |
private boolean | sessionEndedThe session termination status |
private Thread | handlerThreadThe thread executing this handler |
private Socket | socketThe TCP/IP socket over which the SMTP
dialogue is occurring. |
private InputStream | inThe incoming stream of bytes coming from the socket. |
private PrintWriter | outThe writer to which outgoing messages are written. |
private org.apache.james.util.CRLFTerminatedReader | inReaderA Reader wrapper for the incoming stream of bytes coming from the socket. |
private String | remoteHostThe remote host name obtained by lookup on the socket. |
private String | remoteIPThe remote IP address of the socket. |
private String | authenticatedUserThe user name of the authenticated user associated with this SMTP transaction. |
private boolean | authRequiredwhether or not authorization is required for this connection |
private boolean | relayingAllowedwhether or not this connection can relay without authentication |
private boolean | heloEhloEnforcementWhether the remote Server must send HELO/EHLO |
private boolean | blocklistedTEMPORARY: is the sending address blocklisted |
private String | smtpIDThe id associated with this particular SMTP interaction. |
private SMTPHandlerConfigurationData | theConfigDataThe per-service configuration data that applies to all handlers |
private HashMap | stateThe 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 | theWatchdogThe watchdog being used by this handler to deal with idle timeouts. |
private org.apache.james.util.watchdog.WatchdogTarget | theWatchdogTargetThe watchdog target that idles out this handler. |
private StringBuffer | responseBufferThe per-handler response buffer used to marshal responses. |
Methods Summary |
---|
public void | abortMessage()
mode = MESSAGE_ABORT_MODE;
|
public java.lang.String | clearResponseBuffer()
String responseString = responseBuffer.toString();
responseBuffer.delete(0,responseBuffer.length());
return responseString;
|
public void | endSession()
sessionEnded = true;
|
public java.lang.String | getCommandArgument()
return curCommandArgument;
|
public java.lang.String | getCommandName()
return curCommandName;
|
public SMTPHandlerConfigurationData | getConfigurationData()
return theConfigData;
|
public java.io.InputStream | getInputStream()
return in;
|
public org.apache.mailet.Mail | getMail()
return mail;
|
public java.lang.String | getRemoteHost()
return remoteHost;
|
public java.lang.String | getRemoteIPAddress()
return remoteIP;
|
public java.lang.StringBuffer | getResponseBuffer()
return responseBuffer;
|
public java.lang.String | getSessionID()
return smtpID;
|
public java.util.HashMap | getState()
return state;
|
public java.lang.String | getUser()
return authenticatedUser;
|
public org.apache.james.util.watchdog.Watchdog | getWatchdog()
return theWatchdog;
|
org.apache.james.util.watchdog.WatchdogTarget | getWatchdogTarget()Gets the Watchdog Target that should be used by Watchdogs managing
this connection.
return theWatchdogTarget;
|
public void | handleConnection(java.net.Socket connection)
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();
}
|
void | idleClose()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 boolean | isAuthRequired()
return authRequired;
|
public boolean | isBlockListed()
return blocklisted;
|
public boolean | isRelayingAllowed()
return relayingAllowed;
|
public boolean | isSessionEnded()
return sessionEnded;
|
private final void | logResponseString(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.
if (getLogger().isDebugEnabled()) {
getLogger().debug("Sent: " + responseString);
}
|
public final java.lang.String | 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 void | resetHandler()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 void | resetState()
ArrayList recipients = (ArrayList)state.get(RCPT_LIST);
if (recipients != null) {
recipients.clear();
}
state.clear();
|
public void | setBlockListed(boolean blocklisted)
this.blocklisted = blocklisted;
|
void | setConfigurationData(SMTPHandlerConfigurationData theData)Set the configuration data for the handler
theConfigData = theData;
|
public void | setHandlerChain(SMTPHandlerChain handlerChain)Sets the SMTPHandlerChain
this.handlerChain = handlerChain;
|
public void | setMail(org.apache.mailet.Mail mail)
this.mail = mail;
this.mode = MESSAGE_RECEIVED_MODE;
|
public void | setUser(java.lang.String userID)
authenticatedUser = userID;
|
void | setWatchdog(org.apache.james.util.watchdog.Watchdog theWatchdog)Set the Watchdog for use by this handler.
this.theWatchdog = theWatchdog;
|
public boolean | useHeloEhloEnforcement()
return heloEhloEnforcement;
|
final void | writeLoggedFlushedResponse(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.
out.println(responseString);
out.flush();
logResponseString(responseString);
|
final void | writeLoggedResponse(java.lang.String responseString)Write a response string. The response is also logged.
Used for multi-line responses.
out.println(responseString);
logResponseString(responseString);
|
public void | writeResponse(java.lang.String respString)
writeLoggedFlushedResponse(respString);
//TODO Explain this well
if(mode == COMMAND_MODE) {
mode = RESPONSE_MODE;
}
|