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

#include <gtk/gtk.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define __BSD_SOURCE
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <netdb.h>
#include "cheops.h"
#if (__GLIBC__ < 2)
#include "libc5.h"
#endif

int seqno = 0;
static int priv_fd=-1;
static int ident;

static struct ping_request *pqueue=NULL;

static struct sockaddr_in self;
static char hostname[256];
static int caught_idle = 0;
static int caught_discov = 0;

char *print_aliases(struct net_object *no, char c)
{
	static char buf[1024];
	struct alias *a;
	int flag=0;
	char tmp[256];
	buf[0] = '\0';
	a = no->aliases;
	while(a) {
		if (strcasecmp(no->hostname, a->hostname)) {
			if (!flag) {
				if (c == '\n')
					snprintf(buf, sizeof(buf), "Aliases:%c", c);
				flag++;
			}
			snprintf(tmp, sizeof(tmp), "  %s%c", a->hostname, c);
			strncat(buf, tmp, sizeof(buf) - strlen(buf));
		}
		a=a->next;
	}
	return buf;
}

void fix_tooltip(struct net_object *no)
{
	char buf[1024];
	struct in_addr ia;
	struct net_page *np=NULL;
	if (no->eventbox->parent)
		np = (struct net_page *)gtk_object_get_user_data(GTK_OBJECT(no->eventbox->parent));
	ia.s_addr = no->ip_addr;
	snprintf(buf,sizeof(buf), "Name: %s\n%sIP: %s\nOS: %s\n", no->hostname, print_aliases(no, '\n'),inet_ntoa(ia), no->os);
	if (valid_np(np)) {
		gtk_clist_set_text(GTK_CLIST(np->list), no->id, 0, no->hostname);
		gtk_clist_set_text(GTK_CLIST(np->list), no->id, 1, print_aliases(no, ' '));
		gtk_clist_set_text(GTK_CLIST(np->list), no->id, 2, no->os);
		gtk_clist_set_text(GTK_CLIST(np->list), no->id, 3, inet_ntoa(ia));
	}
	gtk_tooltips_set_tip(main_window.tips, no->eventbox, buf, NULL);
}

void add_unique(unsigned int addr, char *icon, char *os, struct net_page *np)
{
	struct net_object *no;
	struct hostent *hp;
	struct in_addr iadr;
	char lab[256];
	const char *name;
	struct alias *a;
	iadr.s_addr = addr;
	name = inet_ntoa(iadr);
	if (!valid_np(np))
		return;
	if (!(no = get_object(np, addr))) {
#if 0
		printf("Adding unique '%s' with icon '%s', os is '%s'\n",name, icon, os);
#endif
		a = build_aliases(addr);
		if (a)
			name = a->hostname;
		if (option_reverse_dns) {
			hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
			if (hp) 
				name = hp->h_name;
		}
		strncpy(lab, name, sizeof(lab));
 		if (option_trim) {
			char *c;
			c = lab;
			/* Don't trim IP addresses */
			if (atoi(lab))
				strncpy(lab,name, sizeof(lab));
			else {
				do {
					c = strchr(c,'.');
					/* See if this is a domain name */
					if (c) {
						if (get_server(c+1, 0)) {
							*c = '\0';
							break;
						} else
							c++;
					}
				} while (c);
			}
			
		}	
		no = network_object(main_window.window->window, icon, lab);
		no->ip_addr = addr;
		no->aliases = a;
		strncpy(no->hostname, name, sizeof(no->hostname));
		strncpy(no->os, os + 2, sizeof(no->os));
		fix_tooltip(no);
		add_object(np, no);
	} else {
		/* Kludge: Don't overwrite a good analysis with a bad one */
		if (strcmp(icon, "desktop.xpm")) {
			set_icon(no, icon);
			strncpy(no->os, os + 2, sizeof(no->os));
			fix_tooltip(no);
		}
	}
#if 0
	 else
		fprintf(stderr, "Already found '%s' in page\n", name);
#endif

}

