/* zodiac - advanced dns spoofer
 *
 * sniffing functions
 *
 * by scut, smiler
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <pcap.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "packet.h"
#include "output.h"
#include "sniff.h"
#include "zodiac.h"


extern struct in_addr	localip;


/* sniff_new
 *
 * the only function that should be called from outside. set up sniffing
 * device, create a new thread, then return.
 * open `interface' device for sniffing, tell sniffing thread to use
 * `pq_size' packet queues, available through `pq_list'.
 * store thread id of new thread in `tid'.
 *
 * return 0 if thread creation was successful
 * return 1 if thread creation failed
 */

int
sniff_new (pthread_t *tid, char *interface, pq_thread *pq_thd)
{
	int		n;		/* temporary return value */
	sniff_info	*sinfo;		/* sniff information structure */

	sinfo = xcalloc (1, sizeof (sniff_info));

	/* open interface
	 */
	sinfo->device = sniff_open (interface);
	if (sinfo->device == NULL) {
		free (sinfo);

		return (1);
	} else if (sinfo->device->error == 1) {
		return (1);
	}

	if (sniff_dev_ip (interface, &localip) != 0) {
		free (sinfo);

		return (1);
	}

	/* store information into sinfo
	 */
	sinfo->pq_thd = pq_thd;

	/* and create our neat thread :)
	 */
	n = pthread_create (tid, NULL, (void *) sniff_handle, (void *) sinfo);
	if (n == -1) {
		sniff_dev_free (sinfo->device);
		free (sinfo);

		return (1);
	}
	/* successfully created sniffer thread
	 */
	return (0);
}

/* sniff_handle
 *
 * the main sniffing thread, fetching packets from the device, then calling
 * the packet grinder `pq_grind' to process the packets
 *
 * should never return except on error or program exit
 */ 

void *
sniff_handle (sniff_info *sinfo)
{
	int		n;		/* temporary return value */
	pcap_handler	grinder;	/* pcap handler for the packet grinding function */

	m_printf (ms, ms->winproc, "[zod] hello world from sniffing thread\n");

	/* sniff packets forever, until an error appears. pass incoming packets
	 * to `pq_grind'.
	 */

	grinder = (pcap_handler) pq_grind;

	/* loop packets to pq_grind until error, passing sinfo struct for queueing
	 */
	n = pcap_loop (sinfo->device->pd, -1, grinder, (void *) sinfo);

	/* on error print error message, then free the device and terminate the
	 * thread
	 */
	if (n == -1) {
		m_printf (ms, ms->winproc, "[zod] sniff_handle (pcap_loop): %s\n", pcap_geterr (sinfo->device->pd));
	}

	return (NULL);
}


/* sniff_filter
 *
 * install a filter `filter' on device `device', with netmask `netmask'
 *
 * return 0 on success
 * return 1 on failure
 */

int
sniff_filter (s_dev *device, char *filter, bpf_u_int32 netmask)
{
	int			n;	/* temporary return value */
	struct bpf_program	fprog;	/* berkeley packet filter program structure */

	n = pcap_compile (device->pd, &fprog, filter, 1, netmask);
	if (n == -1) {
		m_printf (ms, ms->winproc, "[zod] sniff_filter (pcap_compile): failed to compile bpf program\n");
		return (1);
	}

	n = pcap_setfilter (device->pd, &fprog);
	if (n == -1) {
		m_printf (ms, ms->winproc, "[zod] sniff_filter (pcap_setfilter): failed to set bpf on %s\n", device->interface);
		return (1);
	}

	return (0);
}

/* sniff_open
 *
 * open `dev' for sniffing, or just the first sniffable one, if
 * dev is NULL.
 *
 * return NULL on failure
 * return pointer sniffing device structure on success
 * -smiler 
 *   Added link layer header length detection.
 */

