/*
 * Network Explorer
 *
 * Copyright (C) 1998, Mark Spencer
 * 
 * Distributed under the terms of the GNU GPL
 *
 */

#include <gtk/gtk.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#define __BSD_SOURCE
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <time.h>
#include <string.h>
#include <netdb.h>
#include "cheops.h"
#if (__GLIBC__ < 2)
#include "libc5.h"
#endif
struct blah {
	unsigned int addr;
	struct net_page *np;
};

static struct map_request *mrq=NULL;
static struct plink *pq=NULL;

static unsigned int map_lock=0;

static int timeout_installed=0;

static int map_anyway(void *blah_v)
{
	struct blah *blah = (struct blah *)blah_v;
	if (map_lock) {
		if (map_lock == blah->addr)
			return FALSE;
		else
			return TRUE;
	}
	map_lock = blah->addr;
	if (!get_object(blah->np, blah->addr)) 
		add_unique(blah->addr, "unknown.xpm", "  Non-responsive", blah->np);
	g_free(blah);
	map_lock = 0;
	return FALSE;
}

static struct link *connection_exists(struct net_object *no1, struct net_object *no2)
{
	struct link *l;
	l = no1->links;
	while(l) {
		if (l->other == no2)
			break;
		l=l->next;
	}
	return l;
}

static struct plink *plink_exists(unsigned int addr1, unsigned int addr2)
{
	struct plink *p;
	p = pq;
	while(p) {
		if (((p->addr1 == addr1) && (p->addr2 == addr2)) ||
		    ((p->addr2 == addr1) && (p->addr1 == addr2)))
		    	break;
		p=p->next;
	}
	return p;
}

void check_connections(struct net_page *np, struct net_object *no)
{
	struct plink *p;
	struct plink *last;
	struct net_object *no2;
	struct in_addr ia;
	last=NULL;
	p = pq;
	ia.s_addr = no->ip_addr;
	while(p) {
		if (((no->ip_addr == p->addr1) && (no2 = get_object(np, p->addr2))) ||
		    ((no->ip_addr == p->addr2) && (no2 = get_object(np, p->addr1)))) {
			if (last) {
				last->next = p->next;
				g_free(p);
				p=last->next;
			} else {
				pq = p->next;
				g_free(p);
				p = pq;
			}
			if (!connection_exists(no, no2))
				object_link(np, no, no2);
		} else {			
			last=p;
			p=p->next;
		}
	}
	
}

static void map_connect(struct net_page *np, unsigned int addr1, unsigned int addr2)
{
	struct net_object *no1, *no2;
	struct plink *p;
	struct in_addr ia;
	struct blah *blah;
#if 0
	ia.s_addr = addr1;
	printf("adding connection from %s",inet_ntoa(ia));
	ia.s_addr = addr2;
	printf(" to %s\n", inet_ntoa(ia));
#endif
	no1 = get_object(np, addr1);
	no2 = get_object(np, addr2);
	if (no1 && no2) {
		if (!connection_exists(no1, no2))
			object_link(np, no1, no2);
	} else {
		if (!no2) {
			ia.s_addr = addr2;
#if 0
			printf("Starting search for %s\n",inet_ntoa(ia));
#endif
			if (option_queso) {
				/* In case we don't discover anything, queue up a forced host */
				blah = g_new(struct blah, 1);
				blah->np = np;
				blah->addr = addr2;
				gtk_timeout_add(10000, map_anyway, blah);
				discover_network_a(np, inet_ntoa(ia), "255.255.255.255", option_add_routes);
			} else {
				add_unique(addr2, "desktop.xpm", "  Not examined",np);
				no2 = get_object(np, addr2);
				if (!no1) {
					add_unique(addr1, "desktop.xpm", "  Not examined", np);
					no1 = get_object(np, addr1);
				}
				if (no1 && no2)
					object_link(np, no1, no2);
				return;
			}
		}
		if (!plink_exists(addr1, addr2)) {
#if 0
			ia.s_addr = addr1;
			printf("Queuing a connection from %s",inet_ntoa(ia));
			ia.s_addr = addr2;
			printf(" to %s\n", inet_ntoa(ia));
#endif
			/* Queue a connection for when this host is discovered */
			p = g_new(struct plink, 1);
			p->addr1 = addr1;
			p->addr2 = addr2;
			p->next = pq;
			pq = p;
		} 
#if 0
			else
			printf("Connection already queued\n");
#endif

	}
}

static void mr_probe(struct map_request *mr)
{
	struct in_addr ia;
	ia.s_addr = mr->dest;
	mr->retries++;
	mr->sent=time(NULL);
	sendicmp(mr->ttl, ia, 1);
}

static int map_timeout(void *data)
{
	time_t current = time(NULL);
	struct map_request *mr, *l;
	struct in_addr ia;
	int cnt=0;
	if (!mrq) {
		timeout_installed=0;
		set_status("Mapping Complete");
		return FALSE;
	}
	l=NULL;
	mr = mrq;
	while(mr) {
		if (current > mr->sent + REQUEST_TIMEOUT) {
			cnt++;
			/* Limit how many retransmits we do.  This should stabalize replies a bit and
			   reduce flooding */
			if (cnt > MAP_TIMEOUT_MAX) {
#if 0
				printf("Too many timeouts!  Waiting for another opportunity\n");
#endif
				return TRUE;
			}
			ia.s_addr=mr->dest;
			if (mr->retries > MAX_REQUEST_RETRIES) {
#if 0
				printf("too many retries on host %s...  Skipping\n",inet_ntoa(ia));
				mr->retries=0;
				mr->ttl++;
				if (mr->ttl > MAP_MAX) {
					printf("ttl too large on host %s\n", inet_ntoa(ia));
				}
#endif
				if (l) {
					l->next = mr->next;
					g_free(mr);
					mr = l->next;
				} else {
					mrq = mr->next;
					mr = mrq;
				}
			} else {
#if 0
				printf("retransmit %d to host %s\n", mr->retries + 1, inet_ntoa(ia));
#endif
				mr_probe(mr);
			}
		} else {
			l = mr;
			mr = mr->next;
		}
			
	}
	return TRUE;
}

