// ################################################################################  
// ## portscan - the multithreaded TCP port scanner
// ## Redistributions of source code must retain the above copyright notice
// ## DATE: 2011-09-22
// ## AUTHOR: SecPoint ApS  
// ## MAIL: info@secpoint.com  
// ## SITE: http://www.secpoint.com  
// ## Other scanners: http://www.secpoint.com/free-port-scan.html
// ## Other scanners: http://www.secpoint.com/free-vulnerability-scan.html
// ##
// ## LICENSE: BSD http://www.opensource.org/licenses/bsd-license.php
// ################################################################################  
// ## 1.0 initial release
// ## 1.1 you could use not only port range, but single ports and port lists (check -p option)
// ## 1.2 default port list is now not 1-1024, but is a list of frequently used ports; duplicates check
// ## 1.3 added more default ports
// ## 2.0 Added option -s for Syn Scan support
// ## 3.0 Reversed option -r; Added name lookup; Added -o options for output to file; Fixed minor bugs
// ## 4.0 Added mixed port mode: -p 3,4,20-28,80,125-135,8080


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/times.h>
#endif
#include "pscan.h"
#include "bestports.h"

extern long int bestPortsArray[];
extern char BR[];

#define SLEEP_TIME_MSEC			5
#define MAX_PORT_STANDARD		1024


#define ARP_TABLE_FILENAME		"./arp_table.txt"
#define OUI_FILENAME			"./oui.txt"
#define PORT_RES_FILENAME		"./port-numbers.txt"



typedef struct _port_instance
{
	struct _port_instance *next;
	struct _port_instance *prev;
	unsigned short port;
} port_instance;

int options = 0;
unsigned long wait_time = 1000; // msec
unsigned long num_instances = 10;
unsigned long IP;		// ip to scan
char *targethostname = NULL;
char targetip[16] = {0};
unsigned short start_port = 1;
unsigned short end_port = MAX_PORT_STANDARD;

static unsigned short current_port;
static unsigned short current_list_num=0;
unsigned short total_open;
unsigned short total_scanned;
static unsigned short total_ports;
static char *port_res_file;
static port_instance *oports = NULL;
long int* port_list;	//list of ports to scan
char *outfilename = NULL;
short outfiletype;
FILE *pfo = NULL;

int intcmp(const void *a, const void *b)
{
    return *(const int *)a - *(const int *)b;
}
 


static unsigned long get_time()
{
#ifdef WIN32
	return GetTickCount();
#else
	struct tms tm;
	return (unsigned long)times(&tm)*10;
#endif
}

static void argument_error()
{
	fprintf(stderr, "Argument error\n");
	exit(0);
}


