/*
 *      idlescan
 *      Copyright (C) 1999 LiquidK <liquidk@superbofh.org>
 *
 *      You are free to do anything with this code as long as proper credit is given
 *
 *
 *      Implementation of a tcp port scanner by spoffing and detecting volume of outgoing traffic
 *   on a series of relay hosts (aka idle scanning or ip.id scanning).
 *      Linux only implementation for now... And its buggy. yes :)
 *
 *      Credits go to antirez for the concept of the scan 
 *      http://www.securityfocus.org/templates/archive.pike?list=1&date=1998-12-15&msg=19981218074757.A990@seclab.com 
 *      
 *
 *      0.1 - alpha2:
 *        - corrected stupid bug that prevented compilation in machines without glibc2.1
 *
 *      requires..
 *      libnet: www.packetfactory.net
 *      libpcap: <included>
 */

#define VERSION "0.1-alpha2"

#include "global.h"

#include "capture.h"
#include "iface.h"
#include "packet.h"
#include <libnet.h>
#include <netdb.h>

#define MAX_FAILURES 5 /* after MAX_FAILURES the sensor is dropped */
#define SYNS_BURST 3   /* Number of syns to send */

#define DEFAULT_PORTS_STRING "1-1024" /* pretty obvious hum ? */
#define DEFAULT_PASSES 2
#define DEFAULT_WAIT_PKT 2            /*   Pause between sending the syn and checking the ip->id.
					 This should be greater than the time it takes for a packet
				         to travel  from your machine, to the target machine plus the
					 reply bouncing to the sensor, minus half your rtt to the
					 sensor.
				      */
#define PROBE_TIMEOUT 5               /* timeout */

char *option_ports_string = NULL;
int   option_debug_level  = 0;
int   option_passes       = DEFAULT_PASSES;
int   option_wait_pkt     = DEFAULT_WAIT_PKT;


int ports_scanned = 0;

/*
  TODO
  - several probe types (icmp echo, tcp packets with various flag combinations, etc...)
  - several scan types, (fin/ack scan, for being able to use laserjets as sensors, which
    reply to resets, duh! ;))
 */


int my_ip;
int sock;
struct sensor_list *sensor_list;
plist *port_list;
int dest_ip;

int seq = 0;

packet_send_t packet_send;
packet_check_t packet_check;

static sigjmp_buf jmpbuf;


void do_timer();

int byteswap16(int x)
{
    return ( (x & 0xFF00) >> 8  | (x & 0x00FF) << 8 );
}


void banner()
{
    printf("\nidlescan v%s by LiquidK <liquidk@superbofh.org>\n\n", VERSION);
}

void usage()
{
    printf("usage: idlescan sensors victim [ -p port range ] [ -d debug level ]\n");
    printf("sensors = comma separated list of sensor hosts\n\n");
}


void debug( int debug_level, const char *format, ... )
{
    va_list ap;
    char my_format[1024];

    if (debug_level > option_debug_level)
        return;

    strncpy(my_format, format, sizeof(my_format));
    strncat(my_format, "\n", sizeof(my_format) - strlen(format) );

    va_start( ap, format);
    vfprintf( stderr, my_format, ap);
    va_end(ap);
}


void sig_alarm(int signo)
{
    debug(5, "reached interrupt routine");
    do_timer();
    alarm(1);
}


/*
 *   plist *new_plist(char *)
 *   receives: a string with port ranges  (usual format, ie: 10-20,25,50-60 )
 *   returns:  a pointer to a data structure representing the port range
 *
 *   parses a string with port ranges and returns a reference to a structure with that info
 *
 */
plist *new_plist(char *port_string)
{
    plist *port_list;
    struct port_node *node = NULL;
    char *s, *p;
    int end = 0;

    port_list = (plist *)malloc(sizeof(plist));
    port_list->head = NULL;


    port_list->total = 0;

    for(s = strtok(port_string, ","); s; s = strtok(NULL, ",")) {

	/* reserve some memory */
	node = (struct port_node *)malloc(sizeof(struct port_node) );

	/* is it a range ? */
	if(p = strchr(s,'-') ) {
	    *p = '\0';
	    node->end = atoi(p + 1);
	} else {
	    node->end = atoi(s);
	}



	node->begin = atoi(s);
	node->next = NULL;

	/* insert this into the list */
	if(!port_list->head)
	    port_list->head = node;
	else
	    port_list->current->next = node;
	    
	port_list->current = node;

	port_list->total += node->end - node->begin + 1;

    }

    port_list->current = port_list->head;
    return port_list;
}


/*
 *   returns the next port in the plist simulating s stack
 */
int pop_port(plist *plist)
{
    int port;

    if(!plist || !plist->current)
	return 0;

    if(plist->current->begin == plist->current->end) {
	port = plist->current->begin;
	plist->current = plist->current->next;
	return port;
    } else {
	return plist->current->begin++;
    }
}

