package org.mailster.smtp.core; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.atomic.AtomicInteger; import org.apache.mina.core.buffer.BufferDataException; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.filter.ssl.SslFilter.SslFilterMessage; import org.apache.mina.transport.socket.SocketSessionConfig; import org.mailster.smtp.SMTPServerConfig; import org.mailster.smtp.api.handler.DeliveryHandlerFactory; import org.mailster.smtp.core.commands.Command; import org.mailster.smtp.core.commands.CommandException; import org.mailster.smtp.core.commands.CommandHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The IoHandler that handles a connection. This class * passes most of it's responsibilities off to the * CommandHandler. * * @author De Oliveira Edouard <doe_wanted@yahoo.fr> */ public class SMTPConnectionHandler extends IoHandlerAdapter { // Session objects public static final String CONTEXT_ATTRIBUTE = SMTPConnectionHandler.class.getName() + ".ctx"; private static final Logger LOG = LoggerFactory.getLogger(SMTPConnectionHandler.class); private SMTPServerConfig config; private CommandHandler commandHandler; private DeliveryHandlerFactory factory; /** * A thread safe variable that represents the number * of active connections. */ private AtomicInteger numberOfConnections = new AtomicInteger(0); public SMTPConnectionHandler(SMTPServerConfig cfg, CommandHandler handler, DeliveryHandlerFactory factory) { this.config = cfg; this.commandHandler = handler; this.factory = factory; } /** * Are we over the maximum amount of connections ? */ private boolean hasTooManyConnections() { return (config.getMaxConnections() > -1 && getNumberOfConnections() >= config.getMaxConnections()); } /** * Update the number of active connections. */ private void updateNumberOfConnections(int delta) { int count = numberOfConnections.addAndGet(delta); LOG.debug("Active connections count = {}", count); } /** * @return The number of open connections */ public int getNumberOfConnections() { return numberOfConnections.get(); } /** */ public void sessionCreated(IoSession session) { updateNumberOfConnections(+1); if (session.getTransportMetadata().getSessionConfigType() == SocketSessionConfig.class) { ((SocketSessionConfig)session.getConfig()).setReceiveBufferSize(config.getReceiveBufferSize()); ((SocketSessionConfig)session.getConfig()).setSendBufferSize(64); } session.getConfig().setIdleTime(IdleStatus.READER_IDLE, config.getConnectionTimeout() / 1000); // We're going to use SSL negotiation notification. session.setAttribute(SslFilter.USE_NOTIFICATION); // Init protocol internals LOG.debug("SMTP connection count: {}", getNumberOfConnections()); SMTPContext minaCtx = new SMTPContext(config, factory, session); session.setAttribute(CONTEXT_ATTRIBUTE, minaCtx); try { if (hasTooManyConnections()) { LOG.debug("Too many connections to the SMTP server !"); sendResponse(session, "554 Transaction failed. Too many connections."); } else sendResponse(session, "220 " + config.getHostName() + " ESMTP " + SMTPServerConfig.NAME); } catch (IOException e1) { try { sendResponse(session, "450 Problem when connecting. Please try again later."); } catch (IOException e) {} if (LOG.isDebugEnabled()) LOG.debug("Error on session creation", e1); session.close(false); } } /** * Session closed. */ public void sessionClosed(IoSession session) throws Exception { updateNumberOfConnections(-1); } /** * Sends a response telling that the session is idle and closes it. */ public void sessionIdle(IoSession session, IdleStatus status) { try { sendResponse(session, "421 Timeout waiting for data from client."); } catch (IOException ioex) { } finally { session.close(false); } } /** */ public void exceptionCaught(IoSession session, Throwable cause) { if (LOG.isDebugEnabled()) LOG.debug("Exception occured :", cause); boolean fatal = true; try { if (cause instanceof BufferDataException) { sendResponse(session, "501 " + cause.getMessage()); } else if (cause instanceof CommandException) { fatal = false; sendResponse(session, "500 Syntax error"); } else { // primarily if things fail during the MessageListener.deliver(), then try // to send a temporary failure back so that the server will try to resend // the message later. sendResponse(session, "450 Problem attempting to execute commands. Please try again later."); } } catch (IOException e) { } finally { if (fatal) session.close(false); } } /** */ public void messageReceived(IoSession session, Object message) throws Exception { if (message == null) { if (LOG.isDebugEnabled()) LOG.debug("no more lines from client"); return; } if (message instanceof SslFilterMessage) { if (LOG.isDebugEnabled()) LOG.debug("SSL FILTER message -> " + message); return; } SMTPContext minaCtx = (SMTPContext) session.getAttribute(CONTEXT_ATTRIBUTE); if (message instanceof InputStream) { minaCtx.setInputStream((InputStream) message); try { minaCtx.getDeliveryHandler().data(minaCtx.getInputStream()); minaCtx.reset(); sendResponse(session, "250 Ok"); } catch (TooMuchDataException tmdEx) { sendResponse(session, "552 Too much mail data"); } } else { String line = (String) message; if (LOG.isDebugEnabled()) LOG.debug("C: " + line); if (minaCtx.getSMTPState().isAuthenticating()) this.commandHandler.handleAuthChallenge(line, session, minaCtx); else if (!minaCtx.getSMTPState().isAuthenticated() && !minaCtx.getAuthenticationHandler().getAuthenticationMechanisms().isEmpty()) { // Per RFC 2554 Command cmd = this.commandHandler.getCommandFromString(line); if (cmd.isAuthRequired()) sendResponse(session, "530 Authentication required"); else this.commandHandler.handleCommand(line, session, minaCtx); } else this.commandHandler.handleCommand(line, session, minaCtx); } } /** */ public static void sendResponse(IoSession session, String response) throws IOException { if (LOG.isDebugEnabled()) LOG.debug("S: " + response); if (response != null) session.write(response); SMTPContext minaCtx = (SMTPContext) session.getAttribute(CONTEXT_ATTRIBUTE); if (!minaCtx.getSMTPState().isActive()) session.close(false); } }