All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mailster.smtp.core.SMTPConnectionHandler Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package org.mailster.smtp.core;

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.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 <[email protected]>
 */
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;
    }

    public static void sendResponse(IoSession session, String response) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("S: " + response);
        }

        if (response != null) {
            session.write(response);
        }

        var minaCtx = (SMTPContext) session.getAttribute(CONTEXT_ATTRIBUTE);
        if (!minaCtx.getSMTPState().isActive()) {
            session.closeOnFlush();
        }
    }

    /**
     * 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) {
        var count = numberOfConnections.addAndGet(delta);
        LOG.debug("Active connections count = {}", count);
    }

    /**
     * @return The number of open connections
     */
    public int getNumberOfConnections() {
        return numberOfConnections.get();
    }

    @Override
    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());

        var minaCtx = new SMTPContext(config, factory, session);
        session.setAttribute(CONTEXT_ATTRIBUTE, minaCtx);

        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);
        }
    }

    /**
     * Session closed.
     */
    @Override
    public void sessionClosed(IoSession session) {
        updateNumberOfConnections(-1);
    }

    /**
     * Sends a response telling that the session is idle and closes it.
     */
    @Override
    public void sessionIdle(IoSession session, IdleStatus status) {
        try {
            sendResponse(session, "421 Timeout waiting for data from client.");
        } finally {
            session.closeOnFlush();
        }
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Exception occured :", cause);
        }

        var 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.");
            }
        } finally {
            if (fatal) {
                session.closeOnFlush();
            }
        }
    }

    @Override
    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;
        }

        var 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 {
            var 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
                var 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);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy