/*
swish.c 

  SWiSH: Simple Windows SMTP Honeypot - Console
  Copyright (C) 2002 shaun at shat dot net - <http://shat.net/swish/>
  Based on Karl A. Krueger's Python honeypot posted to NANAE

  Your use of this source code, or any binaries created from this source
  code, is restricted by the Code And Binary Acceptable License (CABAL). 
  While the CABAL does not exist, a copy may be found in the LICENSE.TXT
  document distributed with this file.

  SWiSH is a basic multithreaded SMTP honeypot designed to be run on
  Windows. A honeypot is generally defined as a system which has been
  left intentionally vulnerable, in hopes that someone will exploit it.
  In the case of an SMTP honeypot, the idea is to attract spammers who
  believe that your honeypot is actually an open SMTP relay. Once a
  spammer takes your bait, he may pump his garbage into your honeypot,
  which absorbs the messages instead of delivering them. By running an
  SMTP honeypot, you can help to curb the flow of spam.
  
  There is no GUI, SWiSH is a console application. You must have access 
  to a Windows command prompt in order to use this program. 
  
  Usage: swish <[interface]|any> <port> [<logfile>]

  Example: swish 192.168.42.5 25 c:\honeypot\logfile.txt

	Binds SWiSH to port 25 on 192.168.42.5, logging all received mail 
	information to the file c:\honeypot\logfile.txt

  Example: swish any 25

	Binds SWiSH to port 25 on all local interfaces, without logging.

  The following SMTP commands are currently recognized: HELO, EHLO, MAIL,
  RCPT, DATA, VRFY, EXPN, RSET, HELP, NOOP, QUIT. Processing of these 
  commands is not guaranteed to be RFC-compliant, and SWiSH is not 
  intended to be used as an actual mail server.

  WARNING: Running an SMTP honeypot may consume a large amount of 
  system resources, disk space (if logging is enabled), or bandwidth.
  Many consumer oriented ISPs prohibit running any sort of mail server,
  and some ISPs scan their users' machines to locate open SMTP relays.
  SWiSH will generate a false positive on such security scans. If your
  ISP forbids operating a mail server or scans its address space for
  open SMTP relays, _DO_NOT_RUN_SWiSH_ON_YOUR_MACHINE_!

  Compile:
   
	lcc swish.c
	lcclnk swish.obj user32.lib kernel32.lib ws2_32.lib
	Note: <http://archives.neohapsis.com/archives/bugtraq/2002-07/0504.html>

  Known issues:

    RFC compliance is not guaranteed. Sendmail behavior has been mimicked
	where possible.

    In order to address potentially huge drive space requirements, neither
	the recipient addresses nor message bodies are logged. Logged details
	currently include only the sender address, origin IP, subject, and 
	number of recipients. This may change in the future. 

    Detection of terminating \r\n.\r\n may fail under some circumstances,
	causing the associated socket to hang until OS timeout. So, SWiSH
	may (accidentally) operate as a teergrube as well as a honeypot... :)

  TODO:
    
    Use current date/time in logfile and initial 220 response

    More intelligent responses to VRFY/EXPN

    Option for various levels of logging (minimal, full, etc.)

    ?Replace scrolling console output with current stats display?

    ?Implement logfile locking via win32 API?

  CHANGELOG

    2002-08-15: Changed multiple strncmp() to single strnicmp()

*/

//Includes
#include "stdio.h"
#include "string.h"
#include "windows.h"
#include "winsock2.h"
#include <cstdlib>		//LCC chokes without it

//Constants
#define MAIL_BANNER "220 localhost ESMTP Sendmail 8.2.0/8.2.0; Sat, 12 Mar 1994 01:41:48 GMT"
#define MAX_SOCKETS 100

//Functions
void	handle_client(SOCKET socket_client);
void	clearbuf(char *buffer);
int		ShutdownConnection(SOCKET sd);
char	*str_replace(char *Str, size_t BufSiz, char *OldStr, char *NewStr);
void	write_log(char *client_ip, char *sender, char *subject, int message_size, int num_recipients);
int		check_address(char *address);

//Global
int		num_connected = 0;
int		listen_port;
char	listen_interface[16];
char	logfile_name[MAX_PATH];

