ProtocolHandler.java :  » Web-Mail » jsmtpd » org » jsmtpd » core » receive » Java Open Source

Java Open Source » Web Mail » jsmtpd 
jsmtpd » org » jsmtpd » core » receive » ProtocolHandler.java
/*
 * 
 * Jsmtpd, Java SMTP daemon
 * Copyright (C) 2005  Jean-Francois POUX, jf.poux@laposte.net
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
package org.jsmtpd.core.receive;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsmtpd.config.ReadConfig;
import org.jsmtpd.core.common.PluginStore;
import org.jsmtpd.core.common.acl.IACL;
import org.jsmtpd.core.common.io.BareLFException;
import org.jsmtpd.core.common.io.InputSizeToBig;
import org.jsmtpd.core.common.io.InvalidStreamParserInitialisation;
import org.jsmtpd.core.common.io.commandStream.CommandStreamParser;
import org.jsmtpd.core.common.io.dataStream.DataStreamParser;
import org.jsmtpd.core.common.smtpExtension.IProtocolHandler;
import org.jsmtpd.core.common.smtpExtension.ISmtpExtension;
import org.jsmtpd.core.common.smtpExtension.SmtpExtensionException;
import org.jsmtpd.core.mail.Email;
import org.jsmtpd.core.mail.EmailAddress;
import org.jsmtpd.core.mail.InvalidAddress;
import org.jsmtpd.core.mail.Rcpt;
import org.jsmtpd.core.send.QueueService;
import org.jsmtpd.tools.DateUtil;

/**
 * @author Jean-Francois POUX
 * Jsmtp<BR>
 * 02/04/2005<br>
 * Problems with some mailing list managers, when trying to suscribe, so :<br>
 * Increased limit of user size to 512o<br>
 * Increased limit of domain size to 512o<br>
 * 
 * 
 * 16/03/2005 <br>
 * Fixed a bugg that could cause the server to crash by consuming all its memory
 * while parsing random ascii injection. (removed buffered reader) <br>
 * Check for MAX RCPT and adresses sizes.
 * 
 * 
 * Chat with smtp sender
 * Checks acl
 * Write message to delivery service
 * 
 * <br>
 * RFC 821 :
 *   user :     64 byte => add to emailaddress parser  [done]
 *  domain :   64 byte => add to emailaddress parser  [done]
 *  route :    256 byte, route NA in this implem => err 501 
 *  command:  512 byte max => err 500 [done]
 *  response:  512 byte max OK
 *   dataline:  1000 max no problem
 *   max rcpt:  100 => err 552s [done]
 * <br><br>
 * 7/3/2005<br>
 * Changed Email class, so changed the way to handle DATA cmd<br>
 * raw byte reading<br>
 * 
 */
public class ProtocolHandler implements IProtocolHandler {

    /**
     * The tcp socket where the exchange takes place
     */
    private Socket sock = null;

    /**
     * Writer, from the socket
     */
    private BufferedWriter wr = null;
    /**
     * Email instance to store data, rcpt & from mainly
     */
    private Email mail = new Email();

    /**
     * Maximum message size, in ko.
     */
    private int maxMessageSize = ReadConfig.getInstance().getMaxMessageSize();
    /**
     * Connection will fail after this timeout
     */
    private int timeout = ReadConfig.getInstance().getConnectionTimeout();
    /**
     * ACL pluggin to use to check if this mail is to be accepted or not
     */
    private IACL acl = PluginStore.getInstance().getAcl();

    private Log log = LogFactory.getLog(ProtocolHandler.class);
    /**
     * Remote ip of the client connected to the smtp session
     */
    private String remote = "";
    /**
     * Our hostname
     */
    private String localHost = ReadConfig.getInstance().getLocalDomain();

    /**
     * Once a mail is received and validated, it is placed in this service
     * that will deliver it
     */
    private QueueService dsvc = QueueService.getInstance();

    /**
     * relay remote host ?
     */
    private boolean relayed=false;
    
    /**
     * is communication layer secured by any means ?
     */
    private boolean secured=false;
    
    private CommandStreamParser csp;
    
    private InputIPFilterChecker checker = new InputIPFilterChecker();
    
    private List<ISmtpExtension> smtpExtensions=PluginStore.getInstance().getSmtpExtensions();
    
    private List<String> commandHistory;
    /**
     * Listening thread queries the thread pool to receive an instance of this class, 
     * then passes it the socket resulting from the accept method
     * This methods invoques the chat protocol with the client.
     * @param sock the socket to chat with the connected client
     */
    
    private String authContext = null;
    
