org.freenetproject.freemail.smtp.SMTPHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.freenetproject.freemail.smtp.SMTPHandler.java

Source

/*
 * SMTPHandler.java
 * This file is part of Freemail
 * Copyright (C) 2006,2007,2008 Dave Baker
 * Copyright (C) 2007 Alexander Lehmann
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.freenetproject.freemail.smtp;

import java.net.Socket;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.File;
import java.io.PrintWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import freenet.pluginmanager.PluginNotFoundException;
import freenet.support.api.Bucket;
import freenet.support.io.FileBucket;

import org.archive.util.Base32;
import org.bouncycastle.util.encoders.Base64;
import org.freenetproject.freemail.AccountManager;
import org.freenetproject.freemail.Freemail;
import org.freenetproject.freemail.FreemailAccount;
import org.freenetproject.freemail.ServerHandler;
import org.freenetproject.freemail.transport.MessageHandler;
import org.freenetproject.freemail.utils.Logger;
import org.freenetproject.freemail.wot.Identity;
import org.freenetproject.freemail.wot.IdentityMatcher;

public class SMTPHandler extends ServerHandler implements Runnable {
    private final OutputStream os;
    private final PrintStream ps;
    private final BufferedReader bufrdr;
    private FreemailAccount account;
    public static final String MY_HOSTNAME = "localhost";

    private final AccountManager accountmanager;
    private final IdentityMatcher identityMatcher;

    private Vector<Identity> to;

    public SMTPHandler(AccountManager accMgr, Socket client, IdentityMatcher identityMatcher) throws IOException {
        super(client);
        accountmanager = accMgr;
        this.account = null;
        this.os = client.getOutputStream();
        this.ps = new PrintStream(this.os);
        this.bufrdr = new BufferedReader(new InputStreamReader(client.getInputStream()));
        this.identityMatcher = identityMatcher;

        this.to = new Vector<Identity>();
    }

    @Override
    public void run() {
        this.sendWelcome();

        String line;
        try {
            while (!stopping && !this.client.isClosed() && (line = this.bufrdr.readLine()) != null) {
                SMTPCommand msg = null;
                try {
                    //Logger.normal(this,line);
                    msg = new SMTPCommand(line);
                } catch (SMTPBadCommandException bce) {
                    Logger.debug(this, "Parsing failed, line was: " + line);
                    continue;
                }

                Logger.debug(this, "Received: " + line);
                this.dispatch(msg);
            }

            this.client.close();
        } catch (IOException ioe) {

        }
    }

    private void dispatch(SMTPCommand cmd) {
        if (cmd.command.equals("helo")) {
            this.handle_helo();
        } else if (cmd.command.equals("ehlo")) {
            this.handle_ehlo();
        } else if (cmd.command.equals("quit")) {
            this.handle_quit();
        } else if (cmd.command.equals("turn")) {
            this.handle_turn();
        } else if (cmd.command.equals("auth")) {
            this.handle_auth(cmd);
        } else if (cmd.command.equals("mail")) {
            this.handle_mail();
        } else if (cmd.command.equals("rcpt")) {
            this.handle_rcpt(cmd);
        } else if (cmd.command.equals("data")) {
            this.handle_data();
        } else if (cmd.command.equals("rset")) {
            this.handle_rset();
        } else {
            Logger.normal(this, "Unknown command: " + cmd.command);
            this.ps.print("502 Unimplemented\r\n");
        }
    }

    private void handle_helo() {
        this.ps.print("250 " + MY_HOSTNAME + "\r\n");
    }

    private void handle_ehlo() {
        this.ps.print("250-" + MY_HOSTNAME + "\r\n");
        this.ps.print("250 AUTH LOGIN PLAIN\r\n");
    }

    private void handle_quit() {
        this.ps.print("221 " + MY_HOSTNAME + "\r\n");
        try {
            this.client.close();
        } catch (IOException ioe) {

        }
    }

    private void handle_turn() {
        this.ps.print("502 No\r\n");
    }

    private void handle_auth(SMTPCommand cmd) {
        String uname;
        String password;

        if (cmd.args.length == 0) {
            this.ps.print("504 No auth type given\r\n");
            return;
        }

        if (this.account != null) {
            this.ps.print("503 Already authenticated\r\n");
            return;
        }

        if (cmd.args[0].equalsIgnoreCase("login")) {
            try {
                this.ps.print("334 " + new String(Base64.encode("Username:".getBytes("UTF-8"))) + "\r\n");
            } catch (UnsupportedEncodingException e) {
                //JVMs are required to support UTF-8, so we can assume it is always available
                throw new AssertionError("JVM doesn't support UTF-8 charset");
            }

            String b64username;
            String b64password;
            try {
                b64username = this.bufrdr.readLine();
            } catch (IOException ioe) {
                return;
            }
            if (b64username == null)
                return;

            try {
                this.ps.print("334 " + new String(Base64.encode("Password:".getBytes("UTF-8"))) + "\r\n");
            } catch (UnsupportedEncodingException e) {
                //JVMs are required to support UTF-8, so we can assume it is always available
                throw new AssertionError("JVM doesn't support UTF-8 charset");
            }
            try {
                b64password = this.bufrdr.readLine();
            } catch (IOException ioe) {
                return;
            }
            if (b64password == null)
                return;

            try {
                uname = new String(Base64.decode(b64username.getBytes("UTF-8")));
                password = new String(Base64.decode(b64password.getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
                //JVMs are required to support UTF-8, so we can assume it is always available
                throw new AssertionError("JVM doesn't support UTF-8 charset");
            }
        } else if (cmd.args[0].equalsIgnoreCase("plain")) {
            String b64creds;

            if (cmd.args.length > 1) {
                b64creds = cmd.args[1];
            } else {
                this.ps.print("334 \r\n");
                try {
                    b64creds = this.bufrdr.readLine();
                    if (b64creds == null)
                        return;
                } catch (IOException ioe) {
                    return;
                }
            }

            if (b64creds.equals("*")) {
                this.ps.print("501 Authentication canceled\r\n");
                return;
            }

            String creds_plain;
            try {
                creds_plain = new String(Base64.decode(b64creds.getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
                //JVMs are required to support UTF-8, so we can assume it is always available
                throw new AssertionError("JVM doesn't support UTF-8 charset");
            }
            String[] creds = creds_plain.split("\0");
            if (creds.length != 3) {
                this.ps.print("501 Invalid arguments to plain auth\r\n");
                return;
            }

            String authzid = creds[0];
            uname = creds[1];
            password = creds[2];

            if (!authzid.isEmpty()) {
                if (!authzid.equals(uname)) {
                    this.ps.print("535 Authentication failed\r\n");
                    return;
                }
            }
        } else {
            this.ps.print("504 Auth type unimplemented - weren't you listening?\r\n");
            return;
        }

        if (uname.contains("@") && uname.endsWith(".freemail")) {
            //Extract the base32 identity string and convert it to base64
            uname = uname.substring(uname.indexOf("@") + 1, uname.length() - ".freemail".length());

            //We need to use the Freenet Base64 encoder here since it uses a slightly different set
            //of characters
            uname = freenet.support.Base64.encode(Base32.decode(uname));

            Logger.debug(this, "Extracted Identity string: " + uname);
        }

        account = accountmanager.authenticate(uname, password);
        if (account != null) {
            this.ps.print("235 Authenticated\r\n");
        } else {
            this.ps.print("535 Authentication failed\r\n");
        }
    }

    private void handle_mail() {
        if (this.account == null) {
            this.ps.print("530 Authentication required\r\n");
            return;
        }

        this.to.clear();

        // we don't really care.
        this.ps.print("250 OK\r\n");
    }

    private void handle_rcpt(SMTPCommand cmd) {
        if (cmd.args.length < 1) {
            this.ps.print("504 Insufficient arguments\r\n");
            return;
        }

        if (this.account == null) {
            this.ps.print("530 Authentication required\r\n");
            return;
        }

        String allargs = new String();
        for (int i = 0; i < cmd.args.length; i++) {
            allargs += cmd.args[i];
        }

        String[] parts = allargs.split(":", 2);
        if (parts.length < 2) {
            this.ps.print("504 Can't understand that syntax\r\n");
            return;
        }

        String address = parts[1];
        if (address.startsWith("<") && address.endsWith(">")) {
            address = address.substring(1, address.length() - 1);
        }

        //Check if the identity is in WoT
        Set<String> recipient = new HashSet<String>();
        recipient.add(address);
        Map<String, List<Identity>> matches;
        try {
            EnumSet<IdentityMatcher.MatchMethod> methods = EnumSet.of(IdentityMatcher.MatchMethod.FULL_BASE32);
            matches = identityMatcher.matchIdentities(recipient, account.getIdentity(), methods);
        } catch (PluginNotFoundException e) {
            this.ps.print("554 WoT plugin not loaded\r\n");
            return;
        }
        if (matches.get(address).size() != 1) {
            this.ps.print("550 No such user\r\n");
            return;
        }

        this.to.add(matches.get(address).get(0));

        this.ps.print("250 OK\r\n");
    }

    private void handle_data() {
        if (this.account == null) {
            this.ps.print("530 Authentication required\r\n");
            return;
        }

        if (this.to.size() == 0) {
            this.ps.print("503 RCPT first\r\n");
            return;
        }

        File tempfile = null;
        try {
            tempfile = File.createTempFile("freemail-", ".message", Freemail.getTempDir());
            PrintWriter pw = new PrintWriter(new FileOutputStream(tempfile));

            this.ps.print("354 Go crazy\r\n");

            String line;
            boolean done = false;
            while ((line = this.bufrdr.readLine()) != null) {
                if (line.equals(".")) {
                    done = true;
                    break;
                }
                if (line.startsWith(".")) {
                    line = line.substring(1);
                }
                pw.print(line + "\r\n");
            }

            pw.close();
            if (!done) {
                // connection closed before the message was
                // finished. bail out.
                tempfile.delete();
                return;
            }

            MessageHandler messageSender = account.getMessageHandler();
            Bucket data = new FileBucket(tempfile, false, false, false, true);
            try {
                if (messageSender.sendMessage(to, data)) {
                    this.ps.print("250 So be it\r\n");
                } else {
                    this.ps.print("452 Message sending failed\r\n");
                }
            } finally {
                data.free();
            }
        } catch (IOException ioe) {
            this.ps.print("452 Can't store message\r\n");
        } finally {
            if (tempfile != null) {
                tempfile.delete();
            }
        }
    }

    private void handle_rset() {
        this.to.clear();
        this.ps.print("250 Reset\r\n");
    }

    private void sendWelcome() {
        this.ps.print("220 " + MY_HOSTNAME + " ready\r\n");
    }
}