/* ein erweiterter Portscanner.
 * Er versucht mit einem ICMP_ECHO zu schauen ob die Hosts up sind. Das geht aber komischerweise
 * manchmal in die Hose und die Hosts antworten nicht. Deshalb noch einen Timeout, nach dem der
 * Host als tot angenommen wird. Als down zählt also alles das nicht innerhalb vom Timeout ein
 * ICMP_ECHOREPLY liefert. Wem das nicht gefällt muß es nicht verwenden - ist eine optionale
 * Fähigkeit die mit --hostup angestellt werden kann. Benötigt wegen Raw Sockets Root-Rechte.
 *
 * Fragen zur Funktion an <maverick@dragonriders.de> - bitte keine Bugreports.
 */

#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#define MIN_PORT	0x0002	/* 2     */
#define MAX_PORT	0xfffe  /* 65534 */
#define BUF_SIZ		0x0400	/* 1024	 */
#define TIMEOUT		0x001e	/* 30	 */

u_long	start_addr;
u_long	end_addr;
u_short	start_port;
u_short end_port;
int	test_up;

void alrm(int sig)
{
	/* Leere Funktion - soll einfach nur das Signal abfangen */
}

int read_commandline(int len, char **args)
{
	int		i;
	struct hostent 	*host;
	
	start_addr = 0;
	end_addr = 0;
	test_up = 0;
	start_port = htons(MIN_PORT);
	end_port = htons(MAX_PORT);
	
	for(i = 1; i < len; i++)
	{
		if(!strcmp(args[i], "--portrange") && (i + 2 <= len))
		{
			start_port = htons(atol(args[i+1]));
			end_port = htons(atol(args[i+2]));
			i+=2;
			continue;
		}
		
		if(!strcmp(args[i], "--hostup"))
		{
			if(geteuid())
			{
				printf("not root\n");
				return -1;
			}
			else
			{
				fprintf(stderr, "WARNING: --hostup may cause funny bevaviour! Just have a try and watch :-)\n");
				test_up = 1;
			}
				
			continue;
		}
		
		if(!strcmp(args[i], "--iprange") && (i + 2 <= len))
		{
			start_addr = inet_addr(args[i+1]);
			end_addr = inet_addr(args[i+2]);
			i+=2;
			continue;
		}
		else if (i == 1)
		{
			printf("<%s>\n", args[i]);
			host = gethostbyname(args[i]);
			if (!host)
			{
				perror("gethostbyname() failed");
				return -1;
			}
			start_addr = *(u_long*)host->h_addr;
			end_addr = start_addr;
		}
		
	}
	
	if(ntohs(start_port) > ntohs(end_port))
	{
		fprintf(stderr, "start_port > end_port!\n");
		return -1;
	}
	
	if(ntohl(start_addr) > ntohl(end_addr))
	{
		fprintf(stderr, "start_addr > end_addr!\n");
		return -1;
	}
	
	return 0;
}

int tell_todo(void)
{
	struct in_addr 	addr;
	
	if(start_addr == end_addr)
	{
		addr.s_addr = start_addr;
		printf("host:\t%s\n", inet_ntoa(addr));
	}
	else
	{
		addr.s_addr = start_addr;
		printf("hosts\tstart: %s\t", inet_ntoa(addr));
		addr.s_addr = end_addr;
		printf("end: %s\n", inet_ntoa(addr));
	}
	printf("ports\tstart: %i\t\tend: %i\n", ntohs(start_port), ntohs(end_port));
	
	return 0;
}

