/*
relaytest.c - DSBL SMTP open relay tester
Copyright (C) 2002 Ian Gulliver

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <firedns.h>
#include <firestring.h>
#include "testlib.h"

#define DOMAIN_DEPTH 3
#define DOMAIN_TESTSTART 16
#define MAXTEST 34

#define AUTH_LOGIN 0
#define AUTH_NTLM 1

int needgreeting = 1;
char domains[5 + (DOMAIN_DEPTH * 2)][256];
int domaincount = 3;
char headers[4096];
int connection_closed;
int verbose = 0;

int checkdomain(char *domain) {
	int i;
	for (i = 0; i < domaincount; i++) {
		if (firestring_strcasecmp(domains[i],domain) == 0)
			return 1;
	}
	return 0;
}

void adddomain(char *domain) {
	char * tempchr;
	int i;
	if (strlen(domain) > 255)
		return;
	tempchr = strchr(domain,'.');
	if (tempchr == NULL)
		return;
	if (checkdomain(domain))
		return;
	strcpy(domains[domaincount++],domain);
	tempchr = domain;
	for (i = 0; i < DOMAIN_DEPTH; i++) {
		tempchr = strchr(tempchr,'.');
		if (tempchr == NULL)
			return;
		tempchr++;
		if (checkdomain(tempchr))
			return;
		if (strchr(tempchr,'.') == NULL)
			return;
		strcpy(domains[domaincount++],tempchr);
	}
}

char *getline(int fd) {
	static char buffer[16384];
	static char line[16384];
	static int bufpos = 0;
	int i;
	char *tempchr;

	tempchr = memchr(buffer,'\n',bufpos);
	while (tempchr == NULL) {
		i = recv(fd,&buffer[bufpos],16383 - bufpos,0);
		if (i <= 0) /* eof of error, let the parent figure out what to do */
			return NULL;
		bufpos += i;
		tempchr = memchr(buffer,'\n',bufpos);
		if (tempchr == NULL && bufpos == 16383) { /* line too long (hostile act) */
			fprintf(stderr,"Input buffer overflow\n");
			exit(2);
		}
	}
	if (tempchr != NULL) {
		memcpy(line,buffer,tempchr - buffer);
		line[tempchr - buffer] = '\0';
		bufpos -= (tempchr - buffer) + 1;
		memcpy(buffer,&tempchr[1],bufpos);
		return line;
	}
	return NULL;
}

int getresponse(int fd) {
	char *line;
	
	while (1) {
		line = getline(fd);
		if (line == NULL) { /* eof */
			connection_closed = 1;
			return 9;
		}
		if (verbose)
			printf("<<< %s\n", line);
		if (strlen(line) >= 4 && line[3] == '-')
			continue;
		else if (line[0] == '1')
			return 1;
		else if (line[0] == '2') {
			if (needgreeting == 1) {
				char *tempchr;
				needgreeting = 0;
				tempchr = strchr(&line[4],' ');
				if (tempchr != NULL)
					*tempchr = '\0';
				adddomain(&line[4]);
			}
			return 2;
		} else if (line[0] == '3')
			return 3;
		else if (line[0] == '4')
			return 4;
		else if (line[0] == '5')
			return 5;
		else
			return 8; /* bad code */
	}
}

