Package com.ericdaugherty.mail.server.services.smtp

Source Code of com.ericdaugherty.mail.server.services.smtp.SMTPProcessor

/******************************************************************************
* $Workfile: SMTPProcessor.java $
* $Revision: 169 $
* $Author: edaugherty $
* $Date: 2007-11-18 15:43:34 -0600 (Sun, 18 Nov 2007) $
*
******************************************************************************
* This program is a 100% Java Email Server.
******************************************************************************
* Copyright (C) 2001, Eric Daugherty
* All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
******************************************************************************
* For current versions and more information, please visit:
* http://www.ericdaugherty.com/java/mail
*
* or contact the author at:
* java@ericdaugherty.com
*
******************************************************************************
* This program is based on the CSRMail project written by Calvin Smith.
* http://crsemail.sourceforge.net/
*****************************************************************************/

package com.ericdaugherty.mail.server.services.smtp;

//Java imports
import java.net.*;
import java.io.*;
import java.util.*;

//Log imports
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

//Local imports
import com.ericdaugherty.mail.server.info.EmailAddress;
import com.ericdaugherty.mail.server.info.User;
import com.ericdaugherty.mail.server.errors.InvalidAddressException;
import com.ericdaugherty.mail.server.services.general.DeliveryService;
import com.ericdaugherty.mail.server.services.general.ConnectionProcessor;
import com.ericdaugherty.mail.server.configuration.ConfigurationManager;


/**
* Handles an incoming SMTP connection.  See rfc821 for details.
*
* @author Eric Daugherty
*/
public class SMTPProcessor implements ConnectionProcessor {

    //***************************************************************
    // Variables
    //***************************************************************

    /** Logger Category for this class. */
    private static Log log = LogFactory.getLog( SMTPMessage.class.getName() );

    /** The ConfigurationManager */
    private static ConfigurationManager configurationManager = ConfigurationManager.getInstance();

    /** Indicates if this thread should continue to run or shut down */
    private boolean running = true;

    /** The server socket used to listen for incoming connections */
    private ServerSocket serverSocket;

    /** Socket connection to the client */
    private Socket socket;

    /** The IP address of the client */
    private String clientIp;

    /** The incoming SMTP Message */
    private SMTPMessage message;

    /** Writer to sent data to the client */
    private PrintWriter out;
    /** Reader to read data from the client */
    private BufferedReader in;


    //***************************************************************
    // Public Interface
    //***************************************************************

    /**
     * Sets the socket used to communicate with the client.
     */
    public void setSocket( ServerSocket serverSocket ) {

        this.serverSocket = serverSocket;
    }


