/*
Copyright (c) 2001                      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.3 2001/09/17 14:29:30 shane 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>



/*
 * CONSTANTS 
 */

/* default name to query - can be set at runtime via the "-h" option */
#define NICHOST "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



/* 
 * FUNCTIONS 
 */


/* 
  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 either
  two consecutive blank lines appear in the server response, or the
  server ends the connection.
 */

int 
whois_query(FILE *in, FILE *out, char *query)
{
    char buf[1024];
    int last_line_blank;
    char *p;

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

    /* send query */
    fputs(query, out);
    fputs("\r\n", out);

    /* wait for reply to finish, printing until then */
    last_line_blank = 0;
    for (;;) {
        /* read next line */
        if (fgets(buf, sizeof(buf), in) == NULL) {
            return 0;
        }

        /* 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 (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;
    char *nptr;
    char *endptr;

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

    /* server name and port to query */
    char *host;
    long port;

    /* persistent mode flag */
    int persistent_mode;

    /* connection information */
    struct servent *serv;
    struct hostent *h;
    struct sockaddr_in host_addr;
    int whois_fd;
    FILE *whois_in, *whois_out;

    /* 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) {
        exename = "whois3";
    } else {
        exename = argv[0];
    }

    /* set defaults for parameters */
    host = NULL;
    port = -1;
    persistent_mode = 0;

    /* 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 != -1) {
                usage_error(exename);
            }

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

            /* see if this is a valid port */
            port = strtol(nptr, &endptr, 0);
            if ((port < 0) || (port > 65535) || (*endptr != '\0')) {
                serv = getservbyname(nptr, "tcp");
                if (serv == NULL) {
                    fprintf(stderr, 
                            "%s: port must be a number between 0 and 65535, "
                            "or the name of a service\n",
                            exename);
                    exit(1);
                }
                port = ntohs(serv->s_port);
            }

            p++;
        }

        /* check for long port name */
        else if (!strncmp(argv[p], "--port=", 7)) {
            /* only specify port once */
            if (port != -1) {
                usage_error(exename);
            }

            nptr = argv[p] + 7;

            /* see if this is a valid port */
            port = strtol(nptr, &endptr, 0);
            if ((port < 0) || (port > 65535) || (*endptr != '\0')) {
                serv = getservbyname(nptr, "tcp");
                if (serv == NULL) {
                    fprintf(stderr, 
                            "%s: port must be a number between 0 and 65535\n",
                            exename);
                    exit(1);
                }
                port = ntohs(serv->s_port);
            }

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

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

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

    /* blank out our address structure for broken (i.e. BSD) Unix variants */
    memset(&host_addr, 0, sizeof(struct sockaddr_in));

    /* get port address if not specified */
    if (port == -1) {
        serv = getservbyname("whois", "tcp");
        if (serv == NULL) {
            host_addr.sin_port = htons(DEFAULT_WHOIS_PORT);
        } else {
            host_addr.sin_port = serv->s_port;
        }
    } else {
        host_addr.sin_port = htons(port);
    }

    /* get server address (checking if it is an IP number first) */
    if (host == NULL) {
        host = NICHOST;
    }
    host_addr.sin_addr.s_addr = inet_addr(host);
    if (host_addr.sin_addr.s_addr == -1) {
        h = gethostbyname(host);
        if (h == NULL) {
            fprintf(stderr, 
                    "%s: error %d getting server address\n",
                    exename,
                    h_errno);
            exit(1);
        }
        memcpy(&host_addr.sin_addr, h->h_addr, sizeof(host_addr.sin_addr));
    }

    /* fill in the rest of our socket structure */
    host_addr.sin_family = AF_INET;

    /* create a socket */
    whois_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (whois_fd == -1) {
        fprintf(stderr, 
                "%s: error %d creating a socket; %s\n",
                exename,
                errno,
                strerror(errno));
        exit(1);
    }

    /* connect to the server */
    if (connect(whois_fd, 
                (struct sockaddr *)&host_addr, 
                sizeof(struct sockaddr_in)) != 0) 
    {
        fprintf(stderr, 
                "%s: error %d connecting to server; %s\n",
                exename,
                errno,
                strerror(errno));
        exit(1);
    }

    /* 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));
    }
    setbuf(whois_in, NULL);
    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);
    }

    /* 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 */
        fputs("-k\n", whois_out);

        /* 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);
            }
            whois_query(whois_in, whois_out, query);
        }

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

    /* everything exited fine */
    return 0;
}


