/* Copyright 1999 by Marco d'Itri <md@linux.it>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/* System library */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <signal.h>

/* Application-specific */
#include "config.h"
#include "data.h"
#include "whois.h"

/* Global variables */
int sockfd, verb = 0, hide_discl = 2;

int main(int argc, char *argv[])
{
    int ch, nopar = 0, optC = 0;
    const char *server = NULL, *port = NULL;
    char *p, *q, *qstring = NULL, fstring[64] = "\0";

#ifdef ENABLE_NLS
    setlocale(LC_MESSAGES, "");
    bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
    textdomain(NLS_CAT_NAME);
#endif

    while ((ch = getopt(argc, argv, "acCFg:h:Hi:LmMp:rRs:St:T:v:V")) > 0) {
	/* RIPE flags */
	if (strchr(ripeflags, ch)) {
	    for (p = fstring; *p != '\0'; p++);
	    sprintf(p--, "-%c ", ch);
	    continue;
	}
	if (strchr(ripeflagsp, ch)) {
	    for (p = fstring; *p != '\0'; p++);
	    sprintf(p--, "-%c %s ", ch, optarg);
	    if (ch == 't' || ch == 'v')
		nopar = 1;
	    continue;
	}
	/* program flags */
	switch (ch) {
	case 'h':
	    server = q = malloc(strlen(optarg) + 1);
	    for (p = optarg; *p != '\0' && *p != ':'; *q++ = tolower(*p++));
	    if (*p == ':')
		port = p + 1;
	    *q = '\0';
	    break;
	case 'H':
	    hide_discl = 0;	/* enable disclaimers hiding */
	    break;
	case 'p':
	    port = optarg;
	    break;
	case 'C':
	    optC = 1;
	    break;
	case 'V':
	    verb = 1;
	    break;
	default:
	    usage();
	}
    }
    argc -= optind;
    argv += optind;

    if (argc == 0 && !nopar)	/* there is no parameter */
	usage();

    /* parse other parameters, if any */
    if (nopar) {
	qstring = malloc(1);
	*qstring = '\0';
    } else {
	int qslen = 0;

	while (1) {
	    qslen += strlen(*argv) + 1;
	    qstring = realloc(qstring, qslen);
	    strcat(qstring, *argv++);
	    if (argc == 1)
		break;
	    strcat(qstring, " ");
	    argc--;
	}
    }

    if (!server &&
#ifndef FIRST_ASK_INTERNIC
	    optC &&
#endif
	    domfind(qstring, gtlds)) {
	if (verb)
	    puts(_("Connecting to whois.internic.net."));
	sockfd = openconn("whois.internic.net", NULL);
	server = query_crsnic(sockfd, qstring);
	closeconn(sockfd);
	if (!server)
	    exit(0);
	printf(_("\nFound InterNIC referral to %s.\n\n"), server);
    }

    if (!server) {
	if (!(server = whichwhois(qstring))) {
	    if (!(server = getenv("WHOIS_SERVER")))
		server = DEFAULTSERVER;
	    if (verb)
		printf(_("Using default server %s.\n"), server);
	} else if (server[0] == '\001') {
	    puts(_("This domain has no whois server, but you can access the "
			"whois database at"));
	    puts(server + 1);
	    exit(0);
	} else if (server[0] == '\002') {
	    puts(server + 1);
	    exit(0);
	} else if (server[0] == '\003') {
	    puts(_("This domain has no whois server."));
	    exit(0);
	} else if (verb)
	    printf(_("Using server %s.\n"), server);
    }

    p = queryformat(server, fstring, qstring);
    if (verb) {
	printf(_("Query string: \"%s\"\n\n"), p);
    }
    strcat(p, "\r\n");

    signal(SIGTERM, sighandler);
    signal(SIGINT, sighandler);

    sockfd = openconn(server, port);
    do_query(sockfd, p);
    closeconn(sockfd);

    exit(0);
}