s_dev *
sniff_open (char *devname)
{
	int	n;				/* temporary return value */
	s_dev	*device;			/* sniffing device structure to create */
	char	errorbuf[PCAP_ERRBUF_SIZE];	/* error buffer for pcap message */

	/* create new sniffing device structure in s_dev
	 */
	device = xcalloc (1, sizeof (s_dev));

	/* check wether to use the first device or a specified device
	 */
	if (devname == NULL) {
		/* due to lame pcap manpage, you should not know that it's static *doh* */
		device->interface = pcap_lookupdev (errorbuf);
		if (device->interface == NULL) {
			m_printf (ms, ms->winproc, "[zod] sniff_open (pcap_lookupdev): %s\n", errorbuf);
			device->error = 1;
			return (device);
		}
	} else {
		/* if the interface we have to use is already known just copy it
		 */
		device->interface = xstrdup (devname);
	}

	/* try to open the device found
	 */
	device->pd = sniff_pcap_open (device->interface);
	if (device->pd == NULL) {
		device->error = 1;
		return (device);
	}

	/* now query some information about the device and store them into our struct
	 */
	n = pcap_lookupnet (device->interface, &device->localnet,
		&device->netmask, errorbuf);
	if (n == -1) {
		device->error = 1;
		return (device);
	}

	device->linktype = pcap_datalink (device->pd);
	if (device->linktype == -1) {
		device->error = 1;
		return (device);
	}
	switch (device->linktype) {
	/* not sure about all of these, but they work for me :\ */
	case DLT_SLIP:
	case DLT_PPP:
	case DLT_NULL:
		device->linkhdrlen = 4;
		break;
	case DLT_RAW:
		device->linkhdrlen = 0;
		break;
	case DLT_EN10MB:
	default:
		device->linkhdrlen = 14;
		break;
	}
	m_printf(ms, ms->winproc, "[zod] sniff_open - linkhdrlen = %d\n",device->linkhdrlen);
	return (device);
}

/* sniff_pcap_open
 *
 * securely wraps the pcap_open_live call to catch any errors
 *
 * return NULL on failure
 * return capture descriptor on succes
 */

pcap_t *
sniff_pcap_open (char *device)
{
	char	errorbuf[PCAP_ERRBUF_SIZE];	/* error buffer */
	pcap_t	*pdes = NULL;			/* packet capture descriptor */

	pdes = pcap_open_live (device, SNAPLEN, PROMISC, READ_TIMEOUT, errorbuf);

	if (pdes == NULL) {
		m_printf (ms, ms->winproc, "[zod] sniff_pcap_open (pcap_open_live): %s\n", errorbuf);
		return (NULL);
	}

	return (pdes);
}

/* sniff_dev_free
 *
 * close and free a sniffing device
 */

void
sniff_dev_free (s_dev *device)
{
	pcap_close (device->pd);
	if (device->interface)
		free (device->interface);

	free (device);

	return;
}


/* sniff_dev_ip
 *
 * get the ip given the name of a device.
 * i /hope/ this is portable ;)
 * -smiler 991001
 *
 * return 0 on success
 * return -1 on failure
 */

int
sniff_dev_ip (const char *dev, struct in_addr *ip)
{
	int		ifsock,
			i_cnt;
	struct ifconf	ifc;
	struct ifreq	*ifr;
	char		buf[1024];


	ifsock = socket (AF_INET, SOCK_DGRAM, 0);
	if (ifsock < 0)
		return (-1);

	ifc.ifc_len = sizeof (buf);
	ifc.ifc_buf = buf;
	if (ioctl (ifsock, SIOCGIFCONF, &ifc) < 0)
		return (-1);

	i_cnt = ifc.ifc_len / sizeof(struct ifreq);

	for (ifr = ifc.ifc_req; i_cnt ; i_cnt--, ifr++) {
		if (strcmp (dev, ifr->ifr_name) == 0) {
			memcpy (ip, &((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr,
				sizeof (struct in_addr));

			return (0);
		}
	}

	return (-1);
}