    private int maxRcpt =ReadConfig.getInstance().getMaxRcpt();
    
    public void init(Socket sock) {
        commandHistory=new LinkedList<String>();
        remote = ((InetSocketAddress) sock.getRemoteSocketAddress()).getAddress().getHostAddress(); // get client hostname

        //Ensure any previous state is cleared
        closeConnection();
        reset();
        this.sock = sock;
        
        try {
            sock.setSoTimeout(timeout * 1000); // Timeout comes from the config file
            wr = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
            // Run the chat with the client
            runDialog();
        } catch (IOException e) {
            log.error("Network error for " + remote);
        } catch (EndofProtocol e) {
            log.info("End service for " + remote);
        } finally {
            closeConnection();
            reset();
        }
    }

    /**
     * Chats with the client
     * @throws IOException Network error
     * @throws EndofProtocol Client is disconnecting
     */
    private void runDialog() throws IOException, EndofProtocol {
        // Count errors, if it exceeds a threshold, the connection is droped because someone
        // is probably trying to do something not allowed
        int errors = 0;
        // The last command issued
        int lastCommand = -1;
        // Current command
        int command;

        if (!checker.checkIPAgainstFilters(sock.getInetAddress())) {
            send(MSG_BLACKLISTED);
            return;
        }

        log.info( "Service for " + remote + " running");
        send(MSG_HELLO_CODE + localHost + MSG_HELLO_MESG); // Send the : HELO serverHostname.com Welcome to jsmtpd
        mail.setReceivedFrom(remote); // Email type records where the connection came from (for filtering later)
        
        csp = new CommandStreamParser(sock.getInputStream(), 4096, true);
        String commandString;
        try {
            while ((commandString = csp.readLine()) != null)
            {
                command = getCommand(commandString);
                log.debug("Command: " + commandString);
                try {
                    if (executePreExtensions(commandString,this))
                        continue;
                } catch (SmtpExtensionException e1) {
                   log.error("Failed to execute smtp extension");
                   break;
                } 
                switch (command) {
                case CMD_EHLO:
                    if (smtpExtensions.size()<=0) {
                        send("250 Command ok");
                    } else {
                        send("250-ok");
                        for (ISmtpExtension ext : smtpExtensions) {
                            if ( (ext.getWelcome()!=null) && (!ext.getWelcome().equals("")))
                                send ("250-"+ext.getWelcome());
                        }
                       send ("250 HELP");
                    }
                    lastCommand = CMD_HELLO;
                    break;
                case CMD_HELLO:
                    send("250 Command ok");
                    lastCommand = CMD_HELLO;
                    break;
                case CMD_QUIT:
                    lastCommand = CMD_RESET;
                    throw new EndofProtocol();
                case CMD_NOOP:
                    send(MSG_OK);
                    break;
                case CMD_RESET:
                    mail = new Email();
                    mail.setReceivedFrom(remote);
                    mail.setAuthContext(authContext); // If we have a auth context, transmit it;
                    send(MSG_OK);
                    lastCommand = CMD_RESET;
                    break;
                case CMD_MAIL_FROM:
                    if (lastCommand == CMD_RESET || lastCommand == CMD_HELLO) {
                        if (decodeFrom(commandString))
                            lastCommand = CMD_MAIL_FROM;
                    } else
                        send(MSG_CMD_NOT_ALLOWED);
                    break;

                case CMD_RCPT:
                    if (lastCommand == CMD_MAIL_FROM || lastCommand == CMD_RCPT) {
                        if (decodeRcpt(commandString))
                            lastCommand = CMD_RCPT;
                    } else {
                        send(MSG_CMD_NOT_ALLOWED);
                    }
                    break;
                    
                case CMD_HELP:
                        send ("214 See rfc 2821");
                        lastCommand=CMD_HELP;
                    break;
                    
                case CMD_DATA:
                    if (lastCommand == CMD_RCPT) {
                        parseData();
                        mail = new Email();
                        mail.setReceivedFrom(remote);
                        mail.setAuthContext(authContext); // transmit auth ctx
                        lastCommand = CMD_RESET;
                    } else {
                        send(MSG_CMD_NOT_ALLOWED);
                    }
                    break;
                    
                default:
                    try {
                            if (!executeExtensions(commandString,this)) {
                                send(MSG_INVALID_CMD);
                                log.error("Invalid command: " + commandString + " from " + remote);
                                errors++;
                                if (errors > 10)
                                    return;
                                break;
                            }
                        } catch (IOException e) {
                            throw new EndofProtocol();
                        } catch (SmtpExtensionException e) {
                            log.error("Error executing SMTP Extensions");
                            errors++;
                        } 
                }
            }

        } catch (InputSizeToBig e) {
            send(MSG_CMD_TO_BIG);
            return;
        } catch (BareLFException e) {
            send(MSG_ERROR_LF);
            return;
        }
    }

