/* === trace's port scanner ===
 *
 *  compile:
 *    linux:  gcc -o pscan pscan.c -lpthread
 *    BSD:    gcc -o pscan pscan.c -pthread
 *
 * usage:
 * 	./pscan host [min_port max_port]
 * 		
 * 		if min_port and max_port is not specified,
 * 		pscan checks ports from /etc/services
 * 		
 * 		-( trace@dump.cz )-
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <pthread.h>

#define MAX_THREADS 100
#define HTABLE_SIZE 200

#define HASH(x) ((x) % HTABLE_SIZE)
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

/* global variables.. what a dirty code ;) ...who cares */
char *hostname;
int max_port;
int port = 0;
int stdports = 0;	 

struct service {
	char name[16];
	int port;
	struct service *next;
};

struct service *hash_table[HTABLE_SIZE] = { NULL };

void add_service(int port, char *name) {
	struct service *new_item; 
	new_item = (struct service *) malloc(sizeof(struct service));	
	memcpy(new_item->name, name, MIN(strlen(name), sizeof(new_item->name)-1));
	new_item->port = port;
	new_item->next = hash_table[HASH(port)];
	hash_table[HASH(port)] = new_item;
}

struct service *lookup(int port) {
	struct service *ptr;

	ptr = hash_table[HASH(port)];
	while (ptr != NULL) {
		if (port == ptr->port)
			return ptr;
		ptr = ptr->next;
	}
	return NULL;
}

void load_ports();
void *check_port(void *arg);
void *check_service(void *arg);

int main(int argc, char *argv[]) {
	int i = 0;	
	pthread_t thread_id;
	void * (*check_function)(void *);
	
	stdports = (argc == 2) ? 1 : 0;
	
	if (argc<4 && !stdports) {
		printf("trace's port scanner\n\tusage: pscan host [min_port max_port]\n");
		exit(-1);
	}
	
	load_ports();
	
	hostname = argv[1];
	if (!stdports) {
		port =atoi(argv[2]);
		max_port = atoi(argv[3]);
		check_function = check_port;
	} else
		check_function = check_service;
	
	if (gethostbyname(hostname) == NULL) {
		fprintf(stderr, "Connection to \"%s\" refused...\n", hostname);
		exit(1);
	}
	
	/* create threads */
	for (;i<MAX_THREADS;i++) {
		pthread_create(&thread_id, NULL, check_function, NULL);
		usleep(200);
	}
		
	pthread_exit(0);
}

/* load tcp ports and it's names from /etc/services to hash table */
void load_ports() {
	FILE *services;
	char line[256] = {0};
	char name[16] = {0};
	char port[6] = {0};
	char proto[4] = {0};
	char *ptr;
	int i;
	int lastport = 0, actualport;

	if ((services = fopen("/etc/services", "r")) == NULL) {
		perror(NULL);
		exit(1);
	}
		
	while (fgets(line, sizeof(line), services)!=NULL) {
		if (line[0] == '#')	/* ommit comments */
			continue;
		ptr = line;
		i = 0;	

		/* get service name */
		while (*ptr && !isspace(*ptr) && i<sizeof(name))
			name[i++] = *(ptr++);
	
		/* skip spaces */
		while (isspace(*ptr))
			ptr++;
		
		i = 0;
		/* get port number */
		while (*ptr && *ptr!='/' && i<sizeof(port))
			port[i++] = *(ptr++);
	
		ptr++;
		i = 0;
		/* get service protocol */
		while (*ptr && !isspace(*ptr) && i<sizeof(proto)-1)
			proto[i++] = *(ptr++);
	
		if (!strcmp(proto, "tcp")) {	
			actualport = atoi(port);
			if (actualport != lastport)
				add_service(actualport, name);
			lastport = actualport;
		}

		memset(line, 0, sizeof(line));		
		memset(name, 0, sizeof(name));
		memset(port, 0, sizeof(port));
		memset(proto, 0, sizeof(proto));
	}
	fclose(services);
}

void *check_port(void *arg) {
	int sock;
	struct sockaddr_in host;
	int myport = port++;
	struct hostent *he;
	struct service *ptr;
	char service[20];
	
	if ((he = gethostbyname(hostname)) == NULL) {
		perror(NULL);
		pthread_exit(NULL);
	}
	
	memset(&host, 0, sizeof(struct sockaddr_in));
	memcpy(&host.sin_addr, he->h_addr_list[0], sizeof(in_addr_t));
	host.sin_family = AF_INET;
	
	while (myport <= max_port) {	
		sock = socket(PF_INET, SOCK_STREAM, 0);
		host.sin_port = htons(myport);
		
		if (!connect(sock, (struct sockaddr *)&host, sizeof(host))) {
			if ((ptr = lookup(myport)) != NULL) 
				snprintf(service, sizeof(service), "[%s]\n", ptr->name);
			else 
				sprintf(service, "\n");
			printf("Port %d is opened.. %s", myport, service);
		}

		close(sock);
		myport += MAX_THREADS;
	}
	pthread_exit(0);
}

void *check_service(void *arg) {
	int sock;
	struct sockaddr_in host;
	int position = port++;  /* i use "port" but it's a position in hash_table */
	struct hostent *he;
	struct service *ptr;
	
	he = gethostbyname(hostname);
	
	memset(&host, 0, sizeof(struct sockaddr_in));
	memcpy(&host.sin_addr, he->h_addr_list[0], sizeof(in_addr_t));
	host.sin_family = AF_INET;
	
	while (position < HTABLE_SIZE) {	
		ptr = hash_table[position];

		while (ptr != NULL) {
			if (ptr->port <= 0)
				continue;

			sock = socket(PF_INET, SOCK_STREAM, 0);
			host.sin_port = htons(ptr->port);
			
			if (!connect(sock, (struct sockaddr *)&host, sizeof(host))) 
				printf("Port %d is opened.. [%s]\n", ptr->port, ptr->name);

			close(sock);
			ptr = ptr->next;
		}
		position += MAX_THREADS;
	}

	pthread_exit(0);
}



