/* Source file for netreconn passive scan program. See
   COPYING file for license details.
   TODO
	- add gethostbyname: next release
	- invalidate broadcasts (255): next release
	- fix the port sort algoritm somehow: next release
	- port testing: next release
	- OSguess by ports
*/

#define _BSD_SOURCE 1

#include "netrecon.h"
#include "utils.h"

#define PACKAGE "passive"

static int xflag = 0; /* Xtra ports flag. Goes past 1024 */
static int port_threshold;

void add_list(char *ipaddr, uint16_t port);
int find_ipaddr(char *ipaddr);

struct ipaddr_list {
    char host_ip_address[IPV4CW]; /* The ipaddress in string form */
	struct {
		int tcpconns[PORTMAX]; /* # of tcp connects for position (port) */
		int udpconns[PORTMAX]; /* # of udp connects for position (port) */
		uint16_t portlist[PORTMAX]; /* The port number that was detected     */
	} portdata;
    struct ipaddr_list *next_ptr; /* Next item in ze list */
};
struct ipaddr_list *first_ptr = NULL; /* All things must have a start */

/* Add a port or a whole new ipaddr and port */
void addport(char *ipaddr, uint16_t port, char *proto_name)
{
    struct ipaddr_list *current_ptr;
    int x;

    current_ptr = first_ptr;

    /* If this is the first item add it to the list */
    if (current_ptr == NULL) {
        add_list(ipaddr, port);
        return;
    }

    /* If we can find the host by ip, add the port */
    while (current_ptr != NULL) {
        if (strcmp(current_ptr->host_ip_address, ipaddr) == 0) {
            for (x = 0; x < PORTMAX; x++) {
				if (current_ptr->portdata.portlist[x] == 0) 
                    current_ptr->portdata.portlist[x] = port;

				if (current_ptr->portdata.portlist[x] == port) {
					if (strncmp(proto_name,"tcp",3))
						current_ptr->portdata.tcpconns[x] =
						  (current_ptr->portdata.tcpconns[x] + 1);

					if (strncmp(proto_name,"udp",3))
						current_ptr->portdata.udpconns[x] =
						  (current_ptr->portdata.udpconns[x] + 1);

					return;
				}
            }
        }

        current_ptr = current_ptr->next_ptr;
    }

    /* We never found the host. So add it to the end of the list */
    if (current_ptr == NULL)
        add_list(ipaddr, port);
}

/* Add a new entry to the end of the list */
void add_list(char *ipaddr, uint16_t port)
{
    struct ipaddr_list *new_item_ptr;
    int x;

    new_item_ptr = malloc(sizeof(struct ipaddr_list));
    strcpy((*new_item_ptr).host_ip_address, ipaddr);
    for (x = 0; x < PORTMAX; x++) {
		new_item_ptr->portdata.portlist[x] = 0;
		new_item_ptr->portdata.tcpconns[x] = 0;
		new_item_ptr->portdata.udpconns[x] = 0;
	}

	(*new_item_ptr).portdata.portlist[0] = port;
    (*new_item_ptr).next_ptr = first_ptr;
    first_ptr = new_item_ptr;
}

/*
 * XXX this printing needs straightened out
 */
void print_hosts(void)
{
    struct ipaddr_list *current_ptr;
    int x;

    current_ptr = first_ptr;

    while(current_ptr != NULL) {
        x = 0;
        printf("%s:", current_ptr->host_ip_address);

        while (current_ptr->portdata.portlist[x] != 0)  {
			if ((current_ptr->portdata.tcpconns[x] >= port_threshold) ||
				 (current_ptr->portdata.udpconns[x] >= port_threshold)) {
	         printf(" %u ", current_ptr->portdata.portlist[x]);
				if (current_ptr->portdata.tcpconns[x] >= port_threshold) 
					printf("tcp");
				if (current_ptr->portdata.udpconns[x] >= port_threshold) {
					if (current_ptr->portdata.tcpconns[x] >= port_threshold) 
						printf("/");

					printf("udp ");
				}
	     }
            x++;
		}

        printf("\n");
        current_ptr = current_ptr->next_ptr;
    }
}


/*
 * copy_argv: Copy off an argument vector
 *
 * requires: argvector
 */
/*
static char *copy_argv(char **argv)
{
	char **p;
	u_int len = 0;
	char *buf;
	char *src, *dst;

	p = argv;

	if (*p == 0)
		return 0;

	while (*p)
		len += strlen(*p++) + 1;

	buf = (char *)malloc(len);
	if (buf == NULL) {
		fprintf(stdout, "copy_argv: malloc");
		exit(EXIT_FAILURE);
	}

	p = argv;
	dst = buf;
	while ((src = *p++) != NULL) {
		while ((*dst++ = *src++) != '\0') ;
		dst[-1] = ' ';
	}
	dst[-1] = '\0';

	return buf;
}
*/

/*
 * passive_pcap4: This is the ipv4 pcap looper. It is like most pcap callbacks
 * requires: all of the standard pcap_loop data
 */
