mitm.common.tools.SendMail.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.tools.SendMail.java

Source

/*
 * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.common.tools;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import mitm.common.mail.EmailAddressUtils;
import mitm.common.mail.MailTransport;
import mitm.common.mail.MailTransportImpl;
import mitm.common.mail.MailUtils;
import mitm.common.util.DateTimeUtils;
import mitm.common.util.ThreadUtils;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrBuilder;
import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.MessageContext;
import org.subethamail.smtp.MessageHandler;
import org.subethamail.smtp.MessageHandlerFactory;
import org.subethamail.smtp.RejectException;
import org.subethamail.smtp.TooMuchDataException;
import org.subethamail.smtp.server.SMTPServer;

/**
 * Tool which can be used to send and receive email for testing purposes. 
 * 
 * @author Martijn Brinkers
 */
public class SendMail {
    private final static Logger logger = LoggerFactory.getLogger(SendMail.class);

    private final String smtpHost;
    private final Integer smtpPort;
    private final String username;
    private final String sender;
    private final String from;
    private final String recipients;
    private final String inFile;
    private final String subject;
    private final Integer count;
    private final Integer threads;
    private final Long delay;
    private final Integer serverPort;
    private final Integer throttle;
    private final boolean uniqueFrom;

    private String password;

    private final AtomicInteger receiveCount = new AtomicInteger();
    private final AtomicInteger sentCount = new AtomicInteger();

    private final AtomicBoolean forceQuit = new AtomicBoolean();

    final Semaphore throtllingSemaphore;

    /*
     * MessageHandlerFactory used by the internal SMTP server
     */
    private class MessageHandlerFactoryImpl implements MessageHandlerFactory {
        @Override
        public MessageHandler create(MessageContext context) {
            return new Handler();
        }

        class Handler implements MessageHandler {
            @Override
            public void data(InputStream input) throws RejectException, TooMuchDataException, IOException {
                /*
                 * Drain the input (aka /dev/null)
                 */
                IOUtils.copy(input, new NullOutputStream());

                int received = receiveCount.incrementAndGet();

                logger.info("Message\t" + received + "\trec.\tdifference\t\t" + (sentCount.intValue() - received));

                if (throtllingSemaphore != null) {
                    /* for throttling the sending of emails */
                    throtllingSemaphore.release();
                }
            }

            @Override
            public void done() {
                // ignore
            }

            @Override
            public void from(String from) throws RejectException {
            }

            @Override
            public void recipient(String recipient) throws RejectException {
            }
        }
    }

    public SendMail(String[] args) throws ParseException, IOException, MessagingException, InterruptedException {
        CommandLineParser parser = new BasicParser();

        Options options = createCommandLineOptions();

        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("SendMail", options, true);

        CommandLine commandLine = parser.parse(options, args);

        smtpHost = commandLine.getOptionValue("h");
        smtpPort = optionAsInteger(commandLine, "p", 25);
        username = commandLine.getOptionValue("u");
        password = commandLine.getOptionValue("password");
        sender = commandLine.getOptionValue("s");
        from = commandLine.getOptionValue("f");
        recipients = commandLine.getOptionValue("r");
        inFile = commandLine.getOptionValue("in");
        subject = commandLine.getOptionValue("su");
        count = optionAsInteger(commandLine, "c", 1);
        threads = optionAsInteger(commandLine, "t", 1);
        delay = optionAsLong(commandLine, "d", 0L);
        serverPort = optionAsInteger(commandLine, "sp", 2525);
        throttle = optionAsInteger(commandLine, "throttle", null);
        uniqueFrom = commandLine.hasOption("uf");

        throtllingSemaphore = throttle != null ? new Semaphore(throttle, true) : null;

        if (commandLine.hasOption("pwd")) {
            System.out.println("Please enter your password: ");
            password = new jline.ConsoleReader().readLine(new Character('*'));
        }

        if (commandLine.hasOption("l")) {
            startSMTPServer();
            /* allow the SMTP server to settle */
            ThreadUtils.sleepQuietly(1000);
        }

        Address[] recipientsAddresses = getRecipients(recipients);

        if (recipientsAddresses != null) {
            MimeMessage message = commandLine.hasOption("in") ? loadMessage(inFile)
                    : MailUtils.loadMessage(System.in);

            sendMessage(recipientsAddresses, message);
        }
    }