    /**
     * Entrypoint for the Thread, this method handles the interaction with
     * the client socket.
     */
    public void run() {

        try {
            //Set the socket to timeout every 10 seconds so it does not
            //just block forever.
            serverSocket.setSoTimeout( 10 * 1000 );
        }
        catch( SocketException se ) {
            log.fatal( "Error initializing Socket Timeout in SMTPProcessor" );
        }

        while( running ) {
            try {
                socket = serverSocket.accept();

                //Set the socket to timeout after 10 seconds
                socket.setSoTimeout( 10 * 1000 );

                //Prepare the input and output streams.
                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader( socket.getInputStream() ));

                InetAddress remoteAddress = socket.getInetAddress();
                clientIp = remoteAddress.getHostAddress();

        if( log.isInfoEnabled() ) { log.info( remoteAddress.getHostName() + "(" + clientIp + ") socket connected via SMTP." ); }

                write( WELCOME_MESSAGE );

                //Initialize the input message.
                message = new SMTPMessage();

                //Parses the input for commands and delegates to the appropriate methods.
                handleCommands();

            }
            catch( InterruptedIOException iioe ) {
                //This is fine, it should time out every 10 seconds if
                //a connection is not made.
            }
            //If any exception gets to here uncaught, it means we should just disconnect.
            catch( Throwable e ) {
                log.debug( "Disconnecting Exception:", e );
                log.info( "Disconnecting" );
                try {
                    write( MESSAGE_DISCONNECT );
                }
                catch( Exception e1 ) {
                    log.debug( "Error sending disconnect message.", e1 );
                    //Nothing to do.
                }
                try {
                    if( socket != null ) {
                        socket.close();
                    }
                }
                catch( IOException ioe ) {
                    log.debug( "Error disconnecting.", ioe );
                    //Nothing to do.
                }
            }
        }
        log.warn( "SMTPProcessor shut down gracefully" );
    }

    /**
     * Notifies this thread to stop processing and exit.
     */
    public void shutdown() {
        log.warn( "Shutting down SMTPProcessor." );
        running = false;
    }

    //***************************************************************
    // Private Interface
    //***************************************************************

    /**
     * Checks to make sure that the incoming command is not a quit.  If so,
     * the connection is terminated.
     */
    private void checkQuit( String command ) {

        if( command.equals( COMMAND_QUIT ) ) {
            log.debug( "User has QUIT the session." );
            throw new RuntimeException();
        }
    }

    /**
     * Handles all the commands related the the sending of mail.
     */
    private void handleCommands() {

        //Reusable Variables.
        String inputString;
        String command;
        String argument;

        int lastCommand = NONE;

        //This just runs until a SystemException is thrown, which
        //signals us to disconnect.
        while( true ) {

            inputString = read();

            command = parseCommand( inputString );
            argument = parseArgument( inputString );

            if( command.equals( COMMAND_HELO ) ) {
                write( "250 Hello " + argument );
                lastCommand = HELO;
            }
      //NOOP - Do Nothing.
      else if( command.equals( COMMAND_NOOP ) ) {
        write( MESSAGE_OK );
      }
      //Resets the state of the server back to the initial
      //state.
            else if( command.equals( COMMAND_RSET ) ) {
        message = new SMTPMessage();
        write( MESSAGE_OK );
                lastCommand = RSET;
            }
            //Not only check the command, but the full string, since the prepare command
            //method only returns the text before the first string, and this is a two
            //word command.
            else if( command.equals( COMMAND_MAIL_FROM ) && inputString.toUpperCase().startsWith( "MAIL FROM:" ) ) {

                if( lastCommand == HELO || lastCommand == NONE || lastCommand == RSET || lastCommand == EHLO) {
                    if( handleMailFrom( inputString ) ) {
                        lastCommand = MAIL_FROM;
                    }
                }
                else {
                    write( MESSAGE_COMMAND_ORDER_INVALID );
                }
            }
            //Not only check the command, but the full string, since the prepare command
            //method only returns the text before the first string, and this is a two
            //word command.
            else if( command.equals( COMMAND_RCPT_TO ) && inputString.toUpperCase().startsWith( "RCPT TO:" ) ) {

                if( lastCommand == MAIL_FROM || lastCommand == RCPT_TO ) {
                    handleRcptTo( inputString );
                    lastCommand = RCPT_TO;
                }
                else {
                    write( MESSAGE_COMMAND_ORDER_INVALID );
                }
            }
            else if( command.equals( COMMAND_DATA ) ) {

                if( lastCommand == RCPT_TO && message.getToAddresses().size() > 0 ) {
                    handleData();
                    // Reset for another message
                    message = new SMTPMessage();
                    lastCommand = RSET;
                }
                else {
                    write( MESSAGE_COMMAND_ORDER_INVALID );
                }
            }
            else {
                write( MESSAGE_INVALID_COMMAND + command );
            }
        }
    }

    /**
     * Handle the "MAIL FROM:" command, which defines the sending address for
     * this message.
     */
    private boolean handleMailFrom( String inputString ) {

        String fromAddress = parseAddress( inputString.substring( 10 ) );

        try {
            //It is legal for the MAIL FROM address to be empty.
            if( fromAddress == null || fromAddress.trim().equals( "" ) ) {
                message.setFromAddress( new EmailAddress() );
                message.setFromAddress( new EmailAddress("unknown@example.com") );
                log.debug( "MAIL FROM is empty, using unknown@example.com" );
            }
            //Although this is the normal case...
            else {
                EmailAddress address = new EmailAddress( fromAddress );
                message.setFromAddress( address );
                if( log.isDebugEnabled() ) { log.debug( "MAIL FROM: " + fromAddress ); }
            }
            write( MESSAGE_OK );
            return true;
        }
        catch( InvalidAddressException iae ) {
            log.debug( "Unable to parse From Address: " + fromAddress );
            write( MESSAGE_USER_INVALID );
            return false;
        }
    }

    /**
     * Handle the "RCPT TO:" command, which defines one of the recieving addresses.
     */
    private void handleRcptTo( String inputString ) {

        String toAddress = parseAddress( inputString.substring( 8 ) );

        try {
            EmailAddress address = new EmailAddress( toAddress );
            //Check the address to see if we can deliver it.
            DeliveryService deliveryService = DeliveryService.getDeliveryService();
            if( deliveryService.acceptAddress( address, clientIp, message.getFromAddress() ) ) {
                // Check to see if it is a local user.  If so, ask to
                // user object for the delivery addresses.
                User localUser = configurationManager.getUser( address );
                if( localUser!= null ) {
                    EmailAddress[] addresses = localUser.getDeliveryAddresses();
                    for( int index = 0; index < addresses.length; index++ ) {
                        message.addToAddress( addresses[index] );
                    }
                }
                // Otherwise, just add the address.
                else {
                    message.addToAddress( address );
                }
                write( MESSAGE_OK );
                if( log.isDebugEnabled() ) { log.debug( "RCTP TO: " + address.getAddress() + " accepted." ); }
            }
            else {
                if( log.isInfoEnabled() ) log.info( "Invalid delivery address for incoming mail: " + toAddress + " from client: " + clientIp + " / " + message.getFromAddress() );
                throw new InvalidAddressException();
            }
        }
        catch( InvalidAddressException iae ) {
            write( MESSAGE_USER_NOT_LOCAL );
            log.debug( "RCTP TO: " + toAddress + " rejected." );
            return;
        }
    }

    /**
     * Accepts the data being written to the socket.
     */
    private void handleData() {

        // Get the current maxSize setting and convert to bytes.
        long maxSize = configurationManager.getMaximumMessageSize() * 1024 * 1024;

        write( MESSAGE_SEND_DATA );

        //Add a datestamp to the message to track when the message arrived.
        message.addDataLine( "X-RecievedDate: " + new Date() );
        //Add a line to the message to track that the message when through this server.
        message.addDataLine( "Received: by EricDaugherty's JES SMTP " + configurationManager.getLocalDomains()[0] + " from client: " + clientIp  );

        try {
            String inputString = in.readLine();

            while( !inputString.equals( "." ) ) {
                if( log.isDebugEnabled() ) { log.debug( "Read Data: " + inputString ); }
                message.addDataLine( inputString );
                inputString = in.readLine();

                // Check message size
                if( message.getSize() > maxSize )
                {
                    log.warn( "Message Rejected.  Message larger than max allowed size (" + configurationManager.getMaximumMessageSize() + " MB)" );
                    write( MESSAGE_MESSAGE_TOO_LARGE );
                    throw new RuntimeException( "Aborting Connection.  Message size too large." );
                }
            }
            log.debug( "Data Input Complete." );
        }
        catch( IOException ioe ) {
            log.error( "An error occured while retrieving the message data.", ioe );
            throw new RuntimeException();
        }

        //Write the message to disk.

        try {
            message.save();
            write( MESSAGE_OK );
        }
        catch ( Exception se ) {
            write( MESSAGE_SAVE_MESSAGE_ERROR );
            throw new RuntimeException( se.getMessage() );
        }

        if( log.isInfoEnabled() ) log.info( "Message " + message.getMessageLocation().getName() + " accepted for delivery." );
    }

    /**
     * Reads a line from the input stream and returns it.
     */
    private String read() {
        try {
            String inputLine = in.readLine().trim();
            if( log.isDebugEnabled() ) { log.debug( "Read Input: " + inputLine ); }
            return inputLine;
        }
        catch( IOException ioe ) {
            log.error( "Error reading from socket.", ioe );
            throw new RuntimeException();
        }
    }

    /**
     * Writes the specified output message to the client.
     */
    private void write( String message ) {

    if( log.isDebugEnabled() ) { log.debug( "Writing: " + message ); }
        out.print( message + "\r\n" );
        out.flush();
    }

    /**
     * Parses the input stream for the command.  The command is the
     * begining of the input stream to the first space.  If there is
     * space found, the entire input string is returned.
     * <p>
     * This method converts the returned command to uppercase to allow
     * for easier comparison.
     * <p>
     * Additinally, this method checks to verify that the quit command
     * was not issued.  If it was, a SystemException is thrown to terminate
     * the connection.
     */
    private String parseCommand( String inputString ) {

        int index = inputString.indexOf( " " );

        if( index == -1 ) {
            String command = inputString.toUpperCase();
            checkQuit( command );
            return command;
        }
        else {
            String command = inputString.substring( 0, index ).toUpperCase();
            checkQuit( command );
            return command;
        }
    }

    /**
     * Parses the input stream for the argument.  The argument is the
     * text starting afer the first space until the end of the inputstring.
     * If there is no space found, an empty string is returned.
     * <p>
     * This method does not convert the case of the argument.
     */
    private String parseArgument( String inputString ) {

        int index = inputString.indexOf( " " );

        if( index == -1 ) {
            return "";
        }
        else {
            return inputString.substring( index + 1 );
        }
    }

    /**
     * Parses an address argument into a real email address.  This
     * method strips off any &gt; or &lt; symbols.
     */
    private String parseAddress( String address ) {

        int index = address.indexOf( "<" );
        if( index != -1 ) {
            address = address.substring( index + 1 );
        }
        index = address.indexOf( ">" );
        if( index != -1 ) {
            address = address.substring( 0, index );
        }
        return address;
    }

    //***************************************************************
    // Constants
    //***************************************************************

    //Message Constants
    //General Message
    private static final String WELCOME_MESSAGE = "220 Welcome to EricDaugherty's Java SMTP Server.";
    private static final String MESSAGE_DISCONNECT = "221 SMTP server signing off.";
    private static final String MESSAGE_OK = "250 OK";
    private static final String MESSAGE_COMMAND_ORDER_INVALID = "503 Command not allowed here.";
    private static final String MESSAGE_USER_NOT_LOCAL = "550 User does not exist.";
    private static final String MESSAGE_USER_INVALID = "451 Address is invalid.";
    private static final String MESSAGE_SEND_DATA = "354 Start mail input; end with <CRLF>.<CRLF>";
    private static final String MESSAGE_SAVE_MESSAGE_ERROR = "500 Error handling message.";
    private static final String MESSAGE_INVALID_COMMAND = "500 Command Unrecognized: ";
    private static final String MESSAGE_MESSAGE_TOO_LARGE = "552 Message size exceeds fixed maximum message size.";

    //Commands
    private static final String COMMAND_HELO = "HELO";
    private static final String COMMAND_RSET = "RSET";
    private static final String COMMAND_NOOP = "NOOP";
    private static final String COMMAND_QUIT = "QUIT";
    private static final String COMMAND_MAIL_FROM = "MAIL";
    private static final String COMMAND_RCPT_TO = "RCPT";
    private static final String COMMAND_DATA = "DATA";

  //SMTP Commands
  public int NONE = 0;
    public int HELO = 1;
    public int QUIT = 2;
    public int MAIL_FROM = 3;
    public int RCPT_TO = 4;
    public int DATA = 5;
    public int DATA_FINISHED = 6;
  public int RSET = 7;
  public int EHLO = 8;
}
TOP

Related Classes of com.ericdaugherty.mail.server.services.smtp.SMTPProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.