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

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

/******************************************************************************
* $Workfile: SMTPSender.java $
* $Revision: 160 $
* $Author: edaugherty $
* $Date: 2003-12-23 20:29:38 -0600 (Tue, 23 Dec 2003) $
*
******************************************************************************
* 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.io.*;
import java.util.Date;
import java.util.Vector;
import java.util.List;

//Log imports
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

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

/**
* This class (thread) is responsible for checking the disk for unsent message and
* delivering them to the proper local address or remote smtp server.
* <p>
* There should be only one instance of this thread running in the system at
* any one time.
*/
public class SMTPSender implements Runnable {

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

    /** Logger */
    private static Log log = LogFactory.getLog( SMTPSender.class );

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

    private boolean running = true;

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

    /**
     * The entrypoint for this thread.  This method handles the lifecycle
     * of this thread.
     */
    public void run() {

        while( running ) {

            try {

                log.debug( "Checking for SMTP messages to deliver" );

                File smtpDirectory = new File( configurationManager.getMailDirectory() + File.separator + "smtp" );

                if( smtpDirectory.exists() && smtpDirectory.isDirectory() ) {

                    File[] files = smtpDirectory.listFiles();
                    int numFiles = files.length;

                    for( int index = 0; index < numFiles; index++ ) {
                        try {
                            deliver( SMTPMessage.load( files[index].getAbsolutePath() ) );
                        }
                        catch( Throwable throwable ) {
                            log.error( "An error occured attempting to deliver an SMTP Message: " + throwable, throwable );
                            //Do nothing else, contine on to the next message.
                        }
                    }
                }

                //Rest the specified sleep time.  If it is greater than 10 seconds
                //Wake up every 10 seconds to check to see if the thread is shutting
                //down.
                long sleepTime = configurationManager.getDeliveryIntervealMilliseconds();
                if( configurationManager.getDeliveryIntervealMilliseconds() < 10000 ) {
                    Thread.sleep( sleepTime );
                }
                else {
                    long totalSleepTime = sleepTime;
                    while( totalSleepTime > 0 && running ) {
                        if( totalSleepTime > 10000 ) {
                            totalSleepTime -= 10000;
                            Thread.sleep( 10000);
                        }
                        else {
                            Thread.sleep( totalSleepTime );
                            totalSleepTime = 0;
                        }
                    }
                }
            }
            catch( InterruptedException ie ) {
                log.error( "Sleeping Thread was interrupted." );
            }
            catch( Throwable throwable )
            {
                log.error( "An error occured attempting to deliver an SMTP Message: " + throwable, throwable );
            }
        }
        log.warn( "SMTPSender shut down gracefully.");
    }

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

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

