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

org.subethamail.smtp.server.Session Maven / Gradle / Ivy

There is a newer version: 3.1.7
Show newest version
package org.subethamail.smtp.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.*;
import java.security.cert.Certificate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.AuthenticationHandler;
import org.subethamail.smtp.MessageContext;
import org.subethamail.smtp.MessageHandler;
import org.subethamail.smtp.io.CRLFTerminatedReader;

/**
 * The thread that handles a connection. This class
 * passes most of it's responsibilities off to the
 * CommandHandler.
 *
 * @author Jon Stevens
 * @author Jeff Schnitzer
 */
public class Session extends Thread implements MessageContext
{
	private final static Logger log = LoggerFactory.getLogger(Session.class);

	/** A link to our parent server */
	private SMTPServer server;

	/** Set this true when doing an ordered shutdown */
	private boolean quitting = false;

	/** I/O to the client */
	private Socket socket;
	private InputStream input;
	private CRLFTerminatedReader reader;
	private PrintWriter writer;

	/** Might exist if the client has successfully authenticated */
	private AuthenticationHandler authenticationHandler;

	/** Might exist if the client is giving us a message */
	private MessageHandler messageHandler;

	/** Some state information */
	private String helo;
	private boolean hasMailFrom;
	private int recipientCount;

	/**
	 * If the client told us the size of the message, this is the value.
	 * If they didn't, the value will be 0.
	 */
	private int declaredMessageSize = 0;

	/** Some more state information */
	private boolean tlsStarted;
	private Certificate[] tlsPeerCertificates;

	/**
	 * Creates (but does not start) the thread object.
	 *
	 * @param server a link to our parent
	 * @param socket is the socket to the client
	 * @throws IOException
	 */
	public Session(SMTPServer server, Socket socket)
		throws IOException
	{
		super(server.getSessionGroup(), Session.class.getName()
				+ "-" + socket.getInetAddress() + ":" + socket.getPort());

		this.server = server;

		this.setSocket(socket);
	}

	/**
	 * @return a reference to the master server object
	 */
	public SMTPServer getServer()
	{
		return this.server;
	}

	/**
	 * The thread for each session runs on this and shuts down when the shutdown member goes true.
	 */
	@Override
	public void run()
	{
		if (log.isDebugEnabled())
		{
			InetAddress remoteInetAddress = this.getRemoteAddress().getAddress();
			remoteInetAddress.getHostName();	// Causes future toString() to print the name too

			log.debug("SMTP connection from {}, new connection count: {}", remoteInetAddress, this.server.getNumberOfConnections());
		}

		try
		{
			if (this.server.hasTooManyConnections())
			{
				log.debug("SMTP Too many connections!");

				this.sendResponse("421 Too many connections, try again later");
				return;
			}

			this.sendResponse("220 " + this.server.getHostName() + " ESMTP " + this.server.getName());

			// Start with fresh message state
			this.resetMessageState();

			while (!this.quitting)
			{
				try
				{
					String line = null;
					try
					{
						line = this.reader.readLine();
					}
					catch (SocketException ex)
					{
						// Lots of clients just "hang up" rather than issuing QUIT, which would
						// fill our logs with the warning in the outer catch.
						if (log.isDebugEnabled())
							log.debug("Error reading client command: " + ex.getMessage(), ex);

						return;
					}

					if (line == null)
					{
						log.debug("no more lines from client");
						return;
					}

					if (log.isDebugEnabled())
						log.debug("Client: " + line);

					this.server.getCommandHandler().handleCommand(this, line);
				}
				catch (SocketTimeoutException ex)
				{
					this.sendResponse("421 Timeout waiting for data from client.");
					return;
				}
				catch (CRLFTerminatedReader.TerminationException te)
				{
					String msg = "501 Syntax error at character position "
						+ te.position()
						+ ". CR and LF must be CRLF paired.  See RFC 2821 #2.7.1.";

					log.debug(msg);
					this.sendResponse(msg);

					// if people are screwing with things, close connection
					return;
				}
				catch (CRLFTerminatedReader.MaxLineLengthException mlle)
				{
					String msg = "501 " + mlle.getMessage();

					log.debug(msg);
					this.sendResponse(msg);

					// if people are screwing with things, close connection
					return;
				}
			}
		}
		catch (IOException e1)
		{
			if (!this.quitting)
			{
				try
				{
					// Send a temporary failure back so that the server will try to resend
					// the message later.
					this.sendResponse("450 Problem attempting to execute commands. Please try again later.");
				}
				catch (IOException e) {}

				if (log.isWarnEnabled())
					log.warn("Exception during SMTP transaction", e1);
			}
		}
		finally
		{
			this.closeConnection();
			this.endMessageHandler();
		}
	}

	/**
	 * Close reader, writer, and socket, logging exceptions but otherwise ignoring them
	 */
	private void closeConnection()
	{
		try
		{
			try
			{
				this.writer.close();
				this.input.close();
			}
			finally
			{
				this.closeSocket();
			}
		}
		catch (IOException e)
		{
			log.info(e.toString());
		}
	}

