/* Source file for netreconn packet traffic analysis program. See
   COPYING file for license details.
   TODO
    - staticky and checks
	- add pcap lib version to -u? .. how?
    - some explanation of the output fields
    - arp needs to have timestamps
    - arp needs decoding
    - ethtraf (will want src/dst mac + IP) ?
    - traffic counters both total and as packets come in (ARP has the latter)
    - add ARP decoding (somehow) inline...
*/

#define _BSD_SOURCE 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in_systm.h>
#include <pcap.h>
#ifndef NETBSD
#include <net/ethernet.h>
#endif
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <signal.h>
#include <math.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <semaphore.h>
#include <fcntl.h>
#include <getopt.h>
#include <errno.h>
#include <netinet/udp.h>
#include <net/if.h>
#ifdef NETBSD
#include <net/if_ether.h>
#endif
#include <sys/ioctl.h>
#include <time.h>

#define PACKAGE "wiretraf"

/* XXX jrf - a lot of this stuff doesn't need to be global and can be 
             moved into the pcap handler */
#define ARP_REQUEST 1
#define ARP_RERPLY 2

typedef struct arphdr {
    u_int16_t hwtype;
    u_int16_t proto;
    u_char hwlen;
    u_char proto_len;
    u_int16_t opcode;
    u_char sender_hwaddr[6];
    u_char sender_ipaddr[4];
    u_char target_hwaddr[6];
    u_char target_ipaddr[4];
} arphdr_t;

/* TCP v4 header */
struct tcphdr4 {
	uint16_t th_sport;
	uint16_t th_dport;
	uint32_t th_seq;
	uint32_t th_ack;
#  if __BYTE_ORDER == __LITTLE_ENDIAN
	uint16_t res1:4;
	uint16_t doff:4;
	uint16_t fin:1;
	uint16_t syn:1;
	uint16_t rst:1;
	uint16_t psh:1;
	uint16_t ack:1;
	uint16_t urg:1;
	uint16_t res2:2;
#  elif __BYTE_ORDER == __BIG_ENDIAN
	uint16_t doff:4;
	uint16_t res1:4;
	uint16_t res2:2;
	uint16_t urg:1;
	uint16_t ack:1;
	uint16_t psh:1;
	uint16_t rst:1;
	uint16_t syn:1;
	uint16_t fin:1;
#  else
#   error "Adjust your <bits/endian.h> defines"
#  endif
	uint16_t th_win;
	uint16_t check;
	uint16_t urg_ptr;
};

typedef struct ether_header eth_hdr;	/* Ethernet header */
typedef struct ip ip4ip;	/* IP data       */
typedef struct tcphdr4 tcp_hdr;	/* TCP header      */

char *pcap_dev;			/* Pcap device file descriptor */
short int proto_version;
short int arp_flag; /* watch arp traffic? */
short int decode_flag; /* To decode or not to decode */

/*
 * copy_argv: Copy off an argument vector
 *         except it does a lot of printing.
 * 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;
}

/*
 * usage - Simple usage message 
 */
static void usage()
{
	printf(PACKAGE " [option][arguments]\n"
	       PACKAGE " [-a][-d][-i <interface>][-p <number>][-u][filter]\n"
	       "OPTIONS:\n"
		   " -a         Read arp traffic only\n"
		   " -d         Decode packets (currently works in nonARP mode only)\n"
	       " -i <dev>   Specify the interface to watch\n"
	       " -p <int>   Exit after analyzing int polls\n"
	       " -u         Display help\n\n"
		   "Default: Reads IP Traffic only with no payload decoding.\n\n"
		   "EXAMPLES:\n"
		   "  " PACKAGE " -p 1024 -i en0 not port 22\n"
		   "  " PACKAGE " -a -p 64\n"
		   "  " PACKAGE " -d -p 2048\n"
		   "Notes:\n"
		   " -  Must be root/sudo to set interface to promiscuous mode\n"
		   " -  Can differentiate TCP/UDP/ICMP traffic in IP only mode (nonARP)\n"
	);
}

/*
 * Call libpcap and decode payload data.
 */
static void payload_print(u_char * arg, const struct pcap_pkthdr *header,
              const u_char * packet)
{
    int i = 0, *counter = (int *)arg;

    printf("Packet RECV Size: %d Payload:\n", header->len);
    for (i = 0; i < header->len; i++) {
        if (isprint(packet[i]))
            printf("%c ", packet[i]);
        else
            printf(". ");

        if ((i % 16 == 0 && i != 0) || i == header->len - 1)
            printf("\n");
    }

    return;
}