    private void sendMessage(Address[] recipients, MimeMessage message)
            throws MissingArgumentException, MessagingException, InterruptedException {
        Properties properties = System.getProperties();

        if (StringUtils.isBlank(smtpHost)) {
            throw new MissingArgumentException("<smtp host> is missing");
        }

        MailTransport mailSender = new MailTransportImpl(smtpHost, smtpPort, sender, properties, username,
                password);

        prepareMessage(message, from, subject);

        sendMultiThreaded(mailSender, message, recipients);
    }

    private void sendMultiThreaded(final MailTransport mailSender, final MimeMessage message,
            final Address[] recipients) throws InterruptedException {
        ExecutorService threadPool = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(threads, true);

        final long startTime = System.currentTimeMillis();

        for (int i = 1; i <= count; i++) {
            long threadStart = System.currentTimeMillis();

            semaphore.acquireUninterruptibly();

            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        MimeMessage clone = MailUtils.cloneMessage(message);

                        int sent = sentCount.incrementAndGet();

                        if (uniqueFrom) {
                            Address[] froms = clone.getFrom();

                            if (froms != null && froms.length > 0) {
                                clone.setFrom(
                                        new InternetAddress(sent + EmailAddressUtils.getEmailAddress(froms[0])));
                            }
                        }

                        mailSender.sendMessage(clone, recipients);

                        long timePassed = DateTimeUtils
                                .millisecondsToSeconds(System.currentTimeMillis() - startTime);

                        StrBuilder sb = new StrBuilder();

                        sb.append("Message\t" + sent + "\tsent.");

                        if (timePassed > 0) {
                            float msgPerSec = (float) sent / timePassed;

                            sb.append("\tmessages/second\t" + String.format("%.2f", msgPerSec));
                        }

                        logger.info(sb.toString());
                    } catch (MessagingException e) {
                        logger.error("Error sending message.", e);
                    } finally {
                        semaphore.release();
                    }
                }
            });

            if (forceQuit.get()) {
                break;
            }

            if (throtllingSemaphore != null) {
                /* for throttling the sending of emails */
                throtllingSemaphore.acquire();
            } else {
                /* no throttling so use delay */
                long sleepTime = delay - (System.currentTimeMillis() - threadStart);

                if (sleepTime > 0) {
                    Thread.sleep(sleepTime);
                }
            }
        }

        threadPool.shutdown();
        threadPool.awaitTermination(30, TimeUnit.SECONDS);

        waitForReceiveThreads();

        logger.info("Total sent: " + sentCount.intValue() + ". Total time: "
                + DateTimeUtils.millisecondsToSeconds(System.currentTimeMillis() - startTime) + " (sec.)");
    }

    private void waitForReceiveThreads() throws InterruptedException {
        //        if (throtllingSemaphore != null) {
        //            throtllingSemaphore.tryAcquire(throttle, 120, TimeUnit.SECONDS);
        //        }
    }

    private void startSMTPServer() {
        MessageHandlerFactoryImpl factory = new MessageHandlerFactoryImpl();

        SMTPServer smtpServer = new SMTPServer(factory);
        smtpServer.setPort(serverPort);
        smtpServer.start();

        /*
         * Add a shutdown hook which waits a few seconds before shutting down. This
         * gives the SMTP server some time to receive all messages
         */
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                /*
                 * Make sure that no more messages are sent 
                 */
                forceQuit.set(true);

                try {
                    waitForReceiveThreads();
                } catch (InterruptedException e) {
                    // ignore
                }

                ThreadUtils.sleepQuietly(5000);
            }
        });
    }

    @SuppressWarnings("static-access")
    private static Options createCommandLineOptions() {
        Options options = new Options();

        Option hostOption = OptionBuilder.withArgName("host").hasArg().withDescription("smtp host").create("h");
        hostOption.setRequired(false);
        options.addOption(hostOption);

        Option portOption = OptionBuilder.withArgName("port").hasArg().withDescription("smtp port").create("p");
        portOption.setRequired(false);
        options.addOption(portOption);

        Option usernameOption = OptionBuilder.withArgName("username").hasArg().withDescription("username")
                .create("u");
        usernameOption.setRequired(false);
        options.addOption(usernameOption);

        Option passwordPromptOption = OptionBuilder.withArgName("p").withDescription("ask for password")
                .create("pwd");
        passwordPromptOption.setRequired(false);
        options.addOption(passwordPromptOption);

        Option passwordOption = OptionBuilder.withArgName("password").hasArg().withDescription("password")
                .create("password");
        passwordOption.setRequired(false);
        options.addOption(passwordOption);

        Option senderOption = OptionBuilder.withArgName("sender").hasArg()
                .withDescription("enveloped sender (MAIL FROM)").create("s");
        senderOption.setRequired(false);
        options.addOption(senderOption);

        Option subjectOption = OptionBuilder.withArgName("subject").hasArg().withDescription("subject")
                .create("su");
        subjectOption.setRequired(false);
        options.addOption(subjectOption);

        Option fromOption = OptionBuilder.withArgName("from").hasArg().withDescription("from (header)").create("f");
        fromOption.setRequired(false);
        options.addOption(fromOption);

        Option recipientsOption = OptionBuilder.withArgName("recipients").hasArg()
                .withDescription("comma separated recipients").create("r");
        recipientsOption.setRequired(false);
        options.addOption(recipientsOption);

        Option inOption = OptionBuilder.withArgName("email file").hasArg().withDescription("input file (rfc 2822)")
                .create("in");
        inOption.setRequired(false);
        options.addOption(inOption);

        Option countOption = OptionBuilder.withArgName("count").hasArg().withDescription("count").create("c");
        countOption.setRequired(false);
        options.addOption(countOption);

        Option threadsOption = OptionBuilder.withArgName("threads").hasArg().withDescription("number of threads")
                .create("t");
        threadsOption.setRequired(false);
        options.addOption(threadsOption);

        Option delayOption = OptionBuilder.withArgName("delay").hasArg().withDescription("delay in milliseconds")
                .create("d");
        delayOption.setRequired(false);
        options.addOption(delayOption);

        Option listenOption = OptionBuilder.withArgName("listen").withDescription("listen for incoming SMTP")
                .create("l");
        listenOption.setRequired(false);
        options.addOption(listenOption);

        Option throttlingOption = OptionBuilder.withArgName("throttling").hasArg().withDescription("throttle send")
                .create("throttle");
        throttlingOption.setRequired(false);
        options.addOption(throttlingOption);

        Option serverPortOption = OptionBuilder.withArgName("port").hasArg().withDescription("smtp server port")
                .create("serverPort");
        serverPortOption.setRequired(false);
        options.addOption(serverPortOption);

        Option uniqueFromOption = OptionBuilder.withDescription("prefix the from with an increasing counter")
                .create("uf");
        uniqueFromOption.setRequired(false);
        options.addOption(uniqueFromOption);

        return options;
    }

    private static Address[] getRecipients(String recipients) throws AddressException {
        if (StringUtils.isBlank(recipients)) {
            return null;
        }

        List<Address> addresses = new LinkedList<Address>();

        StringTokenizer tokenizer = new StringTokenizer(recipients, ",");

        while (tokenizer.hasMoreTokens()) {
            String recipient = tokenizer.nextToken();

            addresses.add(new InternetAddress(recipient));
        }

        return (Address[]) addresses.toArray(new Address[0]);
    }

    private static MimeMessage loadMessage(String filename) throws FileNotFoundException, MessagingException {
        File mail = new File(filename);

        mail = mail.getAbsoluteFile();

        MimeMessage message = MailUtils.loadMessage(mail);

        return message;
    }

    private static void prepareMessage(MimeMessage message, String from, String subject) throws MessagingException {
        message.setSentDate(new Date());

        if (from != null) {
            message.setFrom(new InternetAddress(from));
        }

        if (subject != null) {
            message.setSubject(subject);
        }
    }

    private static Integer optionAsInteger(CommandLine commandLine, String option, Integer defaultIfNull) {
        String value = commandLine.getOptionValue(option);

        if (value == null) {
            return defaultIfNull;
        }

        return Integer.parseInt(value);
    }

    private static Long optionAsLong(CommandLine commandLine, String option, Long defaultIfNull) {
        String value = commandLine.getOptionValue(option);

        if (value == null) {
            return defaultIfNull;
        }

        return Long.parseLong(value);
    }

    public static void main(String[] args) {
        PropertyConfigurator.configure("conf/tools.log4j.properties");

        try {
            new SendMail(args);
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (MissingArgumentException e) {
            logger.warn("Not all required parameters are specified. " + e);
        } catch (ParseException e) {
            logger.error("Command line parsing error. " + e);
        } catch (FileNotFoundException e) {
            logger.error("File cannot be found. " + e);
        } catch (Exception e) {
            logger.error("Unknown error.", e);
        }
    }
}