/*
 *    pushes a port back into a plist structure
 */

void push_port(plist *plist, int port)
{
    struct port_node *pnode;

    if(!plist || !plist->current)
	return;

    pnode = (struct port_node *)malloc(sizeof(struct port_node));

    pnode->next = plist->current->next;
    pnode->begin = plist->current->begin;
    pnode->end = plist->current->end;
    
    plist->current->next = pnode;
    plist->current->begin = port;
    plist->current->end = port;
}


struct sensor_list *new_sensor_list(char *host_string)
{
    char *s, *p;
    struct sensor_list *hl=NULL;
    struct sensor_list *phl=NULL;
    int end=0;

    s = host_string;
    p = s;
    do {
	s = p;
	while(*p && *p != ',')
	    p++;

	if(!*p)
	    end = 1;
	*p++ = '\0';


	if(!hl) {
	    hl = malloc(sizeof(struct sensor_list) );
	    phl = hl;
	} else {
	    phl->next = malloc(sizeof(struct sensor_list) );
	    phl = phl->next;
	}

	phl->hostname = strdup(s);
	phl->ip = libnet_name_resolve(s,1);
	phl->next = NULL;
    } while(!end);

    return hl;
}

/* send SYNS_BURST syns to the destination, could have been macro */
void send_scan(struct sensor_list *h, int dest_ip, int seq, int id)
{
    int n;
    if(!h->port)
	return;
    for(n = 1 ;n <= SYNS_BURST;n++)
	send_syn(sock, h->ip, dest_ip, h->port);

    h->seq = seq;
}


void drop_sensor(struct sensor_list *h)
{
    push_port(port_list, h->port);
    h->st = HOST_ST_END;
    h->port = 0;
    fprintf(stderr, "\rdropping %s, too many failures\n", h->hostname);
}


int port_open(struct sensor_list *h)
{
    struct servent *se;

    ports_scanned++;
    setservent(1);

    se = getservbyport(ntohs(h->port), "tcp");

    printf("\r %5u  %s\n", h->port, se ? se->s_name : "unknown");

    printf("\r(%3u\%)\r", ports_scanned * 100 / port_list->total);
    fflush(stdout);


    h->failures = 0;
    h->passes = 0;
    h->port = pop_port(port_list);

    if(!h->port)
	h->st = HOST_ST_END;

    return h->port;
}

int port_closed(struct sensor_list *h)
{
    ports_scanned++;
    printf("\r(%3u\%)\r", ports_scanned * 100 / port_list->total);
    fflush(stdout);

    h->passes = 0;
    h->failures = 0;
    h->port = pop_port(port_list);

    if(!h->port)
	h->st = HOST_ST_END;

    return h->port;
}


/*
 * void do_timer()
 *
 *   Called every second... basically just sends packets and increments the seq
 * which is our notion of time passing..
 *   Warn: This has lots of mutual exclusion problems!! This should
 * a thread!!
 */

void do_timer()
{
    struct sensor_list *h;

    seq++;

    for(h = sensor_list; h; h=h->next) {
	switch(h->st) {
	case HOST_ST_SEND_PROBE:
	    if (seq >= h->seq + option_wait_pkt) {
		debug(5, "%s in send probe state", h->hostname);
		packet_send(sock, my_ip, h->ip, getpid(), seq);
		h->seq = seq;
		h->st = HOST_ST_WAIT_PROBE;
	    }
	    break;
	    /* if too much time in st_wait_probe then the packet was lost... send it again */
	case HOST_ST_WAIT_PROBE:
	case HOST_ST_INIT:
	    if (seq >= h->seq + PROBE_TIMEOUT) {
		debug(5, "%s packet lost", h->hostname);
		packet_send(sock, my_ip, h->ip, getpid(), seq);
		h->failures++;
		h->seq = seq;
		if(h->failures > MAX_FAILURES) 
		    drop_sensor(h);
	    }
	    break;
	}
    }

}

