/*
Copyright (c) RIPE NCC

All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the author not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/* $Id: whois3.c,v 1.11 2003/06/19 14:47:15 can Exp $ */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <netdb.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#include <unistd.h>
#include <stdarg.h>

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif				/*  */

#ifdef HAVE_STRTOL
#include <errno.h>
#include <limits.h>
#endif				/*  */

#include <assert.h>

/* default name to query - can be set at runtime via the "-h" option */
#define DEFAULT_WHOIS_HOST "whois.ripe.net"

/* default port - only used if there is no entry for "whois" in the
   /etc/services file and no "-p" option is specified */
#define DEFAULT_WHOIS_PORT "43"

/* maximum length of the string representing an IP address */
#define MAX_IP_ADDRLEN sizeof("0000:0000:0000:0000:0000:0000:255.255.255.255")
#define MAX_PORTLEN    sizeof("65535")
typedef struct address_t {
    char *address;
    char *port;
} address;

#if !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE___SS_FAMILY_IN_SS)
# define ss_family __ss_family
#endif /* !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE_SA_FAMILY_IN_SS) */

/* program name */
static char *program_name = NULL;

/* 
 * FUNCTIONS 
 */
const char *get_program_name()
{
    return program_name;
}


/* exit the program with an error message */
void fatal(const char *template, ...)
{
    va_list ap;
    fprintf(stderr, "%s: ", get_program_name());
    va_start(ap, template);
    vfprintf(stderr, template, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    exit(EXIT_FAILURE);
}

/* this function opens a socket, connects the socket to the address specified 
 * by "remote" and returns the file descriptor of the socket. */
static int tcp_connect(sa_family_t family, address * remote)
{
    int err, fd;
    socklen_t destlen;
    struct addrinfo hints, *res = NULL, *ptr;
    struct sockaddr_storage dest;

    /* make sure that the preconditions on the addresses and on the flags
     * are respected */
    assert(remote != NULL);
    assert(remote->address != NULL && strlen(remote->address) > 0);
    assert(remote->port != NULL && strlen(remote->port) > 0);

    /* setup hints structure to be passed to getaddrinfo */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = family;
    hints.ai_socktype = SOCK_STREAM;

    /* get the IP address of the remote end of the connection */
    err = getaddrinfo(remote->address, remote->port, &hints, &res);
    if (err != 0)
	fatal("getaddrinfo error: %s", gai_strerror(err));

    /* check the results of getaddrinfo */
    assert(res != NULL);
    assert(res->ai_addrlen <= sizeof(dest));

    /* if the connect to the first address returned by getaddrinfo fails,
     * then we keep trying with the other addresses */
    for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {

	/* get the first sockaddr structure returned by getaddrinfo */
	memcpy(&dest, ptr->ai_addr, ptr->ai_addrlen);
	destlen = ptr->ai_addrlen;

	/* create the socket */
	fd = socket(dest.ss_family, SOCK_STREAM, 0);
	if (fd < 0)
	    fatal("cannot create the socket: %s", strerror(errno));

	/* perform the connection */
	err = connect(fd, (struct sockaddr *) &dest, destlen);
	if (err == 0)
	    break;
    }

    /* cleanup to avoid memory leaks */
    freeaddrinfo(res);

    /* if we have reached the end of the loop without having err == 0 then
     * we have failed to establish the connection */
    if (err < 0)
	fatal("cannot establish connection: %s", strerror(errno));
    return fd;
}


/*
  whois_query

  Writes the query string in "query" to the "out" descriptor, and reads
  the result in the "in" descriptor.  The "out" buffer may have either no
  buffering or line buffering, but must NOT have full buffering.

  The routine then outputs each line the server returns, until the server
  ends the connection.  If the "check_for_blank" variable is set to
  non-zero, then the routine will also return when two consecutive blank
  lines appear in the server response.

  If an error occurs sending or reading the query, -1 is returned.
 */
int whois_query(FILE * in, FILE * out, char *query, int check_for_blank)
{
    char buf[1024];
    int last_line_blank;
    char *p, *query_copy;

    /* manipulate a copy of the query */
    query_copy = (char *) malloc(strlen(query) + 4);
    strcpy(query_copy, query);

    /* remove any newline or carriage return */
    p = strchr(query_copy, '\r');
    if (p != NULL) {
	*p = '\0';
    }
    p = strchr(query_copy, '\n');
    if (p != NULL) {
	*p = '\0';
    }

    /* add CR+LF */
    strcat(query_copy, "\r\n");

    /* send query */
    if (fputs(query_copy, out) == EOF) {
	return (-1);
    }

    /* wait for reply to finish, printing until then */
    last_line_blank = 0;
    for (;;) {

	/* read next line */
	if (fgets(buf, sizeof(buf), in) == NULL) {
	    return (-1);
	}

	/* output the line */
	fputs(buf, stdout);

	/* if entire line fit in buffer */
	if (strchr(buf, '\n')) {

	    /* if line is empty */
	    if (!strcmp(buf, "\n")) {

		/* if the last line was also blank, we're done */
		if (check_for_blank && last_line_blank) {
		    return 1;
		}
		last_line_blank = 1;
	    }

	    /* non-empty line */
	    else {
		last_line_blank = 0;
	    }
	}

	/* otherwise read until end of line */
	else {

	    do {
		if (fgets(buf, sizeof(buf), in) == NULL) {
		    return 0;
		}
		fputs(buf, stdout);
	    } while (!strchr(buf, '\n'));
	    last_line_blank = 0;
	}
    }
}


/* usage_error - output proper syntax and exit */
void usage_error(const char *exename)
{
    fprintf(stderr,
	    "%s: [-h host | --host=host] [-p port | --port=port] -k | query\n",
	    exename);
    exit(1);
}

/* main - program entry point */
int main(int argc, char *argv[])
{

    /* name of executable */
    char *exename;

    /* variables used to parse arguments */
    int p;

    /* arguments to forward to whois server */
    char **whois_argv;
    int whois_argc;

    /* server name and port to query */
    char *host;
    char *port;
    char src_addr[MAX_IP_ADDRLEN + 1];
    char src_port[MAX_PORTLEN + 1];
    sa_family_t family;
    address remote;

    /* persistent mode flag */
    int persistent_mode;
    char linebuf[BUFSIZ];

    /* connection information */
    int whois_fd;
    FILE *whois_in, *whois_out;
    char whois_in_linebuf[BUFSIZ];

    /* query string */
    char *query;
    int query_len;

    /* the all-seeing i */
    int i;

    /* */
    /* parse command line */
    /* */

    /* get the name of this executable */
    if (argc > 0) {
	program_name = exename = "whois3";
    } else {
	program_name = exename = argv[0];
    }

    /* set defaults for parameters */
    host = NULL;
    port = NULL;
    persistent_mode = 0;
    family = AF_UNSPEC;

    /* allocate enough space for our argument list */
    whois_argv = (char **) malloc(sizeof(char *) * (argc + 1));
    if (whois_argv == NULL) {
	fprintf(stderr, "%s: out of memory\n", exename);
	exit(1);
    }
    whois_argc = 0;

    /* parse command-line arguments */
    p = 1;
    while (p < argc) {

	/* check for short host name */
	if (!strncmp(argv[p], "-h", 2)) {

	    /* only specify host once */
	    if (host != NULL) {
		usage_error(exename);
	    }

	    /* see if the host was specified after the 'h' */
	    host = argv[p] + 2;

	    /* if not, then it must be the next argument */
	    if (*host == '\0') {
		p++;
		if (p >= argc) {
		    usage_error(exename);
		}
		host = argv[p];
	    }
	    p++;
	}

	/* check for long host name */
	else if (!strncmp(argv[p], "--host=", 7)) {

	    /* only specify host once */
	    if (host != NULL) {
		usage_error(exename);
	    }

	    /* grab host name */
	    host = argv[p] + 7;
	    if (*host == '\0') {
		usage_error(exename);
	    }
	    p++;
	}

	/* check for short port name */
	else if (!strncmp(argv[p], "-p", 2)) {

	    /* only specify port once */
	    if (port != NULL) {
		usage_error(exename);
	    }

	    /* see if the port was specified after the 'p' */
	    port = argv[p] + 2;
	    if (*port == '\0') {
		p++;
		if (p >= argc) {
		    usage_error(exename);
		}
		port = argv[p];
	    }
	    p++;
	}

	/* check for long port name */
	else if (!strncmp(argv[p], "--port=", 7)) {

	    /* only specify port once */
	    if (port != NULL) {
		usage_error(exename);
	    }
	    port = argv[p] + 7;
	    p++;
	}

	/* check for stand-alone persistent flag */
	else if (!strcmp(argv[p], "-k")) {

	    /* note we explicitly allow multiple -k options, as this doesn't
	       add any ambiguity, even if it is pointless */
	    persistent_mode = 1;
	    p++;
	}

	/* other flags or arguments */
	else {

	    /* check to see if -k was used - this will cause an error below,
	       as you can only use -k by itself */
	    if ((argv[p][0] == '-') && strchr(argv[p], 'k')) {
		persistent_mode = 1;
	    }

	    /* add our argument in any case */
	    whois_argv[whois_argc++] = argv[p];
	    p++;
	}
    }

    /* don't allow any arguments with a persistent mode */
    if (persistent_mode) {
	if (whois_argc > 0) {
	    fprintf(stderr, "%s: do not specify arguments with -k\n",
		    exename);
	    exit(1);
	}
	/* set to line buffering if we are in persistent mode,
	 * to allow programs to pipe the result without block buffering */
	setvbuf(stdout, linebuf, _IOLBF, BUFSIZ);
    }


    /* require options otherwise */
    else {
	if (whois_argc <= 0) {
	    usage_error(exename);
	}
    }

    /* */
    /* arguments look good - connect to server */
    /* */

    /* set port address if not specified */
    if (port == NULL) {
	remote.port = DEFAULT_WHOIS_PORT;
    } else {
	strncpy(src_port, port, sizeof(src_port) - 1);
	src_port[sizeof(src_port) - 1] = '\0';
	remote.port = src_port;
    }

    /* set host address if not specified */
    if (host == NULL) {
	remote.address = DEFAULT_WHOIS_HOST;
    } else {
	strncpy(src_addr, host, sizeof(src_addr) - 1);
	src_addr[sizeof(src_addr) - 1] = '\0';
	remote.address = src_addr;
    }

    /* create a socket, exit if no connection possible */
    whois_fd = tcp_connect(family, &remote);

    /* bind FILE structures to our file descriptor for easy handling */
    whois_in = fdopen(whois_fd, "r");
    if (whois_in == NULL) {
	fprintf(stderr, "%s: error %d creating input stream; %s\n",
		exename, errno, strerror(errno));
    }
    setvbuf(whois_in, whois_in_linebuf, _IOLBF, sizeof(whois_in_linebuf));
    whois_out = fdopen(whois_fd, "w");
    if (whois_out == NULL) {
	fprintf(stderr, "%s: error %d creating input stream; %s\n",
		exename, errno, strerror(errno));
    }
    setbuf(whois_out, NULL);

    /* */
    /* Query away */
    /* */

    /* if we had flags, we're running in "interactive" mode */
    if (whois_argc > 0) {

	/* combine our arguments into a single string */
	query_len = 0;
	for (i = 0; i < whois_argc; i++) {
	    query_len += (1 + strlen(whois_argv[i]));
	}
	query = (char *) malloc(query_len + 1);
	if (query == NULL) {
	    fprintf(stderr, "%s: out of memory\n", exename);
	    exit(1);
	}
	strcpy(query, whois_argv[0]);
	for (i = 1; i < whois_argc; i++) {
	    strcat(query, " ");
	    strcat(query, whois_argv[i]);
	}

	/* now send our query to the server */
	whois_query(whois_in, whois_out, query, 0);
    }

    /* otherwise we're in "batch" mode - read each query a line at a time */
    else {

	/* make a buffer to read into */
	query_len = 8192;
	query = (char *) malloc(query_len);
	if (query == NULL) {
	    fprintf(stderr, "%s: out of memory\n", exename);
	    exit(1);
	}

	/* enter persistent mode */
	if (whois_query(whois_in, whois_out, "-k", 1) == -1) {
	    fprintf(stderr, "%s: unable to send query\n", exename);
	    exit(1);
	}

	/* loop and query */
	while (fgets(query, query_len, stdin) != NULL) {
	    if (strchr(query, '\n') == NULL) {
		fprintf(stderr, "%s: query line too long\n", exename);
		exit(1);
	    }
	    if (whois_query(whois_in, whois_out, query, 1) == -1) {
		fprintf(stderr, "%s: unable to send query\n", exename);
		exit(1);
	    }
	}

	/* exit persistent mode */
	fputs("-k\n", whois_out);
    }

    /* everything exited fine */
    return 0;
}