const char *whichwhois(const char *s)
{
    unsigned long ip;
    unsigned int i;

    /* -v or -t has been used */
    if (*s == '\0')
	return "whois.ripe.net";

    /* IPv6 address */
    if (strchr(s, ':')) {
	if (strncasecmp(s, "2001:2", 6) == 0)	/* XXX ugly hack! */
	    return "whois.apnic.net";
	if (strncasecmp(s, "2001:4", 6) == 0)
	    return "whois.arin.net";
	if (strncasecmp(s, "2001:6", 6) == 0)
	    return "whois.ripe.net";
	//if (strncasecmp(s, "3ffe", 4) == 0)
	    return "whois.6bone.net";
    }

    /* no dot and no hyphen means it's a NSI NIC handle or ASN (?) */
    if (!strpbrk(s, ".-")) {
	const char *p;

	for (p = s; *p != '\0'; p++);		/* go to the end of s */
	if (strncasecmp(s, "as", 2) == 0 &&	/* it's an AS */
	    ((s[2] >= '0' && s[2] <= '9') || s[2] == ' '))
	    return whereas(atoi(s + 2), as_assign);
	else if (strncasecmp(p - 2, "jp", 2) == 0) /* JP NIC handle */
	    return "whois.nic.ad.jp";
	else /* it's a NSI NIC handle or something we don't know about */
	    return NULL;
    }

    /* smells like an IP? */
    if ((ip = myinet_aton(s))) {
	for (i = 0; ip_assign[i].serv; i++)
	    if ((ip & ip_assign[i].mask) == ip_assign[i].net)
		return ip_assign[i].serv;
	if (verb)
	    puts(_("I don't know where this IP has been delegated.\n"
		   "I'll try ARIN and hope for the best..."));
	return "whois.arin.net";
    }

    /* check TLD list */
    for (i = 0; tld_serv[i]; i += 2)
	if (domcmp(s, tld_serv[i]))
	    return tld_serv[i + 1];

    /* no dot but hyphen */
    if (!strchr(s, '.')) {
	/* search for strings at the start of the word */
	for (i = 0; nic_handles[i]; i += 2)
	    if (strncasecmp(s, nic_handles[i], strlen(nic_handles[i])) == 0)
		return nic_handles[i + 1];
	if (verb)
	    puts(_("I guess it's a netblock name but I don't know where to"
		   " look it up."));
	return "whois.arin.net";
    }

    /* has dot and hypen and it's not in tld_serv[], WTF is it? */
    if (verb)
	puts(_("I guess it's a domain but I don't know where to look it"
	       " up."));
    return NULL;
}

const char *whereas(int asn, struct as_del aslist[])
{
    int i;

    if (asn > 16383)
	puts(_("Unknown AS number. Please upgrade this program."));
    for (i = 0; aslist[i].serv; i++)
	if (asn >= aslist[i].first && asn <= aslist[i].last)
	    return aslist[i].serv;
    return "whois.arin.net";
}

char *queryformat(const char *server, const char *flags, const char *query)
{
    char *buf;
    int i, isripe = 0;

    /* +10 for CORE; +2 for \r\n; +1 for NULL*/
    buf = malloc(strlen(flags) + strlen(query) + 10 + 2 + 1);
    *buf = '\0';
    for (i = 0; ripe_servers[i]; i++)
	if (strcmp(server, ripe_servers[i]) == 0) {
	    strcat(buf, "-V " IDSTRING " ");
	    isripe = 1;
	    break;
	}
    if (!isripe)
	for (i = 0; ripe_servers_old[i]; i++)
	    if (strcmp(server, ripe_servers_old[i]) == 0) {
		strcat(buf, "-V" IDSTRING " ");
		isripe = 1;
		break;
	    }
    if (*flags != '\0') {
	if (!isripe && strcmp(server, "whois.corenic.net") != 0)
	    puts(_("Warning: RIPE flags ignored for a traditional server."));
	else
	    strcat(buf, flags);
    }
    if (!isripe &&
	    (strcmp(server, "whois.arin.net") == 0 ||
	     strcmp(server, "whois.nic.mil") == 0) &&
	    strncasecmp(query, "AS", 2) == 0 &&
	    query[2] >= '0' && query[2] <= '9')
	sprintf(buf, "AS %s", query + 2);	/* fix query for ARIN */
    else if (!isripe && strcmp(server, "whois.corenic.net") == 0)
	sprintf(buf, "--machine %s", query);	/* machine readable output */
    else if (!isripe && strcmp(server, "whois.ncst.ernet.in") == 0 &&
	    !strchr(query, ' '))
	sprintf(buf, "domain %s", query);	/* ask for a domain */
    else if (!isripe && strcmp(server, "whois.nic.ad.jp") == 0) {
	char *lang = getenv("LANG");	/* not a perfect check, but... */
	if (!lang || (strncmp(lang, "ja", 2) != 0))
	    sprintf(buf, "%s/e", query);	/* ask for english text */
	else
	    strcat(buf, query);
    } else
	strcat(buf, query);
    return buf;
}