int valid_np(struct net_page *np)
{
	GList *l;
	struct net_page *npt;
	GtkNotebookPage *gnp;
	
	if (!np)
		return 0;
	/* Search for np and be sure it's valid */
	l = g_list_first(GTK_NOTEBOOK(main_window.notebook)->children);
	npt = NULL;
	while(l) {
		gnp = (GtkNotebookPage *)(l->data);
		npt = (struct net_page *)gtk_object_get_user_data(GTK_OBJECT(gnp->child));
		if (np == npt)
			break;
		l = g_list_next(l);
	}
	if (npt == np)
		return -1;
	else
		return 0;
}

void handle_packet(char *buf, int len, struct sockaddr_in *sin)
{
	struct ip *ip;
	struct icmp *i;
	struct net_page *np;
	int hlen;
	ip = (struct ip *)buf;
	hlen = ip->ip_hl << 2;
	if (len < hlen + ICMP_MINLEN) {
		fprintf(stderr, "handle_packet: too short (%d bytes) from %s\n",
			len, inet_ntoa(sin->sin_addr));
		return;
	}
	i = (struct icmp *)(buf + hlen);
	if ((i->icmp_type == ICMP_TIME_EXCEEDED)  || (i->icmp_id == htons(getpid()))
	     || (i->icmp_type == ICMP_DEST_UNREACH))
		if (handle_map(buf, len, sin))
			return;

	if (i->icmp_id != htons(ident)) 
		return;
	
	if (i->icmp_type != ICMP_ECHOREPLY)
		return;
	if (len < hlen + ICMP_MINLEN + sizeof(np)) {
		fprintf(stderr,"handle_packet: too short (%d bytes) from %s (2)\n", len, inet_ntoa(sin->sin_addr));
		return;
	}
	memcpy(&np, &buf[hlen + ICMP_MINLEN], sizeof(np)); 
	/* Ignore if it's invalid, for example if the page is deleted already */
	if (!valid_np(np)) 
		return;
	if (option_queso) {
		if (!get_object(np, sin->sin_addr.s_addr)) {
			if (caught_discov) {
				gtk_timeout_remove(caught_discov);
				caught_discov = 0;
			}
			examine_host_queue(sin->sin_addr.s_addr, try_ports[0], np ? np : current_page);
		}
	} else
		add_unique(sin->sin_addr.s_addr,"desktop.xpm", "  Not examined", np ? np : current_page);
}

void network_socket_cb(void *d, int fd, GdkInputCondition condition)
{
	char buf[1024];
	int len;
	struct sockaddr_in sin;
	int sinlen = sizeof(sin);
     	fd_set fds;
     	struct timeval tv;
	do {
		if ((len = recvfrom(fd, buf, sizeof(buf), 0,
	   	     (struct sockaddr *)&sin, &sinlen)) < 0) {
	    		perror("network_socket_cb: recvfrom");
		} else {
			handle_packet(buf, len, &sin);
		
		}
     		tv.tv_sec = 0;
     		tv.tv_usec = 0;
     		FD_ZERO(&fds);
     		FD_SET(fd, &fds);
	} while (select(fd + 1, &fds, NULL, NULL, &tv) > 0);
}

int net_init()
{
	struct protoent *proto;
	struct hostent *hp;
	int hold;
	
	ident = getpid() & 0xFFFF;
	if (!(proto = getprotobyname("icmp"))) {
                perror("ping: unknown protocol icmp");
                exit(1);
        }
        if ((priv_fd = socket(AF_INET, SOCK_RAW, proto->p_proto)) < 0) {
                perror("ping: socket");
                exit(1);
        }
	
	hold = 48 * 1024;
        setsockopt(priv_fd, SOL_SOCKET, SO_RCVBUF, (char *)&hold,
            sizeof(hold));

	if (gethostname(hostname, sizeof(hostname)) == 0) {
		hp = gethostbyname(hostname);
		if (hp) {
			memcpy(&self.sin_addr, hp->h_addr, sizeof(unsigned int));
			self.sin_family = AF_INET;
			self.sin_port = 0;
			gdk_input_add(priv_fd, GDK_INPUT_READ, network_socket_cb, NULL);
		}
		return 0;
	} else
		perror("ping: gethostname");
		
	exit(1);
}