static int parse_arguments(int argc, char *argv[])
{
	int i;
	char *p, *t, *t2;
	//int nPlast;
	int nP;
	int pcount;
	int nports = 10;
	int j;
	long int pNum;
	
	IP = net_get_iip(argv[1]); 
	if (IP == 0 || IP == 0xFFFFFFFF)	// INADDR_ANY or INADDR_NONE  
	{
		// assume that the string is a hostname; will try to resolve it
		if(!(IP = ResolveHostname(argv[1])))
		{
			fprintf(stderr, "Unable to resolve host name %s\n", argv[1]);
			exit(0);
		}
		else
			targethostname = argv[1];
	}
	strcpy(targetip, net_get_aip(IP));

	for (i = 2; i < argc; i++)
	{
		if (argv[i][0] != '-') continue;
		switch (argv[i][1])
		{
		case 'p':
			if (argv[i][2] != 0) p = &argv[i][2];
			else
			{
				i++;
				if (i == argc) argument_error();
				p = argv[i];
			}

			port_list = (long int *)malloc(sizeof(long int) * (nports + 1));
			port_list[0]=0;
			t = strtok(p, ",");
			j = 0;
			while(t)
			{
				// t is next port or port range
				if((t2 = strchr(t, '-')))
				{
					// is a port range
					start_port = atoi(t);
					end_port = atoi(t2+1);
					if (start_port == 0 || end_port == 0 || end_port < start_port)
					{
						fprintf(stderr, "Incorrect ports, starting = %d, ending = %d\n", start_port, end_port);
						exit(0);
					}
					for (nP=0; nP<= end_port-start_port ; nP++)
					{
						if(j == nports)
						{
						  nports += 10;
						  port_list = (long int*)realloc(port_list, sizeof(long int) * (nports + 1));
						}
						port_list[j++]=start_port+nP;
					}
					start_port = 0;
				}
				else
				{
					// is a single port
					if(j == nports)
					{
					  nports += 10;
					  port_list = (long int*)realloc(port_list, sizeof(long int) * (nports + 1));
					}
					pNum = atoi(t);
					port_list[j++] = pNum==0?-1:pNum; //if not number  -> -1
					start_port = 0;
				}
				t = strtok(0, ",");
			}
			port_list[j]=0;

			break;

		case 's':
			options |= OPTIONS_SYN_SCAN;
			break;

		case 'n':
			if (argv[i][2] != 0) p = &argv[i][2];
			else
			{
				i++;
				if (i == argc) argument_error();
				p = argv[i];
			}
			num_instances = atoi(p);
			break;

		case 'w':
			if (argv[i][2] != 0) p = &argv[i][2];
			else
			{
				i++;
				if (i == argc) argument_error();
				p = argv[i];
			}
			wait_time = atoi(p);
			break;

		case 'o':
			// check for -o -ox -oh
			if (argv[i][2] == 0)	// -o
				outfiletype = TEXTFILE;
			else if (argv[i][2] == 'x')
				outfiletype = XMLFILE;
			else if (argv[i][2] == 'h')
				outfiletype = HTMLFILE;
			else
				argument_error();

			// now read file name
			i++;
			if (i == argc) argument_error();
			outfilename = argv[i];
			break;

		case 'a':
			options |= OPTIONS_ALL_PORTS;
			break;

		case 'r':
			options |= OPTIONS_DONT_RESOLVE_PORTS;
			break;

		case 'M':
			options |= OPTIONS_RESOLVE_MAC;
			break;

		case 'h':
			options |= OPTIONS_ONFLY_HIDE;
			break;

		default:
			break;
		}
	}

	if (start_port!=0)	// taking best ports
	{
		port_list = bestPortsArray;
		start_port=-1;
	}
	//sorting and removing duplicates:
	pcount=0;
	while(port_list[pcount] != 0)  
		++pcount;

	qsort(port_list, pcount, sizeof(long int), intcmp);
	//will remove dups from tail of array one by one (replacing with -1)
	for (i=pcount; i>0 ; i-- )
	{
		if ((port_list[i]==port_list[i-1])  &&(port_list[i]!=-1))
		{
			fprintf(stdout, "Duplicate port found in list: %d\n",(int)port_list[i]);
			port_list[i]=-1;
		}
	}

	return pcount;
}


static void process_open_port(unsigned short port)
{
	if (!(options & OPTIONS_DONT_RESOLVE_PORTS))
	{
		char buff[16], *p;
		sprintf(buff, "%u/tcp", port);
		p = strstr(port_res_file, buff);
		if (!p) print_port(port, NULL, OPEN);
		else
		{
			char *t, tmp;
			int len = strlen(buff);
			p += len;
			t = p;
			while (*t == ' ') t++;
			p = t;
			while (*t != '\n') t++;
			tmp = *t;
			*t = 0;
			print_port(port, p, OPEN);
			*t = tmp;
		}
	}
	else print_port(port, NULL, OPEN);
}

static void save_open_port(unsigned short port)
{
	port_instance *pi, *ps = oports;

	pi = (port_instance *) malloc(sizeof(port_instance));
	pi->port = port;

	if (ps)
	{
		while (ps->next)
		{
			if (ps->port > port) 
				break;
			ps = ps->next;
		}

		if (!ps->next && ps->port < port)
		{
			ps->next = pi;
			pi->prev = ps;
			pi->next = NULL;
			return;
		}
		
		if (!ps->prev)
		{
			ps->prev = pi;
			pi->next = ps;
			pi->prev = NULL;
			oports = pi;
		}
		else
		{
			pi->prev = ps->prev;
			pi->prev->next = pi;
			pi->next = ps;
			ps->prev = pi;
		}
	}
	else 
	{
		oports = pi;
		oports->next = NULL;
		oports->prev = NULL;
	}
}


void on_response(port_response resp, unsigned short port)
{
	switch (resp)
	{
	case CLOSED:
		if (options & OPTIONS_ALL_PORTS && !(options & OPTIONS_ONFLY_HIDE)) 
			//fprintf(stdout, "%u/tcp closed\n", port);
			print_port(port, NULL, CLOSED);
		break;

	case OPEN:
		if (options & OPTIONS_ONFLY_HIDE)
			save_open_port(port);
		else 
			process_open_port(port);
		total_open++;
		break;

	case TIMEDOUT:
		if (options & OPTIONS_ALL_PORTS && !(options & OPTIONS_ONFLY_HIDE)) 
			//fprintf(stdout, "%u/tcp timedout\n", port);
			print_port(port, NULL, CLOSED);
		break;

	default:
		break;
	}

	fflush(stdout);
}

void add_next_port()
{
	total_scanned++;

	if (port_list[current_list_num]!=0)
	{
		while (port_list[current_list_num]==-1)
		{
			current_list_num++;
			if (port_list[current_list_num]==0) break;//last port in list
		}
		net_add(IP, (unsigned short)port_list[current_list_num], get_time());
		current_port++;
		current_list_num++;
	}
}