int main(int argc, char *argv[]){

	WSADATA wsa_data;
	SOCKET socket_listen;
	struct sockaddr_in addr_listen;
	int update = 0, i = 0;
	DWORD dw_threadid;

	//Check command line arguments and show usage if necessary. 
	if((argc != 3) && (argc != 4)){
		system("cls");
		printf("\nYou passed an incorrect number of arguments to SWiSH.\n");
		printf("\nUsage: swish <[interface]|any> <port> [<logfile>]\n\n");
		printf("Example: swish 192.168.42.5 25 c:\\honeypot\\log.txt\n\n");
		printf("This will bind SWiSH to port 25 on 192.168.42.5, logging to the file shown.\n");
		printf("Use \"any\" as the interface argument to bind to all local interfaces. The log\n");
		printf("file argument is optional.\n\n");
		printf("For more information, please see http://shat.net/swish/\n\n");
		exit(0);
	}
	else{
		//Set up variables according to argv[]
		if((int)strlen(argv[1]) < 16){
				sprintf(listen_interface, argv[1]);
		}
		else{
			printf("The first argument to SWiSH should be either an IP address or the word \"any.\"\n");
			return 0;
		}
			
		//Determine listen port
		listen_port = (int)atoi(argv[2]);
		if((listen_port <= 0) || (listen_port >= 65535)){
			printf("You specified an invalid listen port.\n");
			return 0;
		}

		//See if we're using a log file
		if(argc == 4){
			if((int)strlen(argv[3]) < MAX_PATH)
				sprintf(logfile_name, argv[3]);
			else{
				printf("\nThe logfile name you specified was too long. The maximum is %d.\n", MAX_PATH);
				return 0;
			}
		}
	}

	//Display current configuration
	printf("SWiSH - Copyright (C) 2002 shaun@shat.net\nStarting up with the following configuration:\n\n");
	printf("   Interface: %s\n", listen_interface);
	printf("   Listen port: %d\n\n", listen_port);
	if((int)strlen(logfile_name) > 0)
		printf("   Logfile: %s\n\n", logfile_name);

	//Initialize Winsock 
	if (!(WSAStartup(0x101, &wsa_data)==0)){
		printf("[CRIT] Unable to initialize Winsock, shutting down\n.");
		return 0;
	}
	else
		printf("[INFO] Winsock initialized.\n");

	//Tell the listening sockaddr where to connect
	addr_listen.sin_family	    = AF_INET;	
	addr_listen.sin_port        = htons(listen_port);
	if(strnicmp(argv[1],"any",3)==0)
		addr_listen.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		addr_listen.sin_addr.s_addr = inet_addr(listen_interface);

	//Initialize the listening socket
	socket_listen = socket(AF_INET, SOCK_STREAM, 0);

	//Bind the listening socket
	if (bind(socket_listen, (LPSOCKADDR)&addr_listen, sizeof(addr_listen)) == SOCKET_ERROR){
		printf("[CRIT] Couldn't bind to socket_listen! Aborting.\n");
		printf("[CRIT] (Error code %d)\n", WSAGetLastError());
		closesocket(socket_listen);
		return 0;
	}

	//Listen on the listening socket
	if (listen(socket_listen, 1) == SOCKET_ERROR){
		printf("[CRIT] Couldn't listen on socket_listen! Aborting.\n");
		closesocket(socket_listen);
		return 0;
	}
	else{
		printf("[INFO] Listening for connections on port %d\n", listen_port);
	}	
	//Sit around waiting for someone to connect
	while(1) { 
		//Block for accept
		SOCKET socket_client = accept(socket_listen, NULL, NULL);

		//If the connection is good, we have a client!
		if (socket_client != INVALID_SOCKET) {
			printf("[CONN] Incoming connection detected\n");
		}
		else{ //Otherwise we should go back to listening
			printf("[ERR ] accept() failed\n");
			printf("[ERR ] (Error code %d)\n", WSAGetLastError());
			continue;
		}

		//Create a thread to deal with this client
        CreateThread(0, 0, handle_client, (void*)socket_client, 0, &dw_threadid);
	}
	return 0;
}