const char *days[] = {
	        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

const char *months[] = {
	        "Jan", "Feb", "Mar", "Apr", "May",  "Jun", "Jul",  "Aug",  "Sep", "Oct", "Nov", "Dec"
};

void buildheaders(char *cookie, const char *source, const char *target_user, const char *target_domain) {
	struct tm *timestruct;
	time_t temptime;

	time(&temptime);
	timestruct = gmtime(&temptime);
	firestring_snprintf(headers,4096,"Message-ID: <%s@%s>\r\n"
			"Date: %s, %d %s %d %02d:%02d:%02d +0000\r\n"
			"To: <%s@%s>\r\n"
			"Subject: Open Relay Test Message\r\n",cookie,source,days[timestruct->tm_wday],timestruct->tm_mday,months[timestruct->tm_mon],1900 + timestruct->tm_year,timestruct->tm_hour,timestruct->tm_min,timestruct->tm_sec,target_user,target_domain);
}

/*
NUM	FROM					TO
0	<sender@source.com>			<listme@dsbl.org>
1	<sender@source.com>			<"listme@dsbl.org">
2	<sender@source.com>			listme@dsbl.org
3	<sender@source.com>			<dsbl.org!listme>
4	<nobody>				<listme@dsbl.org>
5	<nobody>				<"listme@dsbl.org">
6	<nobody>				listme@dsbl.org
7	<nobody>				<dsbl.org!listme>
8	<source.com!sender>			<listme@dsbl.org>
9	<source.com!sender>			<"listme@dsbl.org">
10	<source.com!sender>			listme@dsbl.org
11	<source.com!sender>			<dsbl.org!listme>
12	<>					<listme@dsbl.org>
13	<>					<"listme@dsbl.org">
14	<>					listme@dsbl.org
15	<>					<dsbl.org!listme>

16	<nobody@DOMAIN>				<listme@dsbl.org>
17	<nobody@DOMAIN>				<"listme@dsbl.org">
18	<nobody@DOMAIN>				listme@dsbl.org
19	<nobody@DOMAIN>				<listme%dsbl.org@DOMAIN>
20	<nobody@DOMAIN>				<listme%dsbl.org>
21	<nobody@DOMAIN>				<"listme%dsbl.org">
22	<nobody@DOMAIN>				<listme@dsbl.org@DOMAIN>
23	<nobody@DOMAIN>				<"listme@dsbl.org"@DOMAIN>
24	<nobody@DOMAIN>				<@DOMAIN:listme@dsbl.org>
25	<nobody@DOMAIN>				<dsbl.org!listme>
26	<nobody@DOMAIN>				<dsbl.org!listme@DOMAIN>
27	<DOMAIN!nobody>				<dsbl.org!listme>
28	<DOMAIN!nobody>				<dsbl.org!listme@DOMAIN>
29	<postmaster@DOMAIN>			<listme@dsbl.org>
30	<administrator@DOMAIN>			<listme@dsbl.org>
31	<sales@DOMAIN>				<listme@dsbl.org>
32	<info@DOMAIN>				<listme@dsbl.org>
33	<webmaster@DOMAIN>			<listme@dsbl.org>
34	<listserv@DOMAIN>			<listme@dsbl.org>
*/

char *mailfrom(int num, const char *sender, const char *source, const char *domain) {
	static char buffer[512];

	switch (num) {
		case 0:
		case 1:
		case 2:
		case 3:
			firestring_snprintf(buffer,512,"<%s@%s>",sender,source);
			break;
		case 4:
		case 5:
		case 6:
		case 7:
			firestring_strncpy(buffer,"<nobody>",512);
			break;
		case 8:
		case 9:
		case 10:
		case 11:
			firestring_snprintf(buffer,512,"<%s!%s>",source,sender);
			break;
		case 12:
		case 13:
		case 14:
		case 15:
			firestring_strncpy(buffer,"<>",512);
			break;
		case 16:
		case 17:
		case 18:
		case 19:
		case 20:
		case 21:
		case 22:
		case 23:
		case 24:
		case 25:
		case 26:
			firestring_snprintf(buffer,512,"<nobody@%s>",domain);
			break;
		case 27:
		case 28:
			firestring_snprintf(buffer,512,"<%s!nobody>",domain);
			break;
		case 29:
			firestring_snprintf(buffer,512,"<postmaster@%s>",domain);
			break;
		case 30:
			firestring_snprintf(buffer,512,"<administrator@%s>",domain);
			break;
		case 31:
			firestring_snprintf(buffer,512,"<sales@%s>",domain);
			break;
		case 32:
			firestring_snprintf(buffer,512,"<info@%s>",domain);
			break;
		case 33:
			firestring_snprintf(buffer,512,"<webmaster@%s>",domain);
			break;
		case 34:
			firestring_snprintf(buffer,512,"<listserv@%s>",domain);
			break;
		default:
			buffer[0] = '\0';
			break;
	}
	return buffer;
}

char *rcptto(int num, const char *sender, const char *source, const char *domain, const char *target_user, const char *target_domain) {
	static char buffer[512];
	
	switch (num) {
		case 0:
		case 4:
		case 8:
		case 12:
		case 16:
		case 29:
		case 30:
		case 31:
		case 32:
		case 33:
		case 34:
			firestring_snprintf(buffer,512,"<%s@%s>",target_user,target_domain);
			break;
		case 1:
		case 5:
		case 9:
		case 13:
		case 17:
			firestring_snprintf(buffer,512,"<\"%s@%s\">",target_user,target_domain);
			break;
		case 2:
		case 6:
		case 10:
		case 14:
		case 18:
			firestring_snprintf(buffer,512,"%s@%s",target_user,target_domain);
			break;
		case 3:
		case 7:
		case 11:
		case 15:
		case 25:
		case 27:
			firestring_snprintf(buffer,512,"<%s!%s>",target_domain,target_user);
			break;
		case 19:
			firestring_snprintf(buffer,512,"<%s%%%s>",target_user,target_domain);
			break;
		case 20:
			firestring_snprintf(buffer,512,"<%s%%%s@%s>",target_user,target_domain,domain);
			break;
		case 21:
			firestring_snprintf(buffer,512,"<\"%s%%%s\">",target_user,target_domain);
			break;
		case 22:
			firestring_snprintf(buffer,512,"<%s@%s@%s>",target_user,target_domain,domain);
			break;
		case 23:
			firestring_snprintf(buffer,512,"<\"%s@%s\"@%s>",target_user,target_domain,domain);
			break;
		case 24:
			firestring_snprintf(buffer,512,"<@%s:%s@%s>",domain,target_user,target_domain);
			break;
		case 26:
		case 28:
			firestring_snprintf(buffer,512,"<%s!%s@%s>",target_domain,target_user,domain);
			break;
		default:
			buffer[0] = '\0';
			break;
	}
	return buffer;
}

int sendtest(int fd, char *intip, const char *cookie, int num, const char *sender, const char *source, const char *domain, const char *target_user, const char *target_domain, const char *message, const char *extra_info, int smtp_port) {
	char buffer[8192];
	char port_info[32];
	char *mf, *rt;
	int i;

	if (smtp_port == 25)
		port_info[0] = '\0';
	else
		firestring_snprintf(port_info,32,"SMTP Port: %d\r\n",smtp_port);

	mf = mailfrom(num,sender,source,domain);
	if (verbose)
		printf(">>> MAIL FROM:%s\n",mf);
	i = firestring_snprintf(buffer,8192,"MAIL FROM:%s\r\n",mf);
	if (send(fd,buffer,i,0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;
	
	rt = rcptto(num,sender,source,domain,target_user,target_domain);
	if (verbose)
		printf(">>> RCPT TO:%s\n",rt);
	i = firestring_snprintf(buffer,8192,"RCPT TO:%s\r\n",rt);
	if (send(fd,buffer,i,0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;

	firestring_strncpy(buffer,"DATA\r\n",8192);
	if (verbose)
		printf(">>> DATA\n");
	if (send(fd,buffer,6,0) != 6)
		return 100;

	i = getresponse(fd);
	if (i != 3)
		return 1;

	if (verbose)
		printf(">>> (message)\n");
	i = firestring_snprintf(buffer,8192,"%s\r\n"
                       "DSBL LISTME: smtp %s\r\n"
		       "%s\r\n"
		       "%s"
		       "%s"
		       "MAIL FROM:%s\r\n"
		       "RCPT TO:%s\r\n"
		       "DSBL END\r\n"
		       "\r\n"
		       "%s\r\n"
		       ".\r\n",headers,intip,cookie,port_info,extra_info,mf,rt,message);

	if (send(fd,buffer,i,0) != i)
		return 100;
	
	i = getresponse(fd);
	if (i != 2)
		return 1;

	return 0;
}

int reset(int fd) {
	if (verbose)
		printf(">>> RSET\n");
	if (send(fd,"RSET\r\n",6,0) != 6)
		return 100;
	getresponse(fd);
	return 0;
}

/* copy message contents while replacing bare linefeeds */
void copymessage(char *outbuf, const char *inbuf) {
	int l,o,i;
	l = strlen(inbuf);
	o = 0;
	for (i = 0; i < l; i++) {
		if (o > 8189)
			break;
		if (inbuf[i] == '\n') {
			outbuf[o++] = '\r';
			outbuf[o++] = '\n';
		} else
			outbuf[o++] = inbuf[i];
	}
	outbuf[o] = '\0';
}

/* try to authenticate using SMTP AUTH, LOGIN method */
int auth_login(int fd, const char *username, const char *password) {
	int i;
	char buffer[1024];

	i = firestring_snprintf(buffer, 1024, "AUTH LOGIN\r\n");
	if (verbose)
		printf(">>> AUTH LOGIN\n");
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 3)
		return 1;

	i = firestring_snprintf(buffer, 1024, "%s\r\n", username);
	if (verbose)
		printf(">>> %s\n", username);
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 3)
		return 1;

	i = firestring_snprintf(buffer, 1024, "%s\r\n", password);
	if (verbose)
		printf(">>> %s\n", password);
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;

	return i;
}

/* try to authenticate using SMTP AUTH, NTLM method */
int auth_ntlm(int fd, const char *username, const char *password) {
	int i;
	char buffer[1024];

	i = firestring_snprintf(buffer, 1024, "AUTH NTLM %s\r\n", username);
	if (verbose)
		printf(">>> AUTH NTLM %s\n", username);
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 3)
		return 1;

	i = firestring_snprintf(buffer, 1024, "%s\r\n", password);
	if (verbose)
		printf(">>> %s\n", password);
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;

	return i;
}

int auth_relaytest(struct sockaddr_in *host_addr, char *intip, int method, const char *auth_details,
	const char *username, const char *password, const char *cookie, int num, const char *sender_user,
	const char *sender_domain, const char *target_user, const char *target_domain,
	const char *msgbuf, int smtp_port) {
	int i, fd;
	char buffer[1024];

	fd = socket(PF_INET, SOCK_STREAM, 0);
	if (fd == -1)
		return 100;

	if (connect(fd, (struct sockaddr *)host_addr, sizeof(struct sockaddr_in)) != 0)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;

	i = firestring_snprintf(buffer, 1024, "EHLO %s\r\n", sender_domain);
	if (verbose)
		printf(">>> EHLO %s\n", sender_domain);
	if (send(fd, buffer, i, 0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;

	switch (method) {
		case AUTH_LOGIN:
			i = auth_login(fd, username, password);
			break;
		case AUTH_NTLM:
			i = auth_ntlm(fd, username, password);
			break;
		default:
			fprintf(stderr,"Bad SMTP AUTH method: %d\n",method);
			exit(100);
	}

	if (i == 2)
		i = sendtest(fd, intip, cookie, num, sender_user, sender_domain, "",
			target_user, target_domain, msgbuf, auth_details, smtp_port);

	if (verbose)
		printf(">>> QUIT\n");
	send(fd, "QUIT\r\n", 6, 0);
	(void)getresponse(fd);
	close(fd);

	return i;
}

/* return values:
 *   0 - host accepted some tests, may relay
 *   1 - host accepted no tests, won't relay
 *   2 - host appears to be blocking tester (or may just be seriously broken)
 *   3 - test timed out (alarm timer)
 * 100 - format or internal error
 */
int main(int argc, char **argv) {
	int fd;
	struct in_addr *in;
	struct sockaddr_in host_addr;
	char *cookie;
	char query[1024];
	int i;
	int ret = 1;
	char *tempchr;
	int test,domain;
	const char *sender_user,
		*sender_domain,
		*target_user,
		*target_domain;
	const char *message;
	char msgbuf[8192];
	char intip[16];
	int smtp_port = 25;

	setalarm();

	/* check for -v (verbose) option */
	if (argc == 3 && !strcmp(argv[1], "-v")) {
		verbose = 1;
		argv[1] = argv[2];
		argc--;
	}

	if (argc != 2) {
		fprintf(stderr,"Usage: %s [-v] <ip address>[:port]\n", argv[0]);
		exit(100);
	}

	/* See if a port has been specified, and if so, extract it */
	tempchr = strchr(argv[1],':');
	if (tempchr != NULL) {
		smtp_port = atoi(&tempchr[1]);
		tempchr[0] = '\0';
	}

	in = firedns_aton4(argv[1]);
	if (in == NULL) {
		fprintf(stderr,"Invalid IP\n");
		exit(100);
	}
	memcpy(&host_addr.sin_addr,in,sizeof(struct in_addr));
	firestring_strncpy(intip,firedns_ntoa4(in),16);

	readconf();

	sender_user = firestring_conf_find(config,"sender_user");
	if (sender_user == NULL) {
		fprintf(stderr,"sender_user not set in config.\n");
		exit(100);
	}

	sender_domain = firestring_conf_find(config,"sender_domain");
	if (sender_domain == NULL) {
		fprintf(stderr,"sender_domain not set in config.\n");
		exit(100);
	}

	target_user = firestring_conf_find(config,"target_user");
	if (target_user == NULL) {
		fprintf(stderr,"target_user not set in config.\n");
		exit(100);
	}

	target_domain = firestring_conf_find(config,"target_domain");
	if (target_domain == NULL) {
		fprintf(stderr,"target_domain not set in config.\n");
		exit(100);
	}

	message = firestring_conf_find(config,"message");
	if (message == NULL) {
		fprintf(stderr,"message not set in config.\n");
		exit(100);
	}

	copymessage(msgbuf,message);

	cookie = getcookie();

	/* initial domains */
	firestring_strncpy(domains[0],"localhost",256);
	firestring_strncpy(domains[1],"[127.0.0.1]",256);
	firestring_snprintf(domains[2],256,"[%s]",firedns_ntoa4(&host_addr.sin_addr));

	/* pull up to 3 domains from reverse */
	tempchr = firedns_resolvename4(&host_addr.sin_addr);
	if (tempchr != NULL)
		adddomain(tempchr);

	host_addr.sin_family = AF_INET;
	host_addr.sin_port = htons(smtp_port);

	test = 0;
	domain = 0;
	buildheaders(cookie,sender_domain,target_user,target_domain);

restart:

	connection_closed = 0;
	fd = socket(PF_INET, SOCK_STREAM, 0);
	if (fd == -1) {
		perror("host socket()");
		exit(100);
	}

	if (connect(fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr_in)) != 0) {
		perror("host connect()");
		exit(2);
	}

	i = getresponse(fd);
	if (i != 2) {
		fprintf(stderr,"Error on greeting: %d\n",i);
		exit(2);
	}

	i = firestring_snprintf(query,1024,"HELO %s\r\n",sender_domain);
	if (verbose)
		printf(">>> HELO %s\n",sender_domain);
	if (send(fd,query,i,0) != i) {
		perror("Error sending HELO");
		exit(2);
	}

	i = getresponse(fd);
	if (i != 2) {
		fprintf(stderr,"Error in HELO response: %d\n",i);
		exit(2);
	}

	for (; test < DOMAIN_TESTSTART; test++) {
		i = sendtest(fd,intip,cookie,test,sender_user,sender_domain,"",target_user,target_domain,msgbuf,"",smtp_port);
		if (i == 0)
			ret = 0;
		if (i == 1)
			reset(fd);
		if (i == 100 || connection_closed == 1) {
			if (verbose)
				printf("(connection error - reconnecting)\n");
			close(fd);
			test++;
			goto restart;
		}
	}

	for (; domain < domaincount; domain++) {
		/* Restart the alarm clock when we get here, to allow for slow connections */
		if (test == DOMAIN_TESTSTART) {
			if (verbose)
				printf("*** Resetting alarm timer\n");
			setalarm();
		}
		for (; test <= MAXTEST; test++) {
			i = sendtest(fd,intip,cookie,test,sender_user,sender_domain,domains[domain],target_user,target_domain,msgbuf,"",smtp_port);
			if (verbose)
				printf("*** Resetting alarm timer\n");
			setalarm();
			if (i == 0)
				ret = 0;
			if (i == 1)
				reset(fd);
			if (i == 100 || connection_closed == 1) {
				close(fd);
				test++;
				goto restart;
			}
		}
		test = DOMAIN_TESTSTART;
	}

	send(fd,"QUIT\r\n",6,0);
	if (verbose)
		printf(">>> QUIT\n");
	close(fd);

	/* Now try simple SMTP AUTH exploits */
	if (verbose)
		printf("*** Resetting alarm timer\n");
	setalarm();

	i = auth_relaytest(&host_addr, intip, AUTH_LOGIN, "AUTH LOGIN, user=/webmaster\r\n",
		"L3dlYm1hc3Rlcg==", "", cookie, 0, sender_user, sender_domain,
		target_user, target_domain, msgbuf, smtp_port);
	if (i == 0)
		ret = 0;

	i = auth_relaytest(&host_addr, intip, AUTH_LOGIN, "AUTH LOGIN, user=admin\r\n",
		"YWRtaW4=", "YWRtaW4=", cookie, 0, sender_user, sender_domain,
		target_user, target_domain, msgbuf, smtp_port);
	if (i == 0)
		ret = 0;

	i = auth_relaytest(&host_addr, intip, AUTH_LOGIN, "AUTH LOGIN, user=administrator\r\n",
		"YWRtaW5pc3RyYXRvcg==", "", cookie, 0, sender_user, sender_domain,
		target_user, target_domain, msgbuf, smtp_port);
	if (i == 0)
		ret = 0;

	i = auth_relaytest(&host_addr, intip, AUTH_LOGIN, "AUTH LOGIN, user=test\r\n",
		"dGVzdA==", "dGVzdA==", cookie, 0, sender_user, sender_domain,
		target_user, target_domain, msgbuf, smtp_port);
	if (i == 0)
		ret = 0;

	i = auth_relaytest(&host_addr, intip, AUTH_NTLM, "AUTH NTLM, anonymous\r\n",
		"TlRMTVNTUAABAAAAB4IAgAAAAAAAAAAAAAAAAAAAAAA=",
		"TlRMTVNTUAADAAAAAQABAEAAAAAAAAAAQQAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABBAAAABYIAAAA=",
		cookie, 0, sender_user, sender_domain, target_user, target_domain, msgbuf, smtp_port);
	if (i == 0)
		ret = 0;

	exit(ret);
}