void ConnectScan()
{
	unsigned long i;

	struct_init(on_response, wait_time);

	total_open = 0;
	total_ports = end_port - start_port + 1;
	current_port = start_port;
	for (i = 0; i < num_instances; i++)
	{
		if (port_list[current_list_num]==0)
			break;	//last port in list
		while (port_list[current_list_num]==-1)
		{
			current_list_num++;
			if (port_list[current_list_num]==0)
				break;	//last port in list
		}
		net_add(IP, (unsigned short) port_list[current_list_num], get_time());
		current_port++;
		current_list_num++;
	}

	fflush(stdout);

	while (net_check(get_time())) 
	{
#ifdef WIN32
		Sleep(SLEEP_TIME_MSEC);
#else
		usleep(SLEEP_TIME_MSEC * 1000);
#endif
	}
	
	net_uninit();
}

char *read_file(char *file)
{
	FILE *f;
	long size;
	char *buff;
	int r;

	f = fopen(file, "rb");
	if (!f)
	{
		fprintf(stderr, "Failed to open file \"%s\"\n", file);
		exit(0);
	}

	fseek(f, 0, SEEK_END);
	size = ftell(f);
	fseek(f, 0, SEEK_SET);
	buff = (char *) malloc((unsigned int)size);

	r = fread(buff, 1, (unsigned int)size, f);
	fclose(f);

	return buff;
}

void resolve_MAC(char *mac, char *buff)
{
	int i;
	char buf2[256];
	char *p, *b = read_file(OUI_FILENAME);
	mac[8] = 0;
	for (i = 0; i < 8; i++) if (mac[i] >= 'a' && mac[i] <= 'f') mac[i] &= 0xDF;
	p = strstr(b, mac);
	if (p)
	{
		char *t;
		p += 18;
		t = p;
		while (*t != '\n') t++;
		*t = 0;
		sprintf(buf2, " %s%s", p, BR);
	}
	else
		sprintf(buf2, "Adapter manufacturer: UNKNOWN%s", BR);

	strcat(buff, buf2);
	free(b);
}

void print_MAC()
{
	int r;
	char buff[512], ips[16], *p, *b;

	strcpy(ips, net_get_aip(IP));

#ifdef WIN32
	sprintf(buff, "arp -a %s > "ARP_TABLE_FILENAME, ips);
#else
	sprintf(buff, "arp -a | grep %s | awk '{ print $4 }' > "ARP_TABLE_FILENAME, ips);
#endif
	r = system(buff);

	b = read_file(ARP_TABLE_FILENAME);
#ifdef WIN32
	p = strstr(b, ips);
#else
	for (p = b; *p; p++) 
	{
		if (*p == ':') *p = '-';
		if (*p == '\n') *p = 0;
	}
	p = b;
#endif
	if (!p)
		sprintf(buff, "ARP table contains no information about IP: %s%sMAC address cannot be resolved!%s", ips, BR, BR);
	else
	{
#ifdef WIN32
		char *t;
		while (*p != ' ') p++;
		t = p;
		while (*t == ' ') t++;
		p = t;
		while (*t != ' ') t++;
		*t = 0;
#endif
		sprintf(buff, "MAC Address: %s", p);
		resolve_MAC(p, buff);
	}
	free(b);

	print_mac(buff);

#ifdef WIN32
	system("erase "ARP_TABLE_FILENAME);
#else
	r = system("rm "ARP_TABLE_FILENAME);
#endif
}

int main(int argc, char *argv[])
{
	int PortCount;
	unsigned long time;

	print_general(stdout);

	if (argc < 2)
	{
		print_help();
		return 0;
	}

	net_init();

	PortCount = parse_arguments(argc, argv);

	output_init();

	if (!(options & OPTIONS_DONT_RESOLVE_PORTS))
		port_res_file = read_file(PORT_RES_FILENAME);

	if(outfilename)
		print_general_to_file();

	print_config();

	total_scanned = 0;
	time = get_time();

	if (options & OPTIONS_SYN_SCAN)
		SynScan(targetip, PortCount);
	else
		ConnectScan();

	time = get_time() - time;

	if (options & OPTIONS_ONFLY_HIDE)
	{
		port_instance *tmp, *pi = oports;
		while (pi)
		{
			tmp = pi->next;
			process_open_port(pi->port);
			free(pi);
			pi = tmp;
		}
	}


	if (!(options & OPTIONS_DONT_RESOLVE_PORTS))
		free(port_res_file);

	print_totals(time);

	if (options & OPTIONS_RESOLVE_MAC)
		print_MAC();

	output_end();
	net_end();

	return 0;
}