    private boolean executeExtensions (String cmd, IProtocolHandler protocol) throws SmtpExtensionException, IOException, InputSizeToBig, BareLFException {
      for (ISmtpExtension extension : smtpExtensions) {
        if (extension.smtpTrigger(cmd,protocol))
                return true;
    }
        return false;
    }
    
    private boolean executePreExtensions (String cmd, IProtocolHandler protocol) throws SmtpExtensionException, IOException, InputSizeToBig, BareLFException {
        boolean over=false;
        for (ISmtpExtension extension : smtpExtensions) {
            if (extension.smtpPreTrigger(cmd,protocol))
                over=true;
        }
        return over;
    }
    
    private void parseData() throws IOException {
        send(MSG_DATA_BEGIN);
        mail.setArrival(new Date());

        try {
            DataStreamParser dsp = new DataStreamParser(1024 * 512, 1024 * 1024 * maxMessageSize);
            List<Rcpt> rcpts = mail.getRcpt();
            String rc = "";
            for (Iterator iter = rcpts.iterator(); iter.hasNext();) {
                Rcpt element = (Rcpt) iter.next();
                rc += "<" + element.getEmailAddress().toString() + ">;";
            }
            dsp.appendString("Received: from " + remote + " by " + localHost + " (Jsmtpd) for " + rc + " " + DateUtil.currentRFCDate());

            try {
                dsp.parseInputStream(sock.getInputStream());
                mail.setDataBuffer(dsp.getData());
                if (!dsvc.queueMail(mail))
                    send(MSG_NO_SPACE_LEFT);
                else
                    send(MSG_OK);
            } catch (InputSizeToBig e1) {
                send(MSG_ERROR_SIZE);
            } catch (IOException e1) {
                throw e1;
            } catch (BareLFException e1) {
                send(MSG_ERROR_LF);
            }

        } catch (InvalidStreamParserInitialisation e) {
            send(MSG_SAVE_ERROR);
        }

    }

    private boolean decodeRcpt(String rcpt) throws IOException {
        if ((rcpt == null) || (rcpt.length() < 10))
            return false;

        String rc = rcpt.substring(9).trim().replace("<", "").replace(">", "");
        EmailAddress e = null;
        try {
            e = EmailAddress.parseAddress(rc);

        } catch (InvalidAddress e1) {
            send(MSG_USER_INVALID);
            return false;
        }
        if (isAcceptable(e)) {
            // Todo : check user ?
            if (mail.getRcpt().size() >= maxRcpt) {
                send(MSG_TO_MANY_RCPT);
                return false;
            } else {
                mail.addRcpt(e);
                send(MSG_OK);
                return true;
            }
        } else {
            send(MSG_USER_UNKOWN); // you are not for local domains and connection is not to be relayed.
            return false;
        }

    }


    
    private boolean decodeFrom(String from) throws IOException {
        if ((from == null) || (from.length() < 11))
            return false;
        String fr = from.substring(10).trim();
        if (fr.contains("<>")) {
            EmailAddress e = new EmailAddress();
            e.setUser("<>");
            mail.setFrom(e);
            send(MSG_OK);
            return true;
        }

        fr = fr.replace("<", "").replace(">", "");
        EmailAddress e = null;
        try {
            e = EmailAddress.parseAddress(fr);
            mail.setFrom(e);
            send(MSG_OK);
            return true;

        } catch (InvalidAddress ia) {
            send(MSG_USER_INVALID);
            return false;
        }

    }

    private boolean isAcceptable(EmailAddress addr) {
        //user is authentified by external mecanism
        if (relayed) {
            log.debug("RCPT: " + addr + " is valid for relayed host :" + mail.getReceivedFrom()+" allowed by SMTP extension(s)");
            return true;      
        }
        
        
        // Mail is sent from our local domain
        if (acl.isValidRelay(mail.getReceivedFrom())) {
            log.debug("RCPT: " + addr + " is valid for relayed host :" + mail.getReceivedFrom());
            return true;
        }
        //reject mails not from open relay and for localhost ?
        if (addr.getHost().equals("localhost") || addr.getHost().equals("127.0.0.1")) {
            log.debug("RCPT is not valid for " + mail.getReceivedFrom() + ", this is for localhost but sender is not to be relayed");
            return false;
        }

        // Mail is not send from local domain, so we only accept messages for our domain(s).
        if (acl.isValidAddress(addr)) {
            log.debug("RCPT " + addr + " is valid (local domain)");
            return true;
        }
        log.debug("RCPT is not valid for " + mail.getReceivedFrom() + ", this is not from relayed host and not for local domain");
        return false;
    }