static void passive_pcap4(u_char * args, const struct pcap_pkthdr *header,
              const u_char * packet)
{
    eth_hdr *ethernet;  /* The ethernet header    */
    ip4ip *ip;      /* The IP header          */
    const struct tcphdr4 *tcp;  /* TCP Header             */
	u_int sport;
	u_int dport;
	struct protoent *proto;

    /* Extract ethernet, ip and tcp headers */
    ethernet = (eth_hdr *) (packet);    /* Pointer to ethernet header */
    ip = (ip4ip *) (packet + sizeof(eth_hdr));
    tcp = (struct tcphdr4 *)(packet +
                 sizeof(struct ether_header) +
                 sizeof(struct ip));

    if (ip->ip_v != 4) return;     /* don't try to do ipv6  yet */

	dport = ntohs(tcp->th_dport); /* We only look at the dest port */
	proto = getprotobynumber(ip->ip_p); /* Fetch the protocol string */

	if ((dport <= 1024) || (xflag)) 
		addport(inet_ntoa(ip->ip_dst), dport, proto->p_name);
} 


/*
 * usage - Simple usage message 
 */
static void usage()
{
	printf(PACKAGE " [option][arguments]\n"
	       PACKAGE " [-i <interface>][-p <number>][-t <number>][-u][filter]\n"
	       "OPTIONS:\n"
	       " -i <dev>   Specify the interface to watch\n"
	       " -p <int>   Exit after analyzing int polls (default is %i)\n"
           " -t <int>   Set destination porthit threshold to int.\n"
           "            Default is %i. Lower numbers for wireless and home\n"
           "            and higher ones for workplace/campus networks.\n"
	       " -u         Display help\n"
	       " -x         Log extra ports (past 1024)\n"
		   "EXAMPLES:\n"
		   "  " PACKAGE " -p 1024 -i em0 net 192.168.1.0\n"
		   "  " PACKAGE " -p 64\n"
		   "NOTES:\n"
		   " -  Must be root/sudo to set interface to promiscuous mode\n",
			NPOLLS_DEFAULT, PORT_THRESHOLD_DEFAULT
	);
}

/*
char *getlocaltime(void) {
	char *t;
	time_t result;
	
	t = "";
	result = time(NULL);
	t = asctime(localtime(&result));
	t[strlen(t) - 1] = ' ';
	t[strlen(t)] = 0;

	return (t);
}

int u_int_check(char *value)
{
	int retval;

	if (value != NULL && isdigit(*value)) {
		retval = atol(value);
		if (retval < 0) {
			fprintf(stderr, "Value must be greater than 0\n");
			exit(EXIT_FAILURE);
		}
	} else {
		fprintf(stderr, "Invalid input value.\n");
		exit(EXIT_FAILURE);
	}

	return (retval);
}
*/

int main(int argc, char *argv[])
{
    struct bpf_program program; /* BPF filter program   */
    register int c,i;       /* Temporary variable   */
    char errbuf[PCAP_ERRBUF_SIZE];  /* pcap error buffer    */
    char *filter = NULL;    /* pcap filter          */
    pcap_t *handle;     /* pcap handle          */
    bpf_u_int32 mask;   /* our netmask          */
    bpf_u_int32 net;    /* our IP adx           */
    uint32_t npolls;    /* Number of pcap polls */

    npolls = NPOLLS_DEFAULT;
	port_threshold = PORT_THRESHOLD_DEFAULT;

    while ((c = getopt(argc, argv, "i:p:t:uU")) != -1) {
       switch (c) {
       case 'i':
           pcap_dev = optarg;
           break;
       case 'x':
           xflag = 1;
           break;
       case 'p':
           npolls = u_int_check(optarg);
           break;
       case 't':
           port_threshold = u_int_check(optarg);
           break;
       case 'u':
           usage();
           return EXIT_SUCCESS;
           break;
       default:
           usage();
           return EXIT_FAILURE;
           break;
       }
   }

	    /* Got root? */
    if (getuid()) {
        fprintf(stderr, "Must be root user.\n");
        return EXIT_FAILURE;
    }

    /* Strip off any none getopt arguments for pcap filter */
    if (!filter) filter = copy_argv(&argv[optind]);

    /* Initialize the interface to listen on */
    if ((!pcap_dev)
        && ((pcap_dev = pcap_lookupdev(errbuf)) == NULL)) {
        fprintf(stderr, "%s\n", errbuf);
        return EXIT_FAILURE;
    }

    if ((handle = pcap_open_live(pcap_dev, 68, 0, 0, errbuf)) == NULL) {
        fprintf(stderr, "%s\n", errbuf);
        return EXIT_FAILURE;
    }

    pcap_lookupnet(pcap_dev, &net, &mask, errbuf);  /* Get netinfo */

	if (filter) {
        if (pcap_compile(handle, &program, filter, 0, net) == -1) {
            fprintf(stderr, "Error - `IP: pcap_compile() IP'\n");
            return EXIT_FAILURE;
        }

        if (pcap_setfilter(handle, &program) == -1) {
            fprintf(stderr, "Error - `IP: pcap_setfilter()'\n");
            return EXIT_FAILURE;
        }

        pcap_freecode(&program);
    }

	printf("Starting capturing engine on %s...\n", pcap_dev);
	pcap_loop(handle, npolls, passive_pcap4, NULL);
	printf("Closing capturing engine...\n");
	pcap_close(handle);
	print_hosts();

	return EXIT_SUCCESS;
}

