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

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <netdb.h>
#if (__GLIBC__ < 2)
#ifndef T_SRV
#define T_SRV 33
#endif
#ifndef T_NAPTR
#define T_NAPTR 35
#endif
#else
#include <error.h>
#endif
#include <errno.h>
#include <gtk/gtk.h>
#include "cheops.h"

static struct aliascache *ac=NULL;

static int caught_domain = 0;

/* Why are these routines like res_query so incredibly poorly documented? */

struct ns_cache {
	char dom[256];
	char ser[256];
	struct ns_cache *next;
};

struct ns_cache *nsc=NULL;

struct alias *build_aliases(unsigned int addr)
{
	struct alias *a, *last=NULL;
	struct aliascache *act;
	act = ac;
	while(act) {
		if (act->addr == addr)  {
			a = g_new(struct alias, 1);
			a->next = last;
			strncpy(a->hostname, act->hostname, sizeof(a->hostname));
			last=a;
		}
		act = act->next;
	}
	return last;
}

unsigned char *get_server(char *domain, int force_reload)
{
	unsigned char result[256], *d, *e;
	int res, qdcount;
	unsigned short type;
	struct ns_cache *nc;
	HEADER *h;
	
	nc = nsc;
	while(nc) {
		if (!strcasecmp(domain,nc->dom)) {
			if (strlen(nc->ser))
				break;
		}
		nc=nc->next;
	}
	if (nc) {
		if (!force_reload) {
			if (strlen(nc->ser))
				return nc->ser;
			else
				return NULL;
		}
	} else {			
		nc = g_new(struct ns_cache, 1);
		nc->next = nsc;
		nsc=nc;
		strncpy(nc->dom, domain, sizeof(nc->dom));
	}
	h = (HEADER *)result;
	memset(result, 0, sizeof(result));
	res = res_query(domain, C_IN, T_NS, result, sizeof(result));

	d = result;
	e = result + res;
	/* Skip header */
	d += HFIXEDSZ;
	qdcount = ntohs(h->qdcount);
	while(qdcount-- > 0) {
		d += dn_skipname(d, e) + QFIXEDSZ;
	}
	/* Name server */	
	d += dn_skipname(d, e);
	GETSHORT(type,d);
	/* Skip over the rest */
	d += INT32SZ * 2;
	if (type != T_NS) {
#if 0
		fprintf(stderr, "Didn't get an NS record back on %s\n", domain);
#endif
		nc->ser[0]='\0';
		return NULL;
	}
	d += dn_expand(result, e, d, nc->ser, sizeof(nc->ser));
	return nc->ser;
}

/* Greatly stolen from nslookup's list.c */

typedef union {
    HEADER qb1;
    u_char qb2[PACKETSZ];
} querybuf;

#define NAME_LEN 256

static struct aliascache *getaliascache(char *name)
{
	struct aliascache *act;
	act = ac;
	while(act) {
		if (!strcasecmp(act->hostname, name))
			break;
		act=act->next;
	}
	return act;
}

static struct alias *getalias(struct net_object *no, char *name)
{
	struct alias *a;
	a = no->aliases;
	while(a) {
		if (!strcasecmp(a->hostname, name))
			break;
		a=a->next;
	}
	return a;
}