/* Hier schauen wir einfach mal ob der Host auf ICMP_ECHO reagiert */
int host_up(u_long addr)
{
	struct sockaddr_in 	in;
	int 			s, bytes;
	struct icmphdr		*icmp;
	char 			buffer[BUF_SIZ];
	struct timeval		tv;
	fd_set			fds;
	
	if(!test_up)
		return 1;
		
	in.sin_family = AF_INET;
	in.sin_port = 0;
	in.sin_addr.s_addr = addr;
	
	icmp = (struct icmphdr*) buffer;
	
	/* ein Echo-Paketchen ist schnell gebastelt ... */
	icmp->type = ICMP_ECHO;
	icmp->code = 0;
	icmp->checksum = 0;
	icmp->un.echo.id = getpid();	/* damit wir es nicht verwechseln */
	icmp->un.echo.sequence = 0;
	
	s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (s == -1)
	{
		perror("socket() failed");
		return 0;
	}
	
	if (sendto(s, buffer, sizeof(struct icmphdr), 0, &in, sizeof(in)) == -1)
	{
		perror("sendto() failed");
		return 0;
	}
	
	alarm(TIMEOUT);
	
	/* lustigerweise kehrt nur select() nach einem Signal mit errno==EINTR zurück.
	 * das recv() macht einfach da weiter wo es unterbrochen wurde und blokciert
	 * fröhlich weiter. Dumm aber wahr. Man könnte das Problem auch mit long jumps
	 * lösen, aber dazu war ich zu faul ;-)
	 */
	for(;;)
	{
		FD_ZERO(&fds);
		FD_SET(s, &fds);
		tv.tv_sec = 60;
		tv.tv_usec = 0;
		select(s + 1, &fds, 0, 0, &tv);
		if(errno == EINTR)
		{
			printf("timed out\n");
			return 0;
		}
		
		if(!FD_ISSET(s, &fds))
			continue;
			
		bytes = recv(s, buffer, sizeof(buffer), 0);
		if(bytes == -1)
		{
			perror("recv() failed");
			close(s);
			return 0;
		}
		
		icmp = (struct icmphdr*) (buffer + sizeof(struct iphdr));
		if((icmp->type == ICMP_ECHOREPLY) && (icmp->un.echo.id == getpid()))
		{
			close(s);
			return 1;
		}
		
		if((icmp->type != ICMP_ECHO) && (icmp->type != ICMP_ECHOREPLY))
		{
			close(s);
			return 0;
		}
	}
	
	close(s);
	
	return 0;
}

int do_it(void)
{
	struct sockaddr_in 	in;
	u_long 			addr;
	u_short 		port;
	int 			s;
	struct servent 		*serv;
	
	for(addr = ntohl(start_addr); addr <= ntohl(end_addr); addr++)
	{
		in.sin_addr.s_addr = htonl(addr);
		in.sin_family = AF_INET;
		printf("scanning %s...\n", inet_ntoa(in.sin_addr));
		if(!host_up(htonl(addr)))
		{
			printf("host down\n");
			continue;
		}
		
		for(port = ntohs(start_port); port <= ntohs(end_port); port++)
		{
			in.sin_port = htons(port);
			if ( (s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
			{
				perror("socket() failed");
				return -1;
			}
			if (!connect(s, &in, sizeof(in)))
			{
				serv = getservbyport(htons(port), "tcp");
				if (serv)
					printf("%i (%s)\n", port, serv->s_name);
				else
					printf("%i (unknown)\n", port);
			}
			
			close(s);
		}
	}
	
	return 0;	
}

int main(int argc, char *argv[])
{
	puts("One of Mavericks advanced portscanner, version 0.9");
	puts("bugreports to /dev/null");
	
	if (argc < 2)
	{
		fprintf(stderr, "usage: %s <host> {--portrange <start-port> <end-port>}\n", argv[0]);
		fprintf(stderr, "       %s --iprange <start-ip> <end-ip> {--portrange ...}\n", argv[0]);
		fprintf(stderr, "       additional option: --hostup. root required. experimental.\n");
		return 1;
	}
	
	signal(SIGALRM, alrm);
	
	if (read_commandline(argc, argv) == -1)
		return 2;
	
	tell_todo();
	
	if (do_it() == -1)
		return 3;
	
	return 0;
}

