
package org.jonlin.net;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
import java.util.Random;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;

/**
 * Copyright (c) 2000-2004, Comet Way, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *   - Redistributions of source code must retain the
 *     above copyright notice, this list of conditions
 *     and the following disclaimer.
 *   - Redistributions in binary form must reproduce the
 *     above copyright notice, this list of conditions
 *     and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *   - Neither the name of Comet Way, Inc. nor the names of
 *     its contributors may be used to endorse or promote
 *     products derived from this software without specific
 *     priorwritten permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE
 *  REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; ORBUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER INCONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *
 * Revision history:
 */



/**
 * An SMTP server that attempts to trap clients and waste the client's resources. Attempts
 * are made to trick the client into thinking they are talking to a real SMTP server and 
 * to keep the client talking as long as possible, wasting its resources and keeping the
 * client from connecting to real open relays to send spam.
 */
public class SMTPHoneyPot implements Runnable
{
    Thread runThread;

    Vector smartClients;
    SimpleDateFormat dateFormat;

    String displayHost;
    String displayVersion;
    String greeting;
    int delay;
    int rcptDelay;
    int smartClientLimit;
    String logFile;
    FileWriter logOut;
    String dateFormatString;

    static boolean verbose;

    public SMTPHoneyPot(String[] args)
    {
	// parse the arguments. SMTPHoneyPot.start() needs to be called explicity to start the process
	for(int x=0;x<args.length;x++) {
	    if(args[x].startsWith("-displayHost")) {
		displayHost = args[++x];
	    }
	    else if(args[x].startsWith("-displayVersion")) {
		displayVersion = args[++x];
	    }
	    else if(args[x].startsWith("-logfile")) {
		logFile = args[++x];
	    }
	    else if(args[x].startsWith("-delay")) {
		delay = Integer.parseInt(args[++x]);
	    }
	    else if(args[x].startsWith("-rcptDelay")) {
		rcptDelay = Integer.parseInt(args[++x]);
	    }
	    else if(args[x].startsWith("-smartTime")) {
		smartClientLimit = Integer.parseInt(args[++x]);
	    }
	    else if(args[x].startsWith("-greeting")) {
		greeting = args[++x];
	    }
	    else if(args[x].startsWith("-dateFormat")) {
		dateFormatString = args[++x];
	    }
	    else if(args[x].startsWith("-v")) {
		verbose = true;
	    }

	    else if(args[x].startsWith("-help")) {
		printHelp();
		System.exit(-1);
	    }
	    else {
		error("Unknown argument: "+args[x]);
		printHelp();
		System.exit(-1);
	    }
	}
    }

    /**
     * Prints the command line help, listing the available options.
     */
    public void printHelp()
    {
	verbose = true;
	print("USAGE: java org.jonlin.net.SMTPHoneyPot [OPTIONS]");
	print("OPTIONS:");
	print("-displayHost <hostname>   The hostname to display in the SMTP welcome message");
	print("-displayVersion <version> The SMTP version/OS to display in the SMTP welcome message");
	print("-logfile <filename>       The log file to log connections");
	print("-delay <milliseconds>     The number of milliseconds of delay between each character sent to");
	print("                          the connecting client. If this value is too high, the client may timeout");
	print("-rcptDelay <milliseconds> The delay in milliseconds used after the RCPT command");
	print("-greeting <greeting Str>  The greeting message to use after a successful EHLO from client");
	print("-dateFormat <format Str>  The date format string to use in the log file, this must be a format string");
	print("                          user by the java.text.SimpleDateFormat class");
	print("-smartTime <milliseconds> The lower bound of the number of milliseconds it takes for a client IP to");
	print("                          be treated as a \"Smart Client\". These clients will go through the entire");
	print("                          SMTP dialog.");
	print("-v                        Verbose output");
	print("");
	print("-help             Displays this help message");
	verbose = false;
    }


    /**
     * Initialize the local variables and start the thread
     */
    public void start()
    {
	runThread = new Thread(this);

	smartClients = new Vector();

	if(displayHost == null) {
	    displayHost = "localhost-127.0.0.1";
	}
	if(displayVersion == null) {
	    displayVersion = "6.5/OpenBSD";
	}
	if(greeting == null) {
	    greeting = "Hello bastards";
	}
	if(delay==0) {
	    delay = 500;
	}
	if(rcptDelay==0) {
	    rcptDelay = 4000;
	}
	if(smartClientLimit==0) {
	    smartClientLimit = 1000000;
	}
	if(logFile!=null) {
	    try {
		logOut = new FileWriter(logFile,true);
	    }
	    catch(Exception e) {
		error("Could not create logfile: "+logFile,e);
		logFile = null;
	    }
	}
	if(dateFormatString==null) {
	    dateFormatString = "yyyy.MM.dd HH:mm:ss";
	}

	dateFormat = new SimpleDateFormat(dateFormatString);

	runThread.start();
    }


    /**
     * Perma loop to accept connections
     */
    public void run()
    {
	ServerSocket ss = null;
	try {
	    ss = new ServerSocket(25);

	    while(true) {
		try {
		    Socket s = ss.accept();
		    Handler h = new Handler(s);
		    h.start();
		}
		catch(Exception e) {
		    error("An exception occured while handling client connection",e);
		}
	    }
	}
	catch(Exception e) {
	    error("An exception occured while opening port 25",e);
	}
    }





    /**
     * This is the handler thread which handles the SMTP dialog to an incoming client
     * connection. The result of this dialog is logged by the handler.
     */
    class Handler extends Thread
    {
	Socket s;
	OutputStream out = null;
	BufferedReader in = null;

	public Handler(Socket client)
	{
	    s = client;
	}

