/*  Handle communication with whois servers. 
 *  Includes:
 *		- simplified access to regular expressions
 *		- tokenizer
 *		- lookup functions for AS and NETNAME, working with
 *		  ARIN, RIPE, APNIC as well as RA. Will do recursive
 *		  lookups as well.
 *		- Convenient framework for further whois digging.
 *
 * 	Requires GNU "rx lite" regular expressions.
 *    Download from ftp://prep.ai.mit.edu/gnu/rx/rx-1.5.tar.gz
 *		XXX glibc regex is broken in 2.2.5 and Solaris has different
 *		    regexp interface, so use the above. They work.
 * 	To compile:
 *			cc -o w whois.c -DSTANDALONE -lrx
 *
 *		(c) 2002 Ugen Antsilevitch (ugen@xonix.com)
 *
 * This file is part of LFT.
 *
 * The LFT software provided in this Distribution is
 * Copyright 2002 MainNerve, Inc.
 *
 * The full text of our legal notices is contained in the file called
 * COPYING, included with this Distribution.
 *
 *
 */

#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#include <string.h>
#define read(a, b, c)	recv(a, b, c, 0)
#define write(a, b, c)  send(a, b, c, 0)
#define close(s) closesocket(s)
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#ifdef sun
#include <strings.h>
#endif
#endif

#include "regex.h"

#define ASSERT(x)	if (!(x)) { fprintf(stderr, "Assertion ("#x") failed\n"); exit(1); }

#ifdef WIN32
char *
index(char *s, char c)
{
	char *t;
	if (!s)
		return NULL;

	for (t = s; *t; t++)
		if (*t == c) {
			return t;
		}

	/* Return terminating \0 if 
	   specifically requested */
	if (c == '\0')
		return t;

	return NULL;
}
#endif

char *
lft_strndup(char *s, int len)
{
char *rs;

	if (!s || len < 0)
		return NULL;

	if ((int)strlen(s) <= len)
		return(strdup(s));

	if (!(rs = malloc(len + 1)))
		return NULL;

	if (len > 0)
		strncpy(rs, s, len);
	rs[len] = 0;

	return rs;
}

#ifdef WIN32
int
inet_aton(const char *cp, struct in_addr *pin)
{
	if (!pin)
		return -1;

	pin->s_addr = inet_addr(cp);
	return (pin->s_addr != -1) ? 1 : 0;
}
#endif

char *
strskip(char *buf, char *sep)
{
char *c;

	c = buf;
	while (*c != '\0') {
		if (index(sep, *c)) 
			c++;
		else
			break;
	}

	return c;
}

typedef struct token_s {
	char	*ptr;
} token_t;

token_t *
tokens(char *buf, char *sep)
{
char *c, *c1;
int size, cur;
token_t *rt;

	if (!buf || !sep)
		return NULL;

	size = 1;
	for (c = buf; *c ; c++) 
		if (index(sep, *c)) {
			size++;
			while (*c && index(sep, *c)) 
					c++;
		}

	size++; /* for the NULL */

	if (!(rt = (token_t *)malloc(size * sizeof(token_t))))
		return NULL;

	memset(rt, 0, size * sizeof(token_t));

	rt[0].ptr = buf;
	cur = 0;

	for (c = buf; *c ; c++) {
		if (index(sep, *c)) {
			c1 = c;
			while (*c && index(sep, *c)) 
					c++;
			if (*c) 
				rt[++cur].ptr = c;

			*c1 = '\0';
		} 
	}

	rt[++cur].ptr = NULL;

	return rt;
}

#define _RRES_MAX_	10
static regmatch_t _rres_arr_[_RRES_MAX_];
static char *_rstr_ = NULL;

int 
w_rmatch(regex_t *r, const char *str)
{
	_rstr_ = (char *)str;
	return (!regexec(r, str, _RRES_MAX_, _rres_arr_, 0));
}

char *
w_rres(int n)
{
	if ((n >= _RRES_MAX_) || (n < 0))
		return NULL;

	if (_rres_arr_[n].rm_so == -1)
		return NULL;

	return (lft_strndup((_rstr_ + _rres_arr_[n].rm_so), _rres_arr_[n].rm_eo - _rres_arr_[n].rm_so));
}

typedef struct ip_blk_s {
	unsigned int	start;
	unsigned int	end;
} ip_blk_t;

ip_blk_t *
w_blk2range(char *s_start, char *s_end)
{
struct in_addr in;
unsigned int s, e;
ip_blk_t *r;

	if (!s_start || !s_end)
		return NULL;

	if (!inet_aton(s_start, &in))
		return NULL;

	s = ntohl(in.s_addr);

	if (!inet_aton(s_end, &in))
		return NULL;

	e = ntohl(in.s_addr);

	if (!(r = malloc(sizeof(ip_blk_t))))
		return NULL;

	r->start = s;
	r->end = e;
	return r;
}