void do_query(const int sock, const char *query)
{
    char buf[200];
    FILE *fi;
#ifdef HIDE_DISCL
    int i = 0, hide = hide_discl;
#endif

    fi = fdopen(sock, "r");
    if (write(sock, query, strlen(query)) < 0)
	err_sys("write");
    if (shutdown(sock, 1) < 0)
	err_sys("shutdown");
    while (fgets(buf, 200, fi)) {	/* XXX errors? */
#ifdef HIDE_DISCL
	if (hide == 1) {
	    if (strncmp(buf, hide_strings[i+1], strlen(hide_strings[i+1]))==0)
		hide = 2;	/* stop hiding */
	    continue;		/* hide this line */
	}
	if (hide == 0) {
	    for (i = 0; hide_strings[i] != NULL; i += 2) {
		if (strncmp(buf, hide_strings[i], strlen(hide_strings[i]))==0){
		    hide = 1;	/* start hiding */
		    break;
		}
	    }
	    if (hide == 1)
		continue;	/* hide the first line */
	}
#endif
#ifdef EXT_6BONE
	/* % referto: whois -h whois.arin.net -p 43 as 1 */
	if (strncmp(buf, "% referto:", 10) == 0) {
	    char nh[256], np[16], nq[1024];

	    if (sscanf(buf, REFERTO_FORMAT, nh, np, nq) == 3) {
		int fd;

		if (verb)
		    printf(_("Detected referral to %s on %s.\n"), nq, nh);
		strcat(nq, "\r\n");
		fd = openconn(nh, np);
		do_query(fd, nq);
		closeconn(fd);
		continue;
	    }
	}
#endif
	fputs(buf, stdout);
    }
    if (ferror(fi))
	err_sys("fgets");

#ifdef HIDE_DISCL
    if (hide == 1)
	err_quit(_("Catastrophic error: disclaimer text has been changed.\n"
		   "Please upgrade this program.\n"));
#endif
}

const char *query_crsnic(const int sock, const char *query)
{
    char *temp, buf[100], *ret = NULL;
    FILE *fi;

    temp = malloc(strlen(query) + 1 + 2 + 1);
    *temp = '=';
    strcpy(temp + 1, query);
    strcat(temp, "\r\n");

    fi = fdopen(sock, "r");
    if (write(sock, temp, strlen(temp)) < 0)
	err_sys("write");
    while (fgets(buf, 100, fi)) {
	/* If there are multiple matches only the server of the first record
	   is queried */
	if (strncmp(buf, "   Whois Server:", 16) == 0 && !ret) {
	    char *p, *q;

	    for (p = buf; *p != ':'; p++);	/* skip until colon */
	    for (p++; *p == ' '; p++);		/* skip colon and spaces */
	    ret = malloc(strlen(p) + 1);
	    for (q = ret; *p != '\n' && *p != '\r'; *q++ = *p++); /*copy data*/
	    *q = '\0';
	}
	fputs(buf, stdout);
    }
    if (ferror(fi))
	err_sys("fgets");

    free(temp);
    return ret;
}