	public void run()
	{
	    StringBuffer logString = new StringBuffer();
	    String ipAddress = null;
	    long timestamp = System.currentTimeMillis();
	    boolean isSmartClient = false;

	    try {
		s.setSoTimeout(60000);

		// Set up the IP address of the client, used for logging
		ipAddress = s.getInetAddress().toString();
		print(dateFormat.format(new Date())+" - Connection from: "+ipAddress);

		if(ipAddress.indexOf("/")!=-1) {
		    ipAddress = ipAddress.substring(ipAddress.indexOf("/")+1);
		}

		if(smartClients.contains(ipAddress)) {
		    isSmartClient = true;
		}

		logString.append(dateFormat.format(new Date()));
		logString.append(" - ");
		logString.append(ipAddress);

		out = s.getOutputStream();
		in = new BufferedReader(new InputStreamReader(s.getInputStream()));

		// Send the SMTP welcome message
		write("220 "+displayHost+" ESMTP SMTP-Server; (Version "+displayVersion+") ready.\r\n");

		// read the client greeting, HELO or EHLO
		String input = in.readLine();

		if(isSmartClient) {
		    logString.append(" - Flagged as smart client");
		    write("250 "+displayHost+" "+greeting+"\r\n");

		    try {
			while(true) {
			    input = in.readLine();
			    if(input==null) {
				break;
			    }
			    input = input.toLowerCase();
			    if(input.startsWith("mail ")) {
				write("250 2.1.0 Sender ok\r\n");
			    }
			    else if(input.startsWith("rcpt ")) {
				Thread.sleep(rcptDelay);
				write("250 2.1.5 Recipient ok\r\n");
			    }
			    else if(input.startsWith("data")) {
				write("354 Enter mail, end with \".\" on a line by itself\r\n");
				try {
				    while(!input.equals(".")) {
					input = in.readLine();
				    }
				    write("250 2.0.0 Message accepted for delivery\r\n");
				}
				catch(Exception e) {
				    break;
				}
			    }
			    else if(input.startsWith("rset")) {
				write("250 2.0.0 Reset state\r\n");
			    }
			    else if(input.startsWith("quit")) {
				// we'll attempt to NOT let the client quit
				try {
				    while(true) {
					write("0");
				    }
				}
				catch(Exception e) {
				    break;
				}
			    }
			    else {
				write("500 5.5.1 Command unrecognized\r\n");
			    }					
			}
		    }
		    catch(Exception e) {;}
		}
		else {
		    if(!input.toLowerCase().startsWith("ehlo")) {
			// This isn't an extended HELO, so we just send them zeros
			logString.append(" - HELO, sending 0's");
			try {
			    write("250 ");
			    while(true) {
				write("0");
			    }
			}
			catch(Exception e) {;}
		    }
		    else {
			// This is the extended EHLO, we send them infinite extended SMTP commands
			Random rand = new Random();
                        char[] letters = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
                        write("250-"+displayHost+" "+greeting+"\r\n");
                        write("250-ENHANCEDSTATUSCODES\r\n");
                        write("250-PIPELINING\r\n");
                        write("250-8BITMIME\r\n");
                        write("250-SIZE\r\n");
                        write("250-DSN\r\n");
                        write("250-ETRN\r\n");
                        write("250-AUTH PLAIN LOGIN GSSAPI KERBEROS_V4\r\n");
                        write("250-DELIVERBY\r\n");

                        while(true) {
			    write("250-");
			    StringBuffer tmp = new StringBuffer();
			    tmp.append(letters[Math.abs(rand.nextInt())%26]);
			    tmp.append(letters[Math.abs(rand.nextInt())%26]);
			    tmp.append(letters[Math.abs(rand.nextInt())%26]);
			    tmp.append(letters[Math.abs(rand.nextInt())%26]);

			    while(Math.abs(rand.nextInt())%50!=0) {
				if(Math.abs(rand.nextInt())%10==0) {
				    tmp.append(" ");
				}
				tmp.append(letters[Math.abs(rand.nextInt())%26]);
			    }
			    tmp.append("\r\n");
			    write(tmp.toString());
                        }
		    }
		}
	    }
	    catch(Exception e) {
		//		error("Exception while handling request",e);
	    }
	    
	    timestamp = System.currentTimeMillis()-timestamp;
	    logString.append(" - "+timestamp);
	    logString.append("ms");

	    // If the client disconnects too early, we add them to the smart clients list
	    if(!isSmartClient) {
		if(timestamp < smartClientLimit) {
		    smartClients.addElement(ipAddress);
		}
	    }

	    try {
		out.close();
	    }
	    catch(Exception e) {;}
	    try {
		in.close();
	    }
	    catch(Exception e) {;}
	    try {
		s.close();
	    }
	    catch(Exception e) {;}

	    if(logFile!=null) {
		try {
		    logOut.write(logString.toString());
		    logOut.write("\n");
		    logOut.flush();
		}
		catch(Exception e) {
		    error("Cannot write to logfile: "+logFile,e);
		}
	    }
	}

	public void write(String s) throws IOException
	{
	    for(int x=0;x<s.length();x++) {
		out.write((int)s.charAt(x));
		out.flush();
		try {
		    Thread.sleep(delay);
		}
		catch(Exception e) {;}
	    }
	}

    }

























    public static void print(String s)
    {
	if(verbose) {
	    System.out.println("[SMTPHoneyPot] "+s);
	}
    }

    public static void error(String s)
    {
	System.err.println("{SMTPHoneyPot} ERROR: "+s);
    }

    public static void error(String s, Exception e)
    {
	System.err.println("{SMTPHoneyPot} ERROR: "+s+" : "+e);
	e.printStackTrace(System.err);
    }

    public static void main(String[] args)
    {
	SMTPHoneyPot server = new SMTPHoneyPot(args);
	server.start();
    }

}
