/*
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"

struct s_socks5_auth {
#define SOCKS5_VERSION 0x05
	unsigned char version;
	unsigned char nmethods;
#define SOCKS5_METHOD_NONE 0x00
	unsigned char methods[64];
};

struct s_socks5_auth_response {
	unsigned char version;
	unsigned char method;
};

#define SOCKS5_LENGTH 10
struct s_socks5_request {
#define SOCKS5_VERSION 0x05
	unsigned char version;
#define SOCKS5_COMMAND_CONNECT 0x01
	unsigned char command;
	unsigned char reserved;
#define SOCKS5_ADDRESSTYPE_IPV4 0x01
	unsigned char addresstype;
	unsigned char ip[4];
	unsigned short port;
};

char headers[4096];

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

	tempchr = memchr(buffer,'\n',bufpos);
	while (tempchr == NULL) {
		i = recv(fd,&buffer[bufpos],4095 - 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 == 4095) /* line too long (hostile act) */
			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 */
			return 9;
		if (strlen(line) >= 4 && line[3] == '-')
			continue;
		else if (line[0] == '1')
			return 1;
		else if (line[0] == '2')
			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 SOCKS 5 Proxy 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);
}

int sendtest(int fd, const char *intip, int port, struct in_addr *remoteip, char *cookie, const char *sender, const char *source, const char *target_user, const char *target_domain) {
	char buffer[8192];
	int i;

	i = firestring_snprintf(buffer,8192,"HELO [%s]\r\n",firedns_ntoa4(remoteip));
	if (send(fd,buffer,i,0) != i)
		return 100;
	
	i = getresponse(fd);
	if (i != 2)
		return 2;

	i = firestring_snprintf(buffer,8192,"MAIL FROM:<%s@%s>\r\n",sender,source);
	if (send(fd,buffer,i,0) != i)
		return 100;

	i = getresponse(fd);
	if (i != 2)
		return 1;
	
	i = firestring_snprintf(buffer,8192,"RCPT TO:<%s@%s>\r\n",target_user,target_domain);
	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 (send(fd,buffer,6,0) != 6)
		return 100;

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

	i = firestring_snprintf(buffer,8192,"%s\r\n"
                       "DSBL LISTME: socks5 %s\r\n"
		       "%s\r\n"
		       "Port %d, Connect to %s\r\n"
		       "DSBL END\r\n"
		       ".\r\n",headers,intip,cookie,port,firedns_ntoa4(remoteip));
	if (send(fd,buffer,i,0) != i)
		return 100;
	
	i = getresponse(fd);
	if (i != 2)
		return 1;

	i = firestring_snprintf(buffer,8192,"QUIT\r\n");
	if (send(fd,buffer,i,0) != i)
		return 100;

	return 0;
}

int testsocks5(char *cookie, const char *intip, struct sockaddr_in *socksaddr, const char *sourceuser, const char *sourcedomain, struct in_addr *remoteip, const char *target_user, const char *target_domain) {
	struct s_socks5_auth a;
	struct s_socks5_auth_response ar;
	struct s_socks5_request r;
	int i;
	int f;

	a.version = SOCKS5_VERSION;
	a.nmethods = 1;
	a.methods[0] = SOCKS5_METHOD_NONE;

	r.version = SOCKS5_VERSION;
	r.command = SOCKS5_COMMAND_CONNECT;
	r.port = htons(25);
	memcpy(&r.ip,remoteip,sizeof(struct in_addr));
	r.reserved = 0x00;
	r.addresstype = SOCKS5_ADDRESSTYPE_IPV4;

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

	if (connect(f,(struct sockaddr *)socksaddr,sizeof(struct sockaddr_in)) != 0)
		return 2;

	if (send(f,&a,3,0) != 3) {
		close(f);
		return 2;
	}

	if (recv(f,&ar,sizeof(ar),0) != sizeof(ar)) {
		close(f);
		return 2;
	}

	if (ar.method != SOCKS5_METHOD_NONE) {
		close(f);
		return 1;
	}

	if (send(f,&r,SOCKS5_LENGTH,0) != SOCKS5_LENGTH) {
		close(f);
		return 2;
	}

	if (recv(f,&r,SOCKS5_LENGTH,0) != SOCKS5_LENGTH) {
		close(f);
		return 2;
	}

	if (r.command != 0x00) {
		close(f);
		return 1;
	}

	i = getresponse(f);
	if (i != 2) {
		close(f);
		return 2;
	}

	i = sendtest(f, intip, ntohs(socksaddr->sin_port), remoteip, cookie, sourceuser, sourcedomain, target_user, target_domain);

	close(f);
	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)
 * 100 - format or internal error
 */
int main(int argc, char **argv) {
	struct in_addr *in;
	struct sockaddr_in socks_addr;
	char *cookie;
	char intip[16];
	int ret = 1;
	char *tempchr;
	const char *sender_user,
		*sender_domain,
		*target_user,
		*target_domain;

	setalarm();

	if (argc < 3) {
		fprintf(stderr,"Usage: %s <socks ip> <socks port>\n",argv[0]);
		exit(100);
	}

	in = firedns_aton4(argv[1]);
	if (in == NULL) {
		fprintf(stderr,"Invalid IP\n");
		exit(100);
	}
	memcpy(&socks_addr.sin_addr,in,sizeof(struct in_addr));
	socks_addr.sin_family = AF_INET;
	socks_addr.sin_port = htons(atoi(argv[2]));
	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);
	}

	cookie = getcookie();

	tempchr = firedns_resolvemx(target_domain);
	if (tempchr == NULL) {
		fprintf(stderr,"Target domain has no mail exchanger\n");
		exit(100);
	}
	in = firedns_resolveip4(tempchr);
	if (in == NULL) {
		fprintf(stderr,"Target domain MX has no corresponding A record\n");
		exit(100);
	}

	buildheaders(cookie,sender_domain,target_user,target_domain);

	if (testsocks5(cookie,intip,&socks_addr,sender_user,sender_domain,in,target_user,target_domain) == 0)
		ret = 0;

	in = firedns_aton4("127.0.0.1");

	if (testsocks5(cookie,intip,&socks_addr,sender_user,sender_domain,in,target_user,target_domain) == 0)
		ret = 0;

	exit(ret);
}