    /**
     * This method takes a SMTPMessage and attempts to deliver it.  This
     * method assumes that all the addresses have been validated before,
     * and does not perform any delivery rules.
     */
    private void deliver( SMTPMessage message ) {

        List toAddresses = message.getToAddresses();
        int numAddress = toAddresses.size();
        Vector failedAddress = new Vector();
        EmailAddress address = null;

        // If the next scheduled delivery attempt is still in the future, skip.
        if( message.getScheduledDelivery().getTime() > System.currentTimeMillis() )
        {
            if( log.isDebugEnabled() ) log.debug( "Skipping delivery of message " + message.getMessageLocation().getName() + " because the scheduled delivery time is still in the future: " + message.getScheduledDelivery() );
            return;
        }

        for( int index = 0; index < numAddress; index++ ) {
            try {
                address = (EmailAddress) toAddresses.get( index );
                if( log.isDebugEnabled()) { log.debug( "Attempting to deliver message from: " + message.getFromAddress().getAddress() + " to: " + address ); }

                DeliveryService deliveryService = DeliveryService.getDeliveryService();

                try {
                    if( deliveryService.isLocalAddress( address ) ) {
                        deliverLocalMessage( address, message );
                    }
                    else {
                        deliverRemoteMessage( address, message );
                    }
                }
                catch (NotFoundException e) {
                    log.info( "Delivery attempted to unknown user: " + address.getAddress() );
                    //The addressee does not exist.  Notify the sender of the error.
                    bounceMessage( address, message );
                }

                if( log.isInfoEnabled() ) { log.info( "Delivery complete for message " + message.getMessageLocation().getName() + " to: " + address ); }
            }
            catch( Throwable throwable ) {
                log.error( "Delivery failed for message from: " + message.getFromAddress().getAddress() + " to: " + address + " - " + throwable, throwable );
                failedAddress.addElement( toAddresses.get( index ) );
            }
        }

        // If all addresses were successful, remove the message from the spool
        if( failedAddress.size() == 0 ) {
            // Log an error if the delete fails.  This will cause the message to get
            // delivered again, but it is too late to roll back the delivery.
            if( !message.getMessageLocation().delete() )
            {
                log.error( "Error removed SMTP message after delivery!  This message may be redelivered. " + message.getMessageLocation().getName() );
            }
        }
        // Update the message with any changes.
        else {

            message.setToAddresses( failedAddress );
            int deliveryAttempts = message.getDeliveryAttempts();


            // If the message is a bounced email, just give up and move it to the failed directory.
            if(message.getFromAddress().getUsername().equalsIgnoreCase("MAILER_DAEMON"))
            {
                try {
                    log.info( "Delivery of message from MAILER_DAEMON failed, moving to failed folder." );
                    message.moveToFailedFolder();
                }
                catch (Exception e) {
                    log.error( "Unable to move failed message to 'failed' folder." );
                }
            }
            // If we have not passed the maximum delivery count, calculate the
            // next delivery time and save the message.
            else ifdeliveryAttempts < configurationManager.getDeliveryAttemptThreshold() )
            {
                message.setDeliveryAttempts( deliveryAttempts + 1 );

                // Reschedule later, 1 min, 2 min, 4 min, 8 min, ... 2^n
                // Cap delivery interval at 2^10 minutes. (about 17 hours)
                if( deliveryAttempts > 10 )
                {
                    deliveryAttempts = 10;
                }
                long offset = (long)Math.pow( 2, deliveryAttempts);
                Date schedTime = new Date(System.currentTimeMillis() + offset*60*1000);
                message.setScheduledDelivery( schedTime );

                try {
                    message.save();
                }
                catch( Exception exception ) {
                    log.error( "Error updating spooled message for next delivery.  Message may be re-delivered.", exception );
                }
            }
            // All delivery attempts failed, bounce message.
            else
            {
                // Send a bounce message to all failed addresses.
                for( int index = 0; index < failedAddress.size(); index++ ) {
                    try {
                        EmailAddress bounce_address = (EmailAddress)( failedAddress.elementAt(index) );
                        bounceMessage(bounce_address, message);
                    }
                    catch(Exception e) {
                        log.error( "Problem bouncing message. " + message.getMessageLocation().getName() );
                    }
                }

                // Remove the original message.
                if( !message.getMessageLocation().delete() )
                {
                    log.error( "Error removed SMTP message after bounce! This message may be re-bounced. " + message.getMessageLocation().getName() );
                }
            }
        }
    }

    /**
     * This method takes a local SMTPMessage and attempts to deliver it.
     */
    private void deliverLocalMessage( EmailAddress address, SMTPMessage message )
        throws NotFoundException {

        if( log.isDebugEnabled() ) { log.debug( "Delivering Message to local user: " + address.getAddress() ); }

        User user = null;
        //Load the user.  If the user doesn't exist, a not found exception will
        //be thrown and the deliver() message will deal with the notification.
        user = configurationManager.getUser( address );
        if( user == null )
        {
            log.debug( "User not found, checking for default delivery options" );
            //Check to see if a default delivery mailbox exists, and if so, deliver it.
            //Otherwise, just throw the NotFoundException to bounce the email.
            if( configurationManager.isDefaultUserEnabled() ) {
                EmailAddress defaultAddress = configurationManager.getDefaultUser();
                //If this throws a NotFoundException, go ahead and let it bounce.
                user = configurationManager.getUser( defaultAddress );
                if( user == null ) throw new NotFoundException();
                if( log.isDebugEnabled() ) { log.info( "Delivering message addressed to: " + address + " to default user: " + defaultAddress ); }
            }
            else {
                throw new NotFoundException( "User does not exist and no default delivery options found." );
            }
        }

        //The file to write to.
        File messageFile = null;
        //The output stream to write the message to.
        BufferedWriter out = null;

        try {

            //Get the directory and create a new file.
            File userDirectory = user.getUserDirectory();
            messageFile = userDirectory.createTempFile("pop", ".jmsg", userDirectory );

            if( log.isDebugEnabled() ) { log.debug( "Delivering to: " + messageFile.getAbsolutePath() ); }

            //Open the output stream.
            out = new BufferedWriter( new FileWriter( messageFile ) );

            //Get the data to write.
            List dataLines = message.getDataLines();
            int numDataLines = dataLines.size();

            //Write the X-DeliveredTo: header
            out.write( "X-DeliveredTo: " + address.getAddress() );
            out.write( "\r\n" );

            //Write the data.
            for( int index = 0; index < numDataLines; index++ ) {
                out.write( (String) dataLines.get( index ) );
                out.write( "\r\n" );
            }
        }
        catch( IOException ioe ) {
            log.error( "Error performing local delivery.", ioe );
            if( messageFile != null ) {
                //The message was not fully written, so delete it.
                messageFile.delete();
            }
        }
        finally {
            if( out != null ) {
                try {
                    //Make sure we close up the output stream.
                    out.close();
                }
                catch( IOException ioe ) {
                    log.error( "Error closing output Stream.", ioe );
                }
            }
        }
    }

    /**
     * Handles delivery of messages to addresses not handled by this server.
     */
    private void deliverRemoteMessage( EmailAddress address, SMTPMessage message ) throws NotFoundException {

        if( log.isDebugEnabled() ) { log.debug( "Delivering Message to remote user: " + address ); }

        //Delegate this request to the SMTPRemoteSender class.
        new SMTPRemoteSender().sendMessage( address, message );
    }

    private void bounceMessage( EmailAddress address, SMTPMessage message ) {

        if( log.isInfoEnabled() ) { log.info( "Bouncing Messsage from " + message.getFromAddress().getAddress() + " to " + address.getAddress() ); }

        SMTPMessage bounceMessage = new SMTPMessage();

        //Set the from address as mailserver@ the first (default) local domain.
        EmailAddress fromAddress = new EmailAddress( "MAILER_DAEMON", configurationManager.getLocalDomains()[0] );

        bounceMessage.setFromAddress( fromAddress );
        bounceMessage.addToAddress( message.getFromAddress() );
        bounceMessage.addDataLine( "From: Mail Delivery Subsystem <MAILER-DAEMON@" + configurationManager.getLocalDomains()[0] + ">" );
        bounceMessage.addDataLine( "To: " + message.getFromAddress().getAddress() );
        bounceMessage.addDataLine( "Subject: Message Delivery Error." );
        bounceMessage.addDataLine( "Date: " + new Date().toString() ); //TODO: Improve date handling.
        bounceMessage.addDataLine( "" );
        bounceMessage.addDataLine( "Error delivering message to: " + address.getAddress() );
        bounceMessage.addDataLine( "This message will not be delivered." );
        bounceMessage.addDataLine( "" );
        bounceMessage.addDataLine( "------------------" );

        List dataLines = message.getDataLines();

        int numLines = dataLines.size();

        for( int index = 0; index <  numLines; index++ ) {
            bounceMessage.addDataLine( (String) dataLines.get( index ) );
        }
        bounceMessage.addDataLine( "" );

        //Save this message so it will be delivered.
        try {
            bounceMessage.save();
        }
        catch (Exception e) {
            log.error( "Error storing outgoing 'bounce' email message");
            throw new RuntimeException();
        }
    }
}
//EOF
TOP

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

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.