ip_blk_t *
w_mask2range(char *addr, char *mask)
{
struct in_addr in;
unsigned int s, e, m, mb = 0x1;
ip_blk_t *r;

	if (!addr || !mask)
		return NULL;

	m = atoi(mask);
	if ((m < 0) || (m > 32))
		return NULL;

	if (!inet_aton(addr, &in))
		return NULL;

	s = ntohl(in.s_addr); e = s;
	
	while(m) {
		s = s & (~mb);
		e = e | mb;
		mb = mb<<1;
		m--;
	}

	if (!(r = malloc(sizeof(ip_blk_t))))
		return NULL;

	r->start = s;
	r->end = e;
	return r;
}
	

/* XXX */
static regex_t	r_origin;
static regex_t	r_local_as;
static regex_t r_netname;
static regex_t r_inetnum;
static regex_t r_route;
static regex_t r_arin_single;
static regex_t r_arin_single_name;
static regex_t r_arin_single_net;
static regex_t r_arin_single_name_net;

static char	sr_origin[] = "origin:[[:blank:]]+([^[:blank:]]+)";
static char	sr_local_as[] = "local-as:[[:blank:]]+([^[:blank:]]+)";
static char	sr_netname[] = "netname:[[:blank:]]+([^[:blank:]]+)";
static char	sr_inetnum[] = "inetnum:[[:blank:]]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)[[:blank:]]*-[[:blank:]]*([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)";
static char	sr_route[] = "route:[[:blank:]]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)/([0-9]+)";
static char sr_arin_single[] = "To single out one record";
static char sr_arin_single_name[] = "[[:blank:]]+\\(([^[:blank:]]+)\\)";
static char sr_arin_single_net[] = "[[:blank:]]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)[[:blank:]]*-[[:blank:]]*([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)";
static char sr_arin_single_name_net[] = "[[:blank:]]+\\(([^[:blank:]]+)\\)[[:blank:]]+[^[:blank:]]+[[:blank:]]+([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)[[:blank:]]*-[[:blank:]]*([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)";



void
w_init()
{
int e;

#ifdef WIN32
WORD wVersionRequested;
WSADATA wsaData;
	wVersionRequested = MAKEWORD( 2, 2 );
	WSAStartup( wVersionRequested, &wsaData );
#endif

	e = regcomp(&r_origin, sr_origin, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_local_as, sr_local_as, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_netname, sr_netname, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_inetnum, sr_inetnum, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_route, sr_route, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_arin_single, sr_arin_single, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_arin_single_name, sr_arin_single_name, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_arin_single_net, sr_arin_single_net, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	e = regcomp(&r_arin_single_name_net, sr_arin_single_name_net, REG_EXTENDED|REG_ICASE);
	ASSERT(e == 0);
	
}


#define PORT_WHOIS 	43
char *
w_ask(char *serv, char *q)
{
int s;
struct sockaddr_in sin;
struct hostent *hp;
char *br;
int q_s, br_s, cur, n;
char buf[128];


	if (!serv || !q)
		return NULL;

	if (!(hp = gethostbyname(serv)))
		return NULL;

	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT_WHOIS);
	memcpy((void *)&sin.sin_addr, hp->h_addr, hp->h_length);

	if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
		return NULL;

	if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) 
		return NULL;

	br_s = 512;
	if (!(br = malloc(br_s)))
		return NULL;

	q_s = strlen(q);
	if (write(s, q, q_s) != q_s || write(s, "\r\n", 2) != 2)
		return NULL;

	cur = 0;
	while ((n = read(s, buf, sizeof(buf))) > 0) {
		if ((cur + n) >= br_s) {
			br_s = br_s * 2;
			if (!(br = realloc(br, br_s)))
				return NULL;
		}
		strncpy((char *)&br[cur], buf, n);
		cur += n;	
	}
	br[cur] = 0;

	close(s);

	return br;
}

char *
w_lookup_as(char *addr)
{
	token_t *ls;
	ip_blk_t *a = NULL, *b = NULL;
	char *sa, *sb;
	char *reply, *ans = NULL;
	int i;
	int use_this = 1;

	if (!addr)
		return NULL;

	reply = w_ask("whois.ra.net", addr);
	if (!reply)
		return NULL;
	ls = tokens(reply, "\n");

	for (i = 0; ls[i].ptr; i++) {
		if (w_rmatch(&r_local_as, ls[i].ptr)) {
			if (ans)
				free(ans);
			ans = w_rres(1);
			break;
		}
		if (w_rmatch(&r_route, ls[i].ptr)) {
			a = w_mask2range((sa = w_rres(1)), (sb = w_rres(2)));
			free(sa); free(sb);

			if (b) {
				if (((b->end - b->start) > (a->end - a->start))) {
					use_this = 1;
					free(b);
					b = a;
					a = NULL;
				} else {
					use_this = 0;
					free(a);
					a = NULL;
				}
			} else {
				use_this = 1;
				b = a;
				a = NULL;
			}
		}
		if (use_this && w_rmatch(&r_origin, ls[i].ptr)) {
			if (ans)
				free(ans);
			ans = w_rres(1);
		}
	}

	free(ls); free(reply); if (b) free(b);
	return ans;
}