static int idle_network()
{
	/*
	 * Do a ping that is in the queue
	 */
	struct ping_request *pr;
	static char buf[256];
	struct sockaddr_in to;
	int len=8 + sizeof(void *), sent;
	struct icmp *i;

	pr = pqueue;
	if (pr) {
		memset(&to, 0, sizeof(struct sockaddr_in));
		to.sin_family = AF_INET;
		to.sin_addr.s_addr = pr->addr;
		i = (struct icmp *)buf;
		i->icmp_type = ICMP_ECHO;
		i->icmp_code = 0;
		i->icmp_cksum = htons(0);
		i->icmp_seq = htons(seqno++);
		i->icmp_id = htons(ident);	
		memcpy(&buf[8], &(pr->data), sizeof(pr->data));
		i->icmp_cksum = inet_checksum(i, len);
		sent = sendto(priv_fd, buf, len, 0, (struct sockaddr *)&to, sizeof(to));
#if 0
		if (sent < 0 || sent != len) {
			if (sent < 0)
				perror("init_scan_host: sendto");
			printf("init_scan_host: wrote %s %d chars, ret = %d\n",
				inet_ntoa(to.sin_addr), len, sent);
		}
#endif
#if 1
		usleep(Zzz);
#endif
		pqueue = pqueue->next;
		g_free(pr);
	} else {
		caught_idle = 0;
		if (!option_queso) {
			gdk_window_set_cursor(main_window.window->window, NULL);
			set_status("Discovery complete");
		}
		return FALSE;
	}
	return TRUE;
}

static int init_scan_host(unsigned int ip, void *data)
{
	struct ping_request *pr;
	
	pr = g_new(struct ping_request, 1);
	pr->next = pqueue;
	pr->addr = ip;
	pr->data = data;
	pqueue = pr;
  	if (!caught_idle) {
  		gtk_idle_add(idle_network, NULL);  
		caught_idle =  1;
  	}
	return 0;
}

static int discover_network(struct net_page *np, struct in_addr addr, struct in_addr netmask)
{
	unsigned int start;
	unsigned int finish;
	unsigned int mask;
	unsigned int x;
	
	mask = ntohl(netmask.s_addr);
	start = ntohl(addr.s_addr) & mask;
	finish = start + ~mask;
	if (start != finish) {
		start++;
		finish--;
		
	}
	for (x=start;x <= finish; x++)
		if (!get_object(np, htonl(x))) 
			init_scan_host(htonl(x), np);
	return 0;
}

static int cancel_discovery() {
	gdk_window_set_cursor(main_window.window->window, NULL);
	set_status("No hosts discovered");
	caught_discov = 0;
	return FALSE;
};


int discover_network_a(struct net_page *np, char *addr, char *netmask, int add)
{
	struct in_addr a,m;
	struct network *n;
	char buf[256];
	
	if (inet_aton(addr, &a) && inet_aton(netmask, &m)) {
		if (add) {
			n = g_new(struct network, 1);
			n->next = np->ncontents;
			np->ncontents = n;
			n->net_addr = a.s_addr;
			n->net_mask = m.s_addr;
			append_network(np->pe, n);
		}
		if (!caught_discov)
			caught_discov = gtk_timeout_add(5000, cancel_discovery, NULL);
		immediate_setcursor(GDK_WATCH);
		snprintf(buf, sizeof(buf), "Discovering %s/%s", addr, netmask);
		set_status(buf);
		return discover_network(np, a, m);
	} else {
		return -1;
	}
}