int openconn(const char *server, const char *port)
{
    int s, fd;
#ifdef HAVE_GETADDRINFO
    struct addrinfo hints, *res, *ressave;
#else
    struct hostent *hostinfo;
    struct servent *servinfo;
    struct sockaddr_in saddr;
#endif

#ifdef HAVE_GETADDRINFO
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((s = getaddrinfo(server, port ? port : "whois", &hints, &res)) != 0)
	err_quit("getaddrinfo: %s", gai_strerror(s));

    for (ressave = res; res; res = res->ai_next) {
	if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol))<0)
	    continue;		/* ignore */
	if (connect(fd, res->ai_addr, res->ai_addrlen) == 0)
	    break;		/* success */
	close(fd);
    }

    if (!res)
	err_sys("connect");
    freeaddrinfo(ressave);
#else
    if ((hostinfo = gethostbyname(server)) == NULL)
	err_quit(_("Host %s not found."), server);
    if ((fd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP)) < 0)
	err_sys("socket");
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
    saddr.sin_family = AF_INET;
    if (!port) {
	saddr.sin_port = htons(43);
    } else if ((saddr.sin_port = htons(atoi(port))) == 0) {
	if ((servinfo = getservbyname(port, "tcp")) == NULL)
	    err_quit(_("%s/tcp: unknown service"), port);
	saddr.sin_port = servinfo->s_port;
    }
    if (connect(fd, &saddr, sizeof(saddr)) < 0)
	err_sys("connect");
#endif
    return (fd);
}

void closeconn(const int fd)
{
    close(fd);
}

void sighandler(int signum)
{
    closeconn(sockfd);
    err_quit(_("Interrupted by signal %d..."), signum);
}

/* check if dom ends with tld */
int domcmp(const char *dom, const char *tld)
{
    const char *p, *q;

    for (p = dom; *p != '\0'; p++); p--;	/* move to the last char */
    for (q = tld; *q != '\0'; q++); q--;
    while (p >= dom && q >= tld && tolower(*p) == *q) {	/* compare backwards */
	if (q == tld)				/* start of the second word? */
	    return 1;
	p--; q--;
    }
    return 0;
}

/* check if dom ends with an element of tldlist[] */
int domfind(const char *dom, const char *tldlist[])
{
    int i;

    for (i = 0; tldlist[i]; i++)
	if (domcmp(dom, tldlist[i]))
	    return 1;
    return 0;
}

unsigned long myinet_aton(const char *s)
{
    int a, b, c, d;

    if (!s)
	return 0;
    if (sscanf(s, "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
	return 0;
    return (a << 24) + (b << 16) + (c << 8) + d;
}

void usage(void)
{
    fprintf(stderr, _(
"Usage: whois [OPTION]... OBJECT...\n\n"
"-a                     search all databases\n"
"-F                     fast raw output (implies -r)\n"
"-g SOURCE:FIRST-LAST   find updates from SOURCE from serial FIRST to LAST\n"
"-h HOST                connect to server HOST\n"
"-H                     hide legal disclaimers\n"
"-i ATTR[,ATTR]...      do an inverse lookup for specified ATTRibutes\n"
"-L                     find all Less specific matches\n"
"-M                     find all More specific matches\n"
"-m                     find first level more specific matches\n"
"-r                     turn off recursive lookups\n"
"-p PORT                connect to PORT\n"
"-R                     force to show local copy of the domain object even\n"
"                       if it contains referral\n"
"-S                     tell server to leave out syntactic sugar\n"
"-s SOURCE[,SOURCE]...  search the database from SOURCE\n"
"-T TYPE[,TYPE]...      only look for objects of TYPE\n"
"-t TYPE                requests template for object of TYPE ('all' for a list)\n"
"-v TYPE                requests verbose template for object of TYPE\n"
"-V                     explain what is being done\n\n"
"Version %s. Please report bugs to %s.\n"
	), VERSION, "<md+whois@linux.it>");
    exit(1);
}


/* Error routines */
void err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, ": %s\n", strerror(errno));
    va_end(ap);
    exit(2);
}

void err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    fputs("\n", stderr);
    va_end(ap);
    exit(2);
}