char *
w_lookup_netname_other(char *addr, char *serv)
{
	token_t *ls;
	ip_blk_t *a = NULL, *b = NULL;
	char *sa, *sb;
	char *reply, *ans = NULL;
	int i;
	int use_this = 1;

	if (!addr || !serv)
		return NULL;

	reply = w_ask(serv, addr);
	if (!reply)
		return NULL;
	ls = tokens(reply, "\n");

	for (i = 0; ls[i].ptr; i++) {
		if (w_rmatch(&r_inetnum, ls[i].ptr)) {
			a = w_mask2range((sa = w_rres(1)), (sb = w_rres(2)));
			free(sa); free(sb);

			if (b) {
				if (((b->end - b->start) > (a->end - a->start))) {
					use_this = 1;
					free(b);
					b = a;
					a = NULL;
				} else {
					use_this = 0;
					free(a);
					a = NULL;
				}
			} else {
				use_this = 1;
				b = a;
				a = NULL;
			}
		}
		if (use_this && w_rmatch(&r_netname, ls[i].ptr)) {
			if (ans)
				free(ans);
			ans = w_rres(1);
		}
	}

	free(ls); free(reply); if (b) free(b);
	return ans;
}

char *
w_lookup_netname(char *addr)
{
	token_t *ls;
	ip_blk_t *a = NULL, *b = NULL;
	char *sa, *sb;
	char *na = NULL, *nb = NULL;
	char *reply, *ans = NULL;
	int i;
	int have_new, have_old;

	if (!addr)
		return NULL;

	reply = w_ask("whois.arin.net", addr);
	if (!reply)
		return NULL;

	ls = tokens(reply, "\n");

	ans = NULL;
	for (i = 0; ls[i].ptr; i++) {
		if (w_rmatch(&r_netname, ls[i].ptr)) {
			ans = w_rres(1);
			break;
		}
	}

	if (ans) {
		free(reply);
		free(ls);
		return ans;
	}

	for (i = 0; ls[i].ptr; i++) {
		if (w_rmatch(&r_arin_single_name_net, ls[i].ptr)) {
			na = w_rres(1);
			a = w_blk2range((sa = w_rres(2)), (sb = w_rres(3)));
			free(sa); free(sb);
		} else if (w_rmatch(&r_arin_single_name, ls[i].ptr)) {
			na = w_rres(1);
			if (ls[i+1].ptr && w_rmatch(&r_arin_single_net, ls[i+1].ptr)) {
					a = w_blk2range((sa = w_rres(1)), (sb = w_rres(2)));
					free(sa); free(sb);
			} else { /* Bad format */
				free(na); na = NULL;
				continue;
			}
		}

		have_new = (na && a);
		have_old = (nb && b);

		if (have_new) {
			if (have_old) {
				if (((b->end - b->start) > (a->end - a->start))) {
					/* keep new, discard old */
					free(nb); free(b);
					nb = na; na = NULL;
					b = a; a = NULL;
				} else { /* keep old, discard new */
					free(na); free(a);
					na = NULL;
					a = NULL;
				}
			} else { /* nothing old, save new */
				nb = na; na = NULL;
				b = a; a = NULL;
			}
		}
	} /* loop */

	if (na) free(na);
	if (a) free(a);
	if (b) free(b);
	free(ls); free(reply);
	if (!nb)
		return NULL;

	/* Add "!" to the beginning of the question */
	na = malloc(strlen(nb) + 2);
	strcpy(&na[1], nb);
	na[0] = '!';
	free(nb);

	reply = w_ask("whois.arin.net", na);
	free(na);
	if (!reply)
		return NULL;

	ls = tokens(reply, "\n");

	for (i = 0; ls[i].ptr; i++) {
		if (w_rmatch(&r_netname, ls[i].ptr)) {
			ans = w_rres(1);
			break;
		}
	}

	free(ls); free(reply); 

	if (ans && strstr(ans, "RIPE")) {
		free(ans);
		return(w_lookup_netname_other(addr, "whois.ripe.net"));
	}

	if (ans && !strncmp(ans, "APNIC", 5)) {
		free(ans);
		return(w_lookup_netname_other(addr, "whois.apnic.net"));
	}

	return ans;
}
	


#ifdef STANDALONE
int main(int ac, char **av)
{
	struct hostent *he;
	char *addr;
	struct in_addr in;

	w_init();
	if (ac == 2) {
		if (inet_aton(av[1], &in)) {
			addr = av[1];
		} else {
			if (!(he = gethostbyname(av[1]))) {
				printf("Cannot resolve %s\n", av[1]);
				exit(0);
			}

			memcpy(&in, he->h_addr, he->h_length);
			addr = inet_ntoa(in);
		}
		printf("Address: %s\n", addr);
		printf("Netname: %s\n", w_lookup_netname(addr));
		printf("AS: %s\n", w_lookup_as(addr));
	}
}
#endif