/*
handle_client takes care of an incoming connection. When the 
client sends a QUIT command or drops the connection, the socket is 
closed up and the client thread exits.
*/
void handle_client(SOCKET socket_client){
	
	char buffer[10240], helo[128], sender[128], command[10];
	char subject[128], temp[128], *pch;
	int bytes = 0, message_size = 0, num_recipients = 0, q = 0;
	struct sockaddr_in addr_server;
	struct sockaddr_in addr_client;
	int sz_addr_client = sizeof(addr_client);
	struct hostent *host;

	//Determine the client's IP address
	getsockname(socket_client, (SOCKADDR *)&addr_client, &sz_addr_client);
	char *client_ip = inet_ntoa(addr_client.sin_addr);
	printf("[CONN] Received connection from %s\n", client_ip);

	clearbuf(buffer);

	//If we already have MAX_SOCKETS in use, we'll send back a 452 error
	//and drop the connection. I used 452 instead of 421 (which would 
	//probably be more appropriate) because spamware may interpret 421 
	//as a permanent failure and remove the honeypot from its server list.
	if(++num_connected >= MAX_SOCKETS){
		sprintf(buffer, "452 Insufficient system resources to serve you\r\n");
		bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
		num_connected--;
		ShutdownConnection(socket_client);
		ExitThread(0);
	}
	else{
		//Send our banner
		sprintf(buffer, "%s\r\n", MAIL_BANNER);
		bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
	}


	//Now we read packets until the cows come home.
	while(1){
		bytes = 0;

		//Receive packet from client
		bytes = recv(socket_client, buffer, sizeof(buffer)-1, 0);
		buffer[(bytes-1)] = '\0';

		//Bail if there's a socket error
		if(bytes == SOCKET_ERROR){
			printf("[ERR ] Socket error, terminating client thread.\n", client_ip);
			ShutdownConnection(socket_client);
			num_connected--;
			ExitThread(0);
		}

		//Bail if the client has dropped the connection
		if(bytes==0){
			printf("[CONN] %s dropped connection. Terminating client thread.\n", client_ip);
			ShutdownConnection(socket_client);
			num_connected--;
			ExitThread(0);
		}

		//Do we have a packet? If not, keep waiting.
		if(bytes <= 0)
			continue;

//debug//printf("packet (%d bytes): %s\n", bytes, buffer);
		
		//Bail if the client sent a QUIT command
		if(strnicmp(buffer, "QUIT", 4) == 0){
			clearbuf(buffer);
			sprintf(buffer, "221 localhost closing connection.\r\n");
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			printf("[CONN] Connection from %s closed. Terminating client thread.\n", client_ip);
			ShutdownConnection(socket_client);
			num_connected--;
			ExitThread(0);
		}

		//Now we need to figure out whether or not the client sent a
		//supported command: HELO, EHLO, MAIL, RCPT, DATA, VRFY, EXPN,
		//RSET, HELP, NOOP, QUIT

		if((strnicmp(buffer,"HELO",4)==0)||(strnicmp(buffer,"EHLO",4)==0)){
			clearbuf(helo);
			clearbuf(command);
			if((int)strlen(buffer)<7){
				memcpy(command, buffer, 4);
				clearbuf(buffer);
				sprintf(buffer, "501 %s needs argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;
			}
			memcpy(helo, buffer+5, 127);
			str_replace(helo, sizeof(helo), "\n", "");
			str_replace(helo, sizeof(helo), "\r", "");
			clearbuf(buffer);
			sprintf(buffer, "250 Hello %s, nice to meet you.\r\n", helo);
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		else if(strnicmp(buffer,"NOOP",4)==0){
			clearbuf(buffer);
			sprintf(buffer, "250 OK\r\n");
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		else if(strnicmp(buffer,"RSET",4)==0){
			if((int)strlen(buffer)>6){
				clearbuf(buffer);
				sprintf(buffer, "501 Invalid argument\r\n");
			}
			else{
				num_recipients = 0;
				clearbuf(buffer);
				clearbuf(sender);
				sprintf(buffer, "250 OK\r\n");
			}
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		else if(strnicmp(buffer,"HELP",4)==0){
			clearbuf(buffer);
			sprintf(buffer, "214-This is sendmail version 8.2.0\r\n");
			strcat(buffer, "214-Supported commands:\r\n");
			strcat(buffer, "214-      HELO    EHLO    MAIL   RCPT     DATA\r\n");
			strcat(buffer, "214-      VRFY    EXPN    RSET   HELP     NOOP\r\n");
			strcat(buffer, "214-      QUIT\r\n");
			strcat(buffer, "214-For local information send email to Postmaster at your site.\r\n");
			strcat(buffer, "214- End of HELP info\r\n");
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		if((strnicmp(buffer,"VRFY",4)==0)||(strnicmp(buffer,"EXPN",4)==0)){
			clearbuf(command);
			memcpy(command, buffer, 4);
			if((int)strlen(buffer)<7){
				clearbuf(buffer);
				sprintf(buffer, "501 %s needs argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;
			}
			memcpy(temp, buffer+5, 128);
			str_replace(temp, sizeof(temp), "\n", "");
			str_replace(temp, sizeof(temp), "\r", "");
			str_replace(temp, sizeof(temp), " ", "");
			clearbuf(buffer);
			//Check the address
			switch(check_address(temp)){
			case 0: //OK
				if(strnicmp(command,"VRFY",4)==0)
					sprintf(buffer, "251 User not local; will forward to %s\r\n", temp);
				else
					sprintf(buffer, "250 %s\r\n", temp);
				break;
			case 1: //Missing @ or dot
				if(strnicmp(command,"VRFY",4)==0)
					sprintf(buffer, "252 Cannot VRFY user; try RCPT to attempt delivery (or try finger)\r\n");
				else
					sprintf(buffer, "250 %s\r\n", temp);
				clearbuf(temp);
				break;
			case 2: //Missing user part (@ was first char)
				sprintf(buffer, "553 %s... User address required\r\n", temp);
				clearbuf(temp);
				break;
			}
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		else if(strnicmp(buffer,"MAIL",4)==0){
			clearbuf(command);
			if((int)strlen(buffer)<7){
				memcpy(command, buffer, 4);
				clearbuf(buffer);
				sprintf(buffer, "501 %s needs argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;
			}
			memcpy(command, buffer+5, 6);
			if(strnicmp(command,"FROM:",5)!=0){
				clearbuf(buffer);
				sprintf(buffer, "501 Invalid argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;				
			}
			if((int)strlen(sender) > 2){
				clearbuf(buffer);
				sprintf(buffer, "503 Nested MAIL command\r\n");
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;				
			}
			memcpy(sender, buffer+10, 128);
			str_replace(sender, sizeof(sender), "\n", "");
			str_replace(sender, sizeof(sender), "\r", "");
			str_replace(sender, sizeof(sender), " ", "");
			clearbuf(buffer);
			num_recipients = 0;

			//Check the address
			switch(check_address(sender)){
			case 0: //OK
				sprintf(buffer, "250 %s... Sender ok\r\n", sender);
				break;
			case 1: //Missing @ or dot
				sprintf(buffer, "553 %s... Domain name required for sender address\r\n", sender);
				clearbuf(sender);
				break;
			case 2: //Missing user part (@ was first char)
				sprintf(buffer, "553 %s... User address required\r\n", sender);
				clearbuf(sender);
				break;
			}

			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
		else if(strnicmp(buffer,"RCPT",4)==0){
			clearbuf(command);
			if((int)strlen(buffer)<7){
				memcpy(command, buffer, 4);
				clearbuf(buffer);
				sprintf(buffer, "501 %s needs argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;
			}
			memcpy(command, buffer+5, 4);
			if(strnicmp(command,"TO:",3)!=0){
				clearbuf(buffer);
				sprintf(buffer, "501 Invalid argument\r\n", command);
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;				
			}
			if((int)strlen(sender) < 1){
				clearbuf(buffer);
				sprintf(buffer, "503 Need MAIL before RCPT\r\n");
				bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
				continue;				
			}
			memcpy(temp, buffer+8, 128);
			str_replace(temp, sizeof(temp), "\n", "");
			str_replace(temp, sizeof(temp), "\r", "");
			str_replace(temp, sizeof(temp), " ", "");
			clearbuf(buffer);

			//Check the address
			switch(check_address(temp)){
			case 0: //OK
				sprintf(buffer, "250 %s... Recipient ok\r\n", temp);
				break;
			case 1: //Missing @ or dot
				sprintf(buffer, "553 %s... User unknown\r\n", temp);
				clearbuf(temp);
				break;
			case 2: //Missing user part (@ was first char)
				sprintf(buffer, "553 %s... User address required\r\n", temp);
				clearbuf(temp);
				break;
			}
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			num_recipients++;
			continue;
		}
		else if(strnicmp(buffer,"DATA",4)==0){
			if(num_recipients == 0){
				clearbuf(buffer);
				sprintf(buffer, "503 Need RCPT (recipient)\r\n");
			}
			else{
				clearbuf(buffer);
				sprintf(buffer, "354 Enter mail, end with \".\" on a line by itself\r\n");
			}
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			while(1){
				//Receive the message contents
				clearbuf(buffer);
				bytes = recv(socket_client, buffer, sizeof(buffer)-1, 0);
				buffer[(bytes-1)] = '\0';
				if(bytes <= 0)
					break;

				message_size += bytes;

				//Does this packet contain the subject?
				if((pch = strstr(buffer, "Subject:")) != NULL){
					pch = strtok(pch, "\n");
					sprintf(subject, pch+8);
					str_replace(subject, sizeof(subject), "\n", "");
					str_replace(subject, sizeof(subject), "\r", "");
				}

				//End of message?
				if((strstr(buffer, "\r\n.\r\n") != NULL) || ((bytes < 5) && (strstr(buffer, ".") != NULL))){
					//Log the details, if necessary
					printf("[INFO] Honeypot abuse (%s, %d bytes, %d recip)\n", client_ip, message_size, num_recipients);
					if((int)strlen(logfile_name) > 0){
						if((int)strlen(subject) < 1)
							sprintf(subject, "(no subject)");
						write_log(client_ip, sender, subject, message_size, num_recipients);
					}

					//Ack the message
					clearbuf(buffer);
					sprintf(buffer, "250 Message accepted for delivery\r\n");
					bytes = send(socket_client, buffer, (int)strlen(buffer), 0);

					//Reset a few things
					num_recipients = 0;
					message_size = 0;
					clearbuf(sender);
					clearbuf(subject);
					break;
				}
			}
			continue;
		}
		else{
			clearbuf(buffer);
			sprintf(buffer, "500 Command unrecognized\r\n");
			bytes = send(socket_client, buffer, (int)strlen(buffer), 0);
			continue;
		}
	//Now we're ready to receive a new packet
	}
}

/*
clearbuf clears a character array buffer passed to it.
*/
void clearbuf(char *buffer){

	memset(buffer, '\0', sizeof(buffer));
}

/*
str_replace replaces a string within a string.
*/
char *str_replace(char *str, size_t size, char *target, char *replace){
      int origlength, newlength;
      char *ptr1, *ptr2;

      if((ptr1 = strstr(str, target)) == NULL)
            return str;
      origlength = strlen(target);
      newlength = strlen(replace);
      if ((strlen(str) + newlength - origlength + 1) > size)
            return NULL;
      memmove(ptr2 = ptr1+newlength, ptr1+origlength, strlen(ptr1+origlength)+1);
      memcpy(ptr1, replace, newlength);
      return ptr2;
}

/*
ShutdownConnection(), modified from the Winsock Programmer's FAQ. 
Receives any pending data on a socket and then closes it gracefully.
*/
int	ShutdownConnection(SOCKET sd){

    if(shutdown(sd, SD_SEND) == SOCKET_ERROR){
        return 0;
    }
    char acReadBuffer[1024];
    while(1){
        int nNewBytes = recv(sd, acReadBuffer, 1024, 0);
        if(nNewBytes == SOCKET_ERROR){
            return 0;
        }
        else if(nNewBytes != 0){
            printf("[INFO] Ignoring %d superfluous bytes during connection close.\n", nNewBytes);
        }
        else{
            break;
        }
    }
    if(closesocket(sd) == SOCKET_ERROR){
        return 0;
    }
    return 1;
}

/*
write_log writes to the log file
*/
void write_log(char *client_ip, char *sender, char *subject, int message_size, int num_recipients){

	FILE *fp;
	if((fp=fopen(logfile_name, "a")) == NULL)
		return;

	char entry[1024];

	sprintf(entry, "%s, From: %s, Subject: %s, Bytes: %d, Recipients: %d\n", client_ip, sender, subject, message_size, num_recipients);
	fputs(entry, fp);
	fclose(fp);
}

/*
check_address performs basic sanity checks on an email address. The
address must contain an @ sign and at least one dot. The @ sign cannot
be the first character in the address.
*/
int check_address(char *address){
	if((strchr(address, '@') == NULL) || (strchr(address, '.') == NULL))
		return 1;
	else if( (strncmp(address, "@", 1) == 0) || (strncmp(address, "<@", 2) == 0)){
		return 2;
	}
	else
		return 0;
}