/*
  scan(int dest_ip)

  the main routine

   If host ip.id diff is equal to the syns we sent plus replies to our traffic,
 then the port is probably listening. One idea is to augment exponencially
 the number of syns we sent if we sense traffic on that host, and use an error
 margin to check  our results.. say 20% ? configurable ?
 If the host ip.id diff is equal to the replies sent to us, then the port is
 probably closed. We could apply an error margin here aswell, given a considerable
 ammount of syns sent.
*/
void scan(int dest_ip)
{
    struct sensor_list *h;
    struct iphdr *packet;
    int id;
    int end = 0;
    int id_diff;

    /* Initialize state information and send the first ip.id probes */
    seq = 0;
    for(h = sensor_list; h; h = h->next) {
	h->id = 0;
	h->port = pop_port(port_list);
	h->type = HOST_TYPE_NA;
	h->st = HOST_ST_INIT;
	h->failures = 0;
	packet_send(sock, my_ip, h->ip, getpid(), seq);
    }


    /* Init timer */
    signal(SIGALRM, sig_alarm);
    alarm(1);

    for(;;) {
	packet = (struct iphdr *)capture();
	end = 1;

	for(h = sensor_list; h; h=h->next) {

	    /* Is this a correct reply to one of our probes ?  */
	    if(packet && packet->saddr == h->ip && packet_check(sock, packet, 0, my_ip, getpid())) {

		/* Invert endianess if needed... */
		id = h->type == HOST_TYPE_WINDOWS ? byteswap16(ntohs(packet->id)) : ntohs(packet->id);

		/* lame state machine */
		switch(h->st) {
		    
		case HOST_ST_INIT:
		    h->id = id;
		    h->seq = seq;
		    packet_send(sock, my_ip, h->ip, getpid(), seq);
		    h->st = HOST_ST_ANALYSE;
		    break;

		case HOST_ST_ANALYSE:
		    debug(5, "%s in state analyse", h->hostname);
		    if (id - h->id < 4) {
			debug(3, "%s is a regular machine", h->hostname);
			h->type = HOST_TYPE_NORMAL;
		    } else if(byteswap16(id - h->id) < 4) {
			debug(3, "%s is a windows machine", h->hostname);
			h->type = HOST_TYPE_WINDOWS;
		    } else {
			debug(3,"% s has too much traffic or does not have linear ip.id increments",
			      h->hostname);
			h->st = HOST_ST_END;
			/* FIXME: should push port back into port list */
		    }
		    id = h->type == HOST_TYPE_WINDOWS ? byteswap16(ntohs(packet->id)) : ntohs(packet->id);
		    h->id = id;
		    h->st = HOST_ST_SEND_PROBE;
		    send_scan(h, dest_ip, seq, id);
		    break;
		    
		case HOST_ST_WAIT_PROBE:
		    debug(4, "%s in wait probe state: diff = %u",
			  h->hostname, id - h->id);

		    switch(id - h->id) {

		    case SYNS_BURST + 1:  /* Port open */
			if(h->passes < option_passes - 1) {
				/* recheck... may be regular host traffic */
			    h->passes++;
			    send_scan(h, dest_ip, seq, id);
			    h->st = HOST_ST_SEND_PROBE;
			} else {
			    if(port_open(h)) {
				send_scan(h, dest_ip, seq, id);
				h->st = HOST_ST_SEND_PROBE;
			    }
			}
			break;

		    case 1: /* Port seems to be closed */
			if(port_closed(h)) {
			    send_scan(h, dest_ip, seq, id);
			    h->st = HOST_ST_SEND_PROBE;
			}
			break;

		    default: /* Inconsistent ip.id */
			debug(2,"(%s) failed: retrying port %u", h->hostname, h->port);
			h->failures++;
			if (h->failures >= MAX_FAILURES) {
			    drop_sensor(h);
			} else {
			    h->passes = 0;
			    send_scan(h, dest_ip, seq, id);
			    h->st = HOST_ST_SEND_PROBE;
			}
		    }
		    if(!h->port)
		      h->st = HOST_ST_END;
		    

		    h->id = id;
		    h->seq = seq;
		}
		
	    }

	    /* if all reached HOST_ST_END, then the scan is finished */
	    if(h->st != HOST_ST_END)
		end = 0;
	    
	}

	if(end)
	    return;

    }



}


int main(int argc, char **argv)
{
    int source_ip;

    int c;
    char *device;


    banner();


    /* get options */

    while ((c = getopt(argc, argv, "p:d:")) != -1) {
	switch(c) {
	case 'p':
	    option_ports_string = strdup(optarg);
	    break;
	case 'd':
	    option_debug_level = atoi(optarg);
	    break;
	default:
	    usage();
	    exit(-1);
	    break;
	}
    }


    if((argc - optind) != 2) {
	fprintf(stderr, "Invalid number of arguments\n");
	usage();
	exit(-1);
    }

    /* Init libnet stuff */
    dest_ip = libnet_name_resolve(argv[1 + optind], 1);

    sensor_list = new_sensor_list(argv[optind]);

    if(!option_ports_string)
	option_ports_string = strdup(DEFAULT_PORTS_STRING);

    port_list = new_plist(option_ports_string);

    if ((sock = libnet_open_raw_sock(IPPROTO_RAW)) == -1) {
	perror("raw sock");
	exit(1);
    }

    /* Check routing tables and get device/ip address to use */
    /* These only work in linux now... so we may be better off with SOCK_PACKET
     but its more portable like this */

    device = get_iface_by_dest(dest_ip);
    my_ip = get_ip_by_iface(device);

    /* Init capture device */
    init_capture(device);

    debug(1, "using interface %s", device);

    packet_send = packet_send_icmpecho;
    packet_check = packet_check_icmpecho;

    scan(dest_ip);

    return 0;
}