static int PrintListInfo(FILE *file, u_char *msg, u_char *eom, int qtype,
             char *domain, int recursive, struct net_page *np)
{
    register u_char	*cp;
    HEADER		*headerPtr;
    int			type, class, dlen, nameLen;
    u_int32_t		ttl;
    int			n, count, cnt=0;
    struct in_addr	inaddr;
    char		name[NAME_LEN];
    char		name2[NAME_LEN];
    int			stripped;
    struct alias	*a;
    struct net_object	*no;
    struct aliascache	*acn;


    /*
     * Read the header fields.
     */
    headerPtr = (HEADER *)msg;
    cp = msg + HFIXEDSZ;
    if (headerPtr->rcode != NOERROR) {
	return(headerPtr->rcode);
    }

    /*
     *  We are looking for info from answer resource records.
     *  If there aren't any, return with an error. We assume
     *  there aren't any question records.
     */

    if (ntohs(headerPtr->ancount) == 0) {
	return(-1);
    }
    for (n = ntohs(headerPtr->qdcount); n > 0; n--) {
	nameLen = dn_skipname(cp, eom);
	if (nameLen < 0)
	    return (-1);
	cp += nameLen + QFIXEDSZ;
    }
    for (count = ntohs(headerPtr->ancount); count > 0; count--) {
	nameLen = dn_expand(msg, eom, cp, name, sizeof name);
	if (nameLen < 0)
	    return (-1);
	cp += nameLen;

	type = _getshort((u_char*)cp);
	cp += INT16SZ;

	if (!(type == qtype || qtype == T_ANY) &&
	    !((type == T_NS || type == T_PTR) && qtype == T_A))
		return(0);

	class = _getshort((u_char*)cp);
	cp += INT16SZ;
	ttl = _getlong((u_char*)cp);
	cp += INT32SZ;
	dlen = _getshort((u_char*)cp);
	cp += INT16SZ;

	if (name[0] == 0)
		strcpy(name, "(root)");

	/* Strip the domain name from the data, if desired. */
	stripped = FALSE;

	/* XXX merge this into debug.c's print routines */

	switch (type) {
	    case T_A:
		if (class == C_IN) {
		    if (dlen != 4)
		    	break;
		    bcopy(cp, (char *)&inaddr, INADDRSZ);
		    cnt++;
		    if (caught_domain) {
		    	gtk_timeout_remove(caught_domain);
			caught_domain = 0;
		    }
		    if ((no = get_object(np, inaddr.s_addr))) {
		    	if (!getalias(no, name)) {
		        	a = g_new(struct alias, 1);
				strncpy(a->hostname, name, sizeof(a->hostname));
				a->next = no->aliases;
				no->aliases = a->next;
				if (!strcmp(no->hostname, inet_ntoa(inaddr))) {
					char *c;
					c = no->aliases->hostname;
					strncpy(no->hostname, c, sizeof(no->hostname));
 					if (option_trim) {
						/* Don't trim IP addresses */
						if (!atoi(c)) {
							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);
						}
					}
					gtk_label_set(GTK_LABEL(no->label), c);
				}
				fix_tooltip(no);
			}								
		    } else {
		        if (!getaliascache(name)) {
				acn = g_new(struct aliascache, 1);
				strncpy(acn->hostname, name, sizeof(acn->hostname));
				acn->addr = inaddr.s_addr;
				acn->next = ac;
				ac = acn;
			}
		    	discover_network_a(np, inet_ntoa(inaddr), "255.255.255.255", 0);
		    }
#if 0
		    printf("%s is %s\n", name, inet_ntoa(inaddr));
#endif
		}
		cp += dlen;
		break;

	    case T_CNAME:
 	    case T_MB:
	    case T_MG:
	    case T_MR:
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		cp += nameLen;
		break;
	    case T_NS:
	    case T_PTR:
	    case T_NSAP_PTR:
	    	
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		if (type == T_NS) {
			if (strcasecmp(name, name2)) {
				if (recursive) {
					cnt++;
					gather_hosts(np, name2, recursive,0);
				}
#if 0
				else
					printf("%s uses server %s\n",name, name2);
#endif
			}
		}		
		cp += nameLen;
		break;

	    case T_HINFO:
	    case T_ISDN:
		{
		    u_char *cp2 = cp + dlen;
		    if ((n = *cp++)) {
			cp += n;
		    }
		    if (cp == cp2)
			break;
		    if ((n = *cp++)) {
			cp += n;
		    }
		}
		break;

	    case T_SOA:
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		
		cp += nameLen;
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		cp += nameLen;
		for (n = 0; n < 5; n++) {
		    u_int32_t u;

		    u = _getlong((u_char*)cp);
		    cp += INT32SZ;
		}
		break;

	    case T_MX:
	    case T_AFSDB:
		case T_RT:
		_getshort((u_char*)cp);
		cp += INT16SZ;
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		cp += nameLen;
		break;

	    case T_PX:
		_getshort((u_char*)cp);
		cp += INT16SZ;
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
			return (-1);
		cp += nameLen;
		nameLen = dn_expand(msg, eom, cp, name2, sizeof name2);
		if (nameLen < 0) 
		    return (-1);
		cp += nameLen;
		break;

	    case T_X25:
	    case T_TXT:
	    case T_NAPTR: 
	    case T_SRV:
	    case T_NSAP:
	    case T_AAAA: 
	    case T_LOC: 
	    case T_MINFO:
	    case T_RP:
	    case T_UINFO:
	    case T_UID:
	    case T_GID:
	    case T_WKS:
		cp += dlen;
		break;
	}
    }
    return(cnt);
}
static int data_ready(int fd) 
{
	    fd_set fds;
	    int res;
	    int cnt=0;
	    struct timeval tv;
	    
	    while(cnt < 5) {
	        FD_ZERO(&fds);
	        FD_SET(fd, &fds);
	        tv.tv_sec = 1;
	        tv.tv_usec = 0;
	    	res = select(fd + 1, &fds, NULL, NULL, &tv);
		if (res>= 0)
			return res;
		cnt++;
		while (gtk_events_pending())
               		gtk_main_iteration();

	    }
	    return -1;
}
static int ListSubr(int qtype, char *domain, struct in_addr server_addr,
       int recursive, struct net_page *np) 
{
	querybuf		buf;
	struct sockaddr_in	sin;
	int			msglen;
	int			amtToRead;
	int			numRead, n;
	int			numAnswers = 0;
	int			numRecords = 0;
	int			result = 0;
	int			soacnt = 0;
	int			count, done;
	int			sockFD;
	int			cnt=0;
	u_short			len;
	u_char			*cp;
	char			dname[2][NAME_LEN];
	static u_char		*answer = NULL;
	static int		answerLen = 0;
	enum {
	    NO_ERRORS,
	    ERR_READING_LEN,
	    ERR_READING_MSG,
	    ERR_PRINTING
	} error = NO_ERRORS;

	/*
	 *  Create a query packet for the requested domain name.
	 */
	msglen = res_mkquery(QUERY, domain, C_IN, T_AXFR,
			     NULL, 0, 0, buf.qb2, sizeof buf);
	if (msglen < 0) {
	    if (_res.options & RES_DEBUG) {
		fprintf(stderr, "*** ls: res_mkquery failed\n");
	    }
	    return (-1);
	}

	bzero((char *)&sin, sizeof(sin));
	sin.sin_family	= AF_INET;
	sin.sin_port	= htons(53);

	/*
	 *  Check to see if we have the address of the server or the
	 *  address of a server who knows about this domain.
	 *
	 *  For now, just use the first address in the list.
	 */

	sin.sin_addr = server_addr;

	/*
	 *  Set up a virtual circuit to the server.
	 */
	if ((sockFD = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	    perror("ls: socket");
	    return(-1);
	}
	if (connect(sockFD, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
	    if (errno == ECONNREFUSED) {
		perror("ls: no reply");
	    } else {
		perror("ls: connect");
	    }
	    (void) close(sockFD);
	    sockFD = -1;
	    return -1;
	}

	/*
	 * Send length & message for zone transfer
	 */

	__putshort(msglen, (u_char *)&len);

        if (write(sockFD, (char *)&len, INT16SZ) != INT16SZ ||
            write(sockFD, (char *) &buf, msglen) != msglen) {
		perror("ls: write");
		(void) close(sockFD);
		sockFD = -1;
		return(-1);
	}


	dname[0][0] = '\0';
	done = 0;
	while(!done) {
	    unsigned short tmp;
	    

	    /*
	     * Read the length of the response.
	     */

	    cp = (u_char *)&tmp;
	    amtToRead = INT16SZ;
	    
	    if (!data_ready(sockFD)) {
	    	error = ERR_READING_LEN;
		break;
	    }
	    while ((numRead = read(sockFD, cp, amtToRead)) > 0) {
		cp += numRead;
		if ((amtToRead -= numRead) <= 0)
			break;
	    }
	    if (numRead <= 0) {
		error = ERR_READING_LEN;
		break;
	    }

	    if ((len = _getshort((u_char*)&tmp)) == 0) {
		break;	/* nothing left to read */
	    }

	    /*
	     * The server sent too much data to fit the existing buffer --
	     * allocate a new one.
	     */
	    if (len > (u_int)answerLen) {
		answerLen = len;
		if (answerLen != 0) {
		    answer = (unsigned char *)realloc(answer, answerLen);
		} else {
		    answer = (unsigned char *)malloc(answerLen);
		}
	    }

	    /*
	     * Read the response.
	     */

	    amtToRead = len;
	    cp = answer;
	    if (!data_ready(sockFD)) {
	    	error = ERR_READING_LEN;
		break;
	    }
	    while (amtToRead > 0 && (numRead=read(sockFD, cp, amtToRead)) > 0) {
		cp += numRead;
		amtToRead -= numRead;
	    	if ((amtToRead > 0) && !data_ready(sockFD)) {
	    		error = ERR_READING_MSG;
			numRead = 0;
			break;
	    	}
	    }
	    if (numRead <= 0) {
		error = ERR_READING_MSG;
		perror("nothing to read");
		break;
	    }

	    result = PrintListInfo(stdout, answer, cp, qtype, dname[0], recursive, np);
	    
	    if (result < 0) {
		error = ERR_PRINTING;
		break;
	    }
	    cnt += result;
	    numRecords += htons(((HEADER *)answer)->ancount);
	    numAnswers++;
	    /* Header. */
	    cp = answer + HFIXEDSZ;
	    /* Question. */
	    for (count = ntohs(((HEADER* )answer)->qdcount);
		 count > 0;
		 count--)
		    cp += dn_skipname(cp, answer + len) + QFIXEDSZ;
	    /* Answer. */
	    for (count = ntohs(((HEADER* )answer)->ancount);
		 count > 0;
		 count--) {
		int type, class, rlen;

		n = dn_expand(answer, answer + len, cp,
			      dname[soacnt], sizeof dname[0]);
		if (n < 0) {
		    error = ERR_PRINTING;
		    done++;
		    break;
		}
		cp += n;
		GETSHORT(type, cp);
		GETSHORT(class, cp);
		cp += INT32SZ;	/* ttl */
		GETSHORT(rlen, cp);
		cp += rlen;
		if (type == T_SOA && soacnt++ &&
		    !strcasecmp(dname[0], dname[1])) {
		    done++;
		    break;
		}
	    }
	}

	(void) close(sockFD);
	sockFD = -1;

	switch (error) {
	    case NO_ERRORS:
		return (cnt);

	    case ERR_READING_LEN:
		return(-1);

	    case ERR_PRINTING:
		return(result);

	    case ERR_READING_MSG:
#if 0
		headerPtr = (HEADER *) answer;
		fprintf(stderr,"*** ls: error receiving zone transfer:\n");
		fprintf(stderr,
	       "  result: %s, answers = %d, authority = %d, additional = %d\n",
			_res_resultcodes[headerPtr->rcode],
			ntohs(headerPtr->ancount), ntohs(headerPtr->nscount),
			ntohs(headerPtr->arcount));
#endif
		return(-1);
	    default:
		return(-1);
	}
	/* Never reached */
	return (-1);
}

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

int gather_hosts(struct net_page *np, char *domain, int recursive, int add)
{
	char *server;
	struct in_addr new;
	struct hostent *hp;
	struct domain *d;
	char buf[256];

	if (add) {
		d = g_new(struct domain, 1);
		strncpy(d->domain, domain, sizeof(d->domain));
		d->next = np->dcontents;
		d->recursive = recursive;
		np->dcontents=d;
		append_domain(np->pe, d);
	}
		

	server = get_server(domain, 0);
	if (!server)
		return -1;
	hp = gethostbyname(server);
	if (!hp) {
		printf("Unable to lookup server '%s' for domain '%s'\n", server, domain);
		return -1;
	}
	new = *(struct in_addr *)hp->h_addr;
#if 0
	printf("ip for new nameserver is %s\n",inet_ntoa(new));
#endif
	immediate_setcursor(GDK_WATCH);
	snprintf(buf, sizeof(buf), "Analyzing domain '%s'",domain);
	set_status(buf);
	if (!caught_domain)
		caught_domain = gtk_timeout_add(10000, cancel_domain, NULL);
	return ListSubr(T_ANY, domain, new, recursive, np);
	
}