	/**
	 * Initializes our reader, writer, and the i/o filter chains based on
	 * the specified socket.  This is called internally when we startup
	 * and when (if) SSL is started.
	 */
	public void setSocket(Socket socket) throws IOException
	{
		this.socket = socket;
		this.input = this.socket.getInputStream();
		this.reader = new CRLFTerminatedReader(this.input);
		this.writer = new PrintWriter(this.socket.getOutputStream());

		this.socket.setSoTimeout(this.server.getConnectionTimeout());
	}

	/**
	 * This method is only used by the start tls command
	 * @return the current socket to the client
	 */
	public Socket getSocket()
	{
		return this.socket;
	}

	/** Close the client socket if it is open */
	public void closeSocket() throws IOException
	{
		if ((this.socket != null) && this.socket.isBound() && !this.socket.isClosed())
			this.socket.close();
	}

	/**
	 * @return the raw input stream from the client
	 */
	public InputStream getRawInput()
	{
		return this.input;
	}

	/**
	 * @return the cooked CRLF-terminated reader from the client
	 */
	public CRLFTerminatedReader getReader()
	{
		return this.reader;
	}

	/** Sends the response to the client */
	public void sendResponse(String response) throws IOException
	{
		if (log.isDebugEnabled())
			log.debug("Server: " + response);

		this.writer.print(response + "\r\n");
		this.writer.flush();
	}

	/* (non-Javadoc)
	 * @see org.subethamail.smtp.MessageContext#getRemoteAddress()
	 */
	public InetSocketAddress getRemoteAddress()
	{
		return (InetSocketAddress)this.socket.getRemoteSocketAddress();
	}

	/* (non-Javadoc)
	 * @see org.subethamail.smtp.MessageContext#getSMTPServer()
	 */
	public SMTPServer getSMTPServer()
	{
		return this.server;
	}

	/**
	 * @return the current message handler
	 */
	public MessageHandler getMessageHandler()
	{
		return this.messageHandler;
	}

	/** Simple state */
	public String getHelo()
	{
		return this.helo;
	}

	/** */
	public void setHelo(String value)
	{
		this.helo = value;
	}

	/** */
	public boolean getHasMailFrom()
	{
		return this.hasMailFrom;
	}

	/** */
	public void setHasMailFrom(boolean value)
	{
		this.hasMailFrom = value;
	}

	/** */
	public void addRecipient()
	{
		this.recipientCount++;
	}

	/** */
	public int getRecipientCount()
	{
		return this.recipientCount;
	}

	/** */
	public boolean isAuthenticated()
	{
		return this.authenticationHandler != null;
	}

	/** */
	public AuthenticationHandler getAuthenticationHandler()
	{
		return this.authenticationHandler;
	}

	/**
	 * This is called by the AuthCommand when a session is successfully authenticated.  The
	 * handler will be an object created by the AuthenticationHandlerFactory.
	 */
	public void setAuthenticationHandler(AuthenticationHandler handler)
	{
		this.authenticationHandler = handler;
	}

	/**
	 * @return the maxMessageSize
	 */
	public int getDeclaredMessageSize()
	{
		return this.declaredMessageSize;
	}

	/**
	 * @param declaredMessageSize the size that the client says the message will be
	 */
	public void setDeclaredMessageSize(int declaredMessageSize)
	{
		this.declaredMessageSize = declaredMessageSize;
	}

	/**
	 * Some state is associated with each particular message (senders, recipients, the message handler).
	 * Some state is not; seeing hello, TLS, authentication.
	 */
	public void resetMessageState()
	{
		this.endMessageHandler();
		this.messageHandler = this.server.getMessageHandlerFactory().create(this);
		this.helo = null;
		this.hasMailFrom = false;
		this.recipientCount = 0;
		this.declaredMessageSize = 0;
	}

	/** Safely calls done() on a message hander, if one exists */
	protected void endMessageHandler()
	{
		if (this.messageHandler != null)
		{
			try
			{
				this.messageHandler.done();
			}
			catch (Exception ex)
			{
				log.error("done() threw exception", ex);
			}
		}
	}

	/**
	 * Triggers the shutdown of the thread and the closing of the connection.
	 */
	public void quit()
	{
		this.quitting = true;
		this.closeConnection();
	}

	/**
	 * @return true when the TLS handshake was completed, false otherwise
	 */
	public boolean isTLSStarted()
	{
		return tlsStarted;
	}

	/**
	 * @param tlsStarted true when the TLS handshake was completed, false otherwise
	 */
	public void setTlsStarted(boolean tlsStarted)
	{
		this.tlsStarted = tlsStarted;
	}

	public void setTlsPeerCertificates(Certificate[] tlsPeerCertificates)
	{
		this.tlsPeerCertificates = tlsPeerCertificates;
	}

	/**
	 * {@inheritDoc}
	 */
	public Certificate[] getTlsPeerCertificates()
	{
		return tlsPeerCertificates;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy