/*
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;
}