/*  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.
 *
 * 	To compile:
 *			cc -o w whois.c -DSTANDALONE
 *
 *		(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

#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;
}

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, m;
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);

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

	r->start = s &~ (((unsigned)0xffffffff) >> m);
	r->end = s | (((unsigned)0xffffffff) >> m);
	return r;
}
	
char *match_prefix(const char *prefix, const char *target)
{
  /* Target will be something like "origin:        AS22773" and prefix will be "origin:" and
   * we return a pointer to "AS22773" */
  while (*prefix) {
    if (tolower(*prefix) != tolower(*target))
      return NULL;
    prefix++;
    target++;
  }
  while (isspace(*target))
    target++;
  return strdup(target);
}

ip_blk_t *match_iprange(char *target)
{
  /* matches something like "1.2.3.4-5.6.7.8" */
  char *pos, *dash, *beforedash;
  ip_blk_t *out;

  while (isspace(*target))
    target++;
  pos = target;
  while (*pos && !isspace(*pos))
	pos++;

  beforedash = strdup(target);
  beforedash[pos-target] = 0;

  dash = strchr(target, '-');
  if (!dash)
    return NULL;
  dash++;
  while (isspace(*dash))
    dash++;

  return w_blk2range(beforedash, dash);
}

ip_blk_t *match_ipprefix(char *target)
{
  /* matches something like 1.2.3.0/24 */
  char *slash, *pos;
  char *beforeslash;
  ip_blk_t *out;

  while (isspace(*target))
    target++;
  pos = target;
  while (*pos && !isspace(*pos) && *pos != '/')
    pos++;
  beforeslash = strdup(target);
  beforeslash[pos - target] = 0;

  slash = strchr(target, '/');
  if (!slash) return NULL;

  slash++;

  return w_mask2range(beforeslash, slash);
}

char *match_inparens(char *target)
{
  /* matches something like "    (HELLO)" and returns "HELLO" */
  char *end, *res;

  target = strchr(target, '(');
  if (!target)
    return NULL;
  
  target++;
  end = strchr(target, ')');
  if (!end)
    return NULL;

  res = strdup(target);
  res[end - target] = 0;
  return res;
}

char *match_afterparens(char *target)
{
  /* matches something like "   (HELLO) xxx" and returns a pointer to "xxx" */
  target = strchr(target, '(');
  if (!target) return NULL;
  target = strchr(target, ')');
  if (!target) return NULL;
  target++;
  while(*target && isspace(*target)) target++;
  if (*target) return strdup(target);
  else return NULL;
}

void
w_init()
{
int e;

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


#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++) {
		char *value;
		if ((value = match_prefix("local-as:", ls[i].ptr))) {
			if (ans)
				free(ans);
			ans = value;
			break;
		}
		if ((value = match_prefix("route:", ls[i].ptr))) {
			a = match_ipprefix(value);

			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 && (value = match_prefix("origin:", ls[i].ptr))) {
			if (ans)
				free(ans);
			ans = value;
		}
	}

	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 *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++) {
		char *value;
		if ((value = match_prefix("inetnum:", ls[i].ptr))) {
			a = match_ipprefix(value);

			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 && (value = match_prefix("netname:", ls[i].ptr))) {
			if (ans)
				free(ans);
			ans = value;
		}
	}

	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 *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++) {
		char *value;
		if ((value = match_prefix("netname:", ls[i].ptr))) {
			ans = value;
			break;
		}
	}

	if (!ans) {

	for (i = 0; ls[i].ptr; i++) {
		char *value;
		if ((value = match_inparens(ls[i].ptr))) {
			char *after = match_afterparens(ls[i].ptr);
			if (after) {
				na = value;
				a = match_iprange(after);
			} else {
				na = value;
				if (ls[i+1].ptr && (a = match_iprange(ls[i+1].ptr))) {
				  /* successful match */
				} 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++) {
		char *value;
		if ((value = match_prefix("netname:", ls[i].ptr))) {
			ans = value;
			break;
		}
	}

	free(ls); free(reply); 

	{
	  char *other = NULL;
	  if (ans && strstr(ans, "RIPE")) {
	    other = w_lookup_netname_other(addr, "whois.ripe.net");
	  }
	  
	  if (ans && !strncmp(ans, "APNIC", 5)) {
	    other = w_lookup_netname_other(addr, "whois.apnic.net");
	  }

	  if (other) {
	    char *together = malloc(strlen(ans) + strlen(other) + 2);
	    strcpy(together, ans);
	    strcat(together, "/");
	    strcat(together, other);
	    free(ans);
	    ans = together;
	  }
	}
	

	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