/*
 * pcap_handler4: This is the ipv4 pcap looper. It is like most pcap callbacks
 *         except it does a lot of printing.
 * requires: all of the standard pcap_loop data
 */
/* XXX jrf: this is really a frakn mess and needs cleaned up a lot */
static void pcap_handler4(u_char * args, const struct pcap_pkthdr *header,
			  const u_char * packet)
{
	eth_hdr *ethernet;	/* The ethernet header    */
	ip4ip *ip;		/* The IP header          */
	u_int id;		/* Host id                */
	u_int i;		/* Counter                */
	const struct tcphdr4 *tcp;	/* TCP Header             */
	int len;		/* real length            */
	u_int off, version;	/* offset, version        */
	u_int length = header->len;	/* True header len        */
	char *t;		/* Timestamp intermediary */
	time_t result;		/* Time result (as read)  */
	struct udphdr *udp;	/* udp header info */
	struct icmphdr *icmp;	/* icmp hdr info */

	t = "";			/* empty */

	/* 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 */
	result = time(NULL);	/* Setup the timestamp */
	t = asctime(localtime(&result));
	t[strlen(t) - 1] = ' ';
	t[strlen(t)] = 0;
	len = ntohs(ip->ip_len);
	off = ntohs(ip->ip_off);
	/* XXX jrf Need to arrange this with less duplication */
	switch (ip->ip_p) {
	case IPPROTO_TCP:
		/* this is just real fun - inet_ntoa mixed with fprintf screws up */
		/* so the WAR is to mix up the print types.                       */
		printf("%s: %s:", t, inet_ntoa(ip->ip_src));
		fprintf(stdout, "%u ", tcp->th_sport);
		printf("-> %s:", inet_ntoa(ip->ip_dst));
		fprintf(stdout, "%u ", tcp->th_dport);
		fprintf(stdout,
			"tcp len %u off %u ttl %u prot %u cksum %u seq %u ack %u win %u\n",
			len, off, ip->ip_ttl, ip->ip_p,
			ip->ip_sum, tcp->th_seq, tcp->th_ack, tcp->th_win);
		break;
	case IPPROTO_UDP:
		udp = (struct udphdr *)(packet + sizeof(struct ip));
		printf("%s: %s:", t, inet_ntoa(ip->ip_src));
		printf("%u", udp->uh_sport);
		printf("-> %s:", inet_ntoa(ip->ip_dst));
		printf("%u ", udp->uh_dport);
		fprintf(stdout,
			"udp len %u sum %u off %u ttl %u prot %u cksum %u seq %u ack %u win %u\n",
			udp->uh_ulen, udp->uh_sum, off, ip->ip_ttl, ip->ip_p,
			ip->ip_sum, tcp->th_seq, tcp->th_ack, tcp->th_win);
		break;
	case IPPROTO_ICMP:
		icmp = (struct icmphdr *)(packet + sizeof(struct ip));
		printf("%s: %s:", t, inet_ntoa(ip->ip_src));
		printf("-> %s", inet_ntoa(ip->ip_dst));
/* XXX jrf: this mess sucks */
#if DARWIN || NETBSD
		fprintf(stdout,
			"icmp len %u off %u ttl %u prot %u cksum %u seq %u ack %u win %u\n",
			len, off, ip->ip_ttl, ip->ip_p,
			ip->ip_sum, tcp->th_seq, tcp->th_ack, tcp->th_win);
#else
		fprintf(stdout,
			"icmp type %u code %u off %u ttl %u prot %u cksum %u seq %u ack %u win %u\n",
#if FREEBSD 
			icmp->icmp_type, icmp->icmp_code,
#else
			icmp->type, icmp->code,
#endif				/* NET and FREEBSD */
			off, ip->ip_ttl, ip->ip_p,
			ip->ip_sum, tcp->th_seq, tcp->th_ack, tcp->th_win);
#endif				/* DARWIN */
		break;
	default:
		printf("%s: %s:", t, inet_ntoa(ip->ip_src));
		fprintf(stdout, "%u ", tcp->th_sport);
		printf("-> %s:", inet_ntoa(ip->ip_dst));
		fprintf(stdout, "%u ", tcp->th_dport);
		fprintf(stdout,
			"??? len %u off %u ttl %u prot %u cksum %u seq %u ack %u win %u\n",
			len, off, ip->ip_ttl, ip->ip_p,
			ip->ip_sum, tcp->th_seq, tcp->th_ack, tcp->th_win);
		break;
	}

	if (decode_flag) {
		int i = 0, *counter = (int *)args;
		printf("Packet RECV Size: %d Payload:\n", header->len);
		for (i = 0; i < header->len; i++) {
			if (isprint(packet[i]))
				printf("%c ", packet[i]);
			else
				printf(". ");

			if ((i % 16 == 0 && i != 0) || i == header->len - 1)
				printf("\n");
		}
	}
}

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 */
	struct pcap_pkthdr pkthdr; /* For ARP short circuit */
	const unsigned char *packet = NULL; /* For ARP short circuit */
	arphdr_t *arpheader = NULL; /* For ARP short circuit */

	npolls = -1; /* Default to loop forever */
	proto_version = 4;	/* Default ipv4 */

	while ((c = getopt(argc, argv, "6adi:p:u")) != -1) {
		switch (c) {
		case 'a':
			arp_flag = 1;
			break;
		case 'd':
			decode_flag = 1;
			break;
		case 'i':
			pcap_dev = optarg;
			break;
		case 'p':
			if (optarg != NULL && isdigit(*optarg)) {
				npolls = atol(optarg);
				if (npolls < 0) {
					fprintf(stderr,
						"Packets must be > than 0\n");
					return EXIT_FAILURE;
				}
			} else {
				fprintf(stderr, "Invalid packet number\n");
				return EXIT_FAILURE;
			}
			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 */

	/* XXX jrf: DIRTY FILTHY FEATURE HACK ... something todo for next vers.
	 * If we are doing ARP traffic we completely short ciruit main for now 
	 * this all really needs to be moved out into something more sane 
	 * It also needs timestamps and decoder. Hence moving it out would
	 * be beneficial... which means decoding needs to be pushed out too :)
	 */
	if (arp_flag) {
		if (pcap_compile(handle, &program, "arp", 1, mask) == -1) {
			fprintf(stderr, "Error - `ARP: pcap_compile()'\n");
			return EXIT_FAILURE;
		}

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

		while (npolls != 0) {
			packet = pcap_next(handle, &pkthdr);
			arpheader = (struct arphdr *)(packet+14);
			printf("recv-packet-len=%dbytes ", pkthdr.len);
			printf("hwtype=%s ", (ntohs(arpheader->hwtype) == 1) ?
			  "ethernet" : "Unknown");
			printf("proto=%s ", (ntohs(arpheader->proto) == 0x800) ?
			  "ipv4" : "Unknown");
			printf("oper=%s ", (ntohs(arpheader->opcode) == ARP_REQUEST) ?
			  "ARPrequest" : "ARPreply");
			if (ntohs(arpheader->hwtype) == 1 && 
			  ntohs(arpheader->proto) == 0x800) {
			/* XXX jrf: again we need to break these out into print functions */
			for (i = 0; i < 6; i++) 
				if (i < 5) 
					printf("%02X:",arpheader->sender_hwaddr[i]);
				else
					printf("%02X ",arpheader->sender_hwaddr[i]);

			for (i = 0; i < 4; i++)
				if (i < 3)
					printf("%d.",arpheader->sender_ipaddr[i]);
				else
					printf("%d ",arpheader->sender_ipaddr[i]);

			printf("-> ");
			for (i = 0; i < 6; i++) 
				if (i < 5)
					printf("%02X:",arpheader->target_hwaddr[i]);
				else
					printf("%02X ",arpheader->target_hwaddr[i]);
			for (i = 0; i < 4; i++) 
				if (i < 3)
					printf("%d.", arpheader->target_ipaddr[i]);
				else
					printf("%d ", arpheader->target_ipaddr[i]);

			printf("\n");
			npolls--;
			}
		}
		
		pcap_freecode(&program);

		return EXIT_SUCCESS;;
	}

	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);
	}

	/* Main loop */
	printf("Starting capturing engine on %s...\n", pcap_dev);
	if (proto_version == 4) 
		pcap_loop(handle, npolls, pcap_handler4, NULL);
	else
		printf("IPV6 not yet supported\n");

	/* Exit program */
	printf("Closing capturing engine...\n");
	pcap_close(handle);

	return EXIT_SUCCESS;
}