    private void closeConnection() {
        if (wr != null) {
            try {
                wr.write("221 Closing channel. Good Bye " + remote + "\r\n");
                wr.flush();
                log.debug("Closing connection ");
            } catch (IOException e) {
                //error closing channel (sending 221)
            }
        }
    }

    private void reset() {
        relayed=false;
        secured=false;
        mail = new Email();
        commandHistory=new LinkedList<String>();
        authContext=null;

        if (wr != null) {
            try {
                wr.close();
                wr = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (sock != null) {
            try {
                sock.close();
                sock = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void send(String msg) throws IOException {
        wr.write(msg + "\r\n");
        wr.flush();
        log.debug("Sent: " + msg);
    }

    private int getCommand(String input) {
        if ((input == null) || (input.length() < 4))
            return CMD_UNKW;
        String tmp = input.substring(0, 4).toUpperCase();

        if ("HELO".equals(tmp))
            return CMD_HELLO;

        if ("EHLO".equals(tmp))
            return CMD_EHLO;
        
        if ("QUIT".equals(tmp))
            return CMD_QUIT;

        if ("MAIL".equals(tmp))
            return CMD_MAIL_FROM;

        if ("RCPT".equals(tmp))
            return CMD_RCPT;

        if ("DATA".equals(tmp))
            return CMD_DATA;

        if ("RSET".equals(tmp))
            return CMD_RESET;

        if ("NOOP".equals(tmp))
            return CMD_NOOP;
        
        if ("HELP".equals(tmp))
            return CMD_NOOP;
        
        return CMD_UNKW;
    }
    
    public void setRelayed (boolean value) {
        this.relayed=value;
    }
    public Socket getSock() {
        return sock;
    }
    public void setSock(Socket sock) throws IOException {
        this.sock = sock;
        //When setting a socket (by a SMTP extension), rewire reader/writers objects
        csp = new CommandStreamParser(sock.getInputStream(), 4096, true);
        wr=new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
    }
    public boolean isSecured() {
        return secured;
    }
    public void setSecured(boolean secured) {
        this.secured = secured;
    }


    public List getCommandHistory() {
        return commandHistory;
    }

    public void addCommandHistory(String command) {
        if (commandHistory.size()>50)
            commandHistory.remove(commandHistory.size());
        commandHistory.add(0,command);
    }
    
  public void setAuthContext(String context) {
    this.authContext=context;
    mail.setAuthContext(context);
  }
    
    private static final String MSG_HELLO_CODE = "220 ";
    private static final String MSG_HELLO_MESG = " Welcome to Jsmtpd.";
    private static final String MSG_OK = "250 Command OK";
    private static final String MSG_CMD_NOT_ALLOWED = "503 Command not allowed";
    private static final String MSG_USER_UNKOWN = "550 User does not exists, and you are not relayed";
    private static final String MSG_USER_INVALID = "501 Address is not valid"; // was 451
    private static final String MSG_DATA_BEGIN = "354 Listening for data input";
    private static final String MSG_SAVE_ERROR = "500 Error processing message";
    private static final String MSG_INVALID_CMD = "500 Invalid command";
    private static final String MSG_ERROR_SIZE = "552 Message size is to big";
    private static final String MSG_ERROR_LF = "551 Bare LF in data";
    private static final String MSG_CMD_TO_BIG = "500 Line size is to big";
    private static final String MSG_TO_MANY_RCPT = "552 To many RCPT";
    private static final String MSG_BLACKLISTED = "421 Your IP is blacklisted"; //RFC responses expects S: 250 or E: 421. 5xx ?
    private static final String MSG_NO_SPACE_LEFT = "552 No space left on storage";
    
    private static final int CMD_HELLO = 0;
    private static final int CMD_EHLO = 10;
    private static final int CMD_QUIT = 1;
    private static final int CMD_MAIL_FROM = 2;
    private static final int CMD_RCPT = 3;
    private static final int CMD_DATA = 4;
    private static final int CMD_RESET = 6;
    private static final int CMD_NOOP = 7;
    private static final int CMD_UNKW = -1;
    private static final int CMD_HELP = 8;

    public boolean isRelayed() {
        return relayed;
    }

    
    public Email getMail() {
        return mail;
    }
    

    
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.