/* swish.c SWiSH: Simple Windows SMTP Honeypot - Console Copyright (C) 2002 shaun at shat dot net - 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> [] 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: 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 //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> []\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; }