void start_mapping(struct net_object *no)
{
	struct map_request *mr;
	struct net_page *np;
	struct in_addr lip;
	char buf[256];
	lip = getlocalip(no->ip_addr);
	np = (struct net_page *)gtk_object_get_user_data(GTK_OBJECT(no->eventbox->parent));
	/* First check to see if there is already a request */
	mr = mrq;
	while(mr) {
		if ((mr->dest == no->ip_addr) && (np == mr->np)) {
#if 0
			printf("Warning: duplicate map request\n");
#endif
			return;
		}
		mr=mr->next;
	}
	snprintf(buf,sizeof(buf), "Mapping '%s'", no->hostname);
	set_status(buf);
	mr = g_new(struct map_request, 1);
	mr->np = np;
	mr->dest = no->ip_addr;
	mr->last = lip.s_addr;
	if (!get_object(np, lip.s_addr))
		if (option_queso)
			discover_network_a(np, inet_ntoa(lip), "255.255.255.255", option_add_routes);
	mr->ttl = 1;
	mr->retries = 0;
	mr->sent=0;
	mr->next = mrq;
	mrq = mr;
	if (!timeout_installed)
		timeout_installed=gtk_timeout_add(100, map_timeout, NULL);

#if 0
	printf("mapping %s\n",no->hostname);
#endif
	mr_probe(mr);

}

int handle_map(char *buf, int len, struct sockaddr_in *sin)
{
	struct ip *ip = (struct ip *)buf;
	struct icmp *icmp = (struct icmp *)&buf[sizeof(struct ip)];
	struct udphdr *udp = (struct udphdr *)&buf[sizeof(struct ip)];
	struct map_request *mr, *last=NULL;
	char host[256];
	unsigned char ttl;
	int res=0;
#if 0
	printf("map reply of %d bytes\n",len);
#endif
	
	switch(icmp->icmp_type) {
	case ICMP_TIME_EXCEEDED:
		/* Get original header, etc */
		buf += (sizeof(struct ip) + ICMP_MINLEN);
		len -= (sizeof(struct ip) + ICMP_MINLEN);
		ip = (struct ip *)buf;
		icmp = (struct icmp *)(buf + sizeof(struct ip));
		udp = (struct udphdr *)(buf + sizeof(struct ip));
		if (len != sizeof(struct ip) + ICMP_MINLEN)
			break;
#if 0
		printf("Revised length: %d\n",len);
#endif
		if (ip->ip_p == IPPROTO_ICMP)
			ttl = ntohs(icmp->icmp_seq);
		else
			ttl = ntohs(udp->dest) - EMPTY_PORT;
		strcpy(host,inet_ntoa(sin->sin_addr));
#if 0
		printf("%s is hop %hu to %s\n", host,ttl, inet_ntoa(ip->ip_dst));
#endif
		mr=mrq;
		last=NULL;
		while(mr) {
			/* See if this is the right place and the right time */
			if ((mr->dest == ip->ip_dst.s_addr) && (ttl = mr->ttl)) {
#if 0
				printf("Found map record for %s\n",inet_ntoa(ip->ip_dst));
#endif
				if (ttl < MAP_MAX) {
					mr->ttl++;
					mr->retries=0;
					map_connect(mr->np, mr->last, sin->sin_addr.s_addr);
					mr->last = sin->sin_addr.s_addr;
					mr_probe(mr);
					last = mr;
					mr = mr->next;
				} else {
					if (last) {
						last->next = mr->next;
						g_free(mr);
						mr = last->next;
					} else {
						mrq = mr->next;
						mr = mrq;
					}
				}
			} else {
				last=mr;
				mr=mr->next;
			}
		}
		res=1;
		break;
	case ICMP_DEST_UNREACH:
		/* Get original header, etc */
		buf += (sizeof(struct ip) + ICMP_MINLEN);
		len -= (sizeof(struct ip) + ICMP_MINLEN);
		ip = (struct ip *)buf;
		icmp = (struct icmp *)(buf + sizeof(struct ip));
		udp = (struct udphdr *)(buf + sizeof(struct ip));
		/* Fall through */
	case ICMP_ECHOREPLY:
		if (len != sizeof(struct ip) + ICMP_MINLEN)
			break;
		if (ip->ip_p == IPPROTO_ICMP)
			ttl = ntohs(icmp->icmp_seq);
		else
			ttl = ntohs(udp->dest) - EMPTY_PORT;
		strcpy(host,inet_ntoa(sin->sin_addr));
#if 0
		printf("%s is last hop %u\n", host, ttl);
#endif
		last=NULL;
		mr=mrq;
		while(mr) {
			/* See if this is the right place and the right time */
			if ((mr->dest == sin->sin_addr.s_addr) && (ttl = mr->ttl)) {
#if 0
				printf("Found map record for %s\n",inet_ntoa(sin->sin_addr));
#endif
				map_connect(mr->np, mr->last, sin->sin_addr.s_addr);
				if (last) {
					last->next = mr->next;
					g_free(mr);
					mr = last->next;
				} else {
					mrq = mr->next;
					mr = mrq;
				}
			} else {
				last=mr;
				mr=mr->next;
			}
		}
		res=1;
	}
	return res;
}
