/*  
*  Handle communication with whois servers. 
*
*  This file is part of the Prefix WhoIs Project.
*  See http://pwhois.org
*
*  The full text of our legal notices is contained in the file called
*  COPYING, included with this Distribution.
* 
*  This software includes:
*       - simplified access to regular expressions
*       - tokenizer
*       - lookup functions for AS, NETNAME, ORGNAME 
*              - works with the following sources:
*         ARIN, RIPE, APNIC, RADB, CYMRU, PWHOIS, RISWHOIS
*              - will do recursive lookups
*       - convenient framework for further whois digging
*
*   To compile the standalone client:
*       cc -o whob whois.c -DSTANDALONE
*
*   
*   Portions (c) Victor Oppleman (lft@oppleman.com)
*   Portions (c) 2011 Markus Gothe <nietzsche@lysator.liu.se>
*   Portions (c) 2002 Ugen Antsilevitch (ugen@xonix.com)
*
*/

#include "lft_lib.h"

#define PORT_WHOIS  43

#if defined(WIN32) || defined(_WIN32)
#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)
#define snprintf    _snprintf
#endif

#include "whois.h"

#ifndef WIN32
#include <signal.h>
#include <getopt.h>
#include <strings.h>
#include <termios.h>
#include <poll.h>
#endif

#if defined(BSD_IP_STACK)
#define pcap_snprintf    snprintf
#endif

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

/* OPTIONS and variable initialization */

static char pwhois_server[] = "whois.pwhois.org";
static char myaddress_server[] = "myaddress.today";
static char radb_server[] = "whois.ra.net";
static char cymru_server[] = "whois.cymru.com";
static char arin_server[] = "whois.arin.net";
static char apnic_server[] = "whois.apnic.net";
static char ripe_server[] = "whois.ripe.net";
static char ripe_ris_server[] = "riswhois.ripe.net";

#ifdef STANDALONE
const char *version = "4.0";                   /* set version string for library and client */
static const char *version_date = "(09/2026)";  /* date of this version */
const char *appname = "WhoB";                   /* set application name */
static int go_interactive = 0;                  /* We'll wait on STDIN unless args suggest otherwise */
static int use_cymru = 0;                       /* Don't use Cymru by default */
static int use_riswhois = 0;                    /* Don't use RIPE NCC RIS by default */
static int display_orgname = 1;                 /* Display orgname by default */
static int display_aspath = 0;                  /* Don't display AS-PATH by default */
static int display_netname = 0;                 /* Don't display netname by default */
static int display_radb_as = 0;                 /* Don't display RADB Origin-AS by default */
static int show_routes_byasn = 0;               /* Don't show all advertisements by default */
static int show_routes_bytransitasn = 0;        /* Don't show all routes that transit ASN by default */
static int show_networks_byasn = 0;             /* Don't show all networks by default */
static int show_contacts_byasn = 0;             /* Don't show all contact info by default */
static int show_routes_byprefix = 0;            /* Don't show all routes by prefix by default */
static int show_server_status = 0;              /* Don't show pwhois server status by default */
static int show_cache_date = 0;                 /* Don't show pwhois cache date by default */
static int read_from_file = 0;                  /* Don't read input from file by default */
static int riswhoisfromfile = 0;                /* Don't use riswhois by default for readfromfile */
static int cymrufromfile = 0;                   /* Don't use Cymru by default for readfromfile */
static int use_gigo = 1;                        /* Use GIGO feature by default */
static int use_stdin = 0;                       /* Don't use STDIN for bulk file input by default */
static const unsigned int max_hostname_input = 200;      /* Maximum length of input from user */
static const int max_lines = 2500;              /* Maximum lines to read from bulk file per query */
static const int line_size = 256;               /* Maximum line length */

/* --- pWhoIs output colorization (STANDALONE / whob only) ------------------
 * Colour is applied to stdout only, and only when stdout is an interactive
 * terminal (never a file or pipe).  --no-color forces it off, --color forces
 * it on.  The palette is "Beacon+": the classic ANSI Beacon scheme with the
 * field-label colour from Cartographer and the geo colour from Teal Ledger.
 * A dark and a light variant are chosen automatically from the terminal's
 * background (OSC 11 query, then COLORFGBG, then a dark default). */
static int w_color_mode = 0;   /* 0 = auto, 1 = --color (force on), 2 = --no-color */
static int w_use_color  = 0;   /* resolved once at startup by w_detect_color()   */
static int w_truecolor  = 0;   /* 1 = emit 24-bit SGR, 0 = 256-colour fallback   */
static int w_bg_light   = 0;   /* 0 = dark background variant, 1 = light          */
static char w_highlight_asn[24] = "";  /* queried ASN to bold within AS-Path (-a/-A) */

/* field groups, used both to pick a colour and to index the palette */
enum { WF_LABEL = 0, WF_ASN, WF_PREFIX, WF_ORG, WF_GEO, WF_DATE, WF_NGROUPS,
       WF_NONE = -1 };

/* one palette entry: exact RGB (truecolor) plus nearest xterm-256 index */
typedef struct { unsigned char r, g, b; unsigned char x256; } wc_color;

/* Cartographer palette.  [0] = dark-background variant, [1] = light. */
static const wc_color w_palette[2][WF_NGROUPS] = {
    /* DARK */ {
        { 0x7a, 0xa2, 0xc8, 110 },  /* label  — steel blue    */
        { 0xe0, 0xa4, 0x58, 179 },  /* asn    — amber         */
        { 0x6c, 0xc4, 0xa1,  79 },  /* prefix — emerald       */
        { 0xe8, 0xe6, 0xdf, 254 },  /* org    — warm off-white*/
        { 0xe0, 0x86, 0x5a, 173 },  /* geo    — terracotta    */
        { 0x7c, 0x84, 0x71, 101 },  /* date   — slate         */
    },
    /* LIGHT */ {
        { 0x35, 0x68, 0x8f,  24 },  /* label  — steel blue    */
        { 0xa8, 0x66, 0x12, 130 },  /* asn    — dark amber    */
        { 0x1f, 0x7a, 0x5a,  29 },  /* prefix — deep emerald  */
        { 0x17, 0x17, 0x16, 233 },  /* org    — near black    */
        { 0xbf, 0x4a, 0x2a, 166 },  /* geo    — burnt sienna  */
        { 0x8a, 0x8a, 0x80, 245 },  /* date   — warm grey     */
    },
};

/* Table-only date shades so Create / Modify / Originated columns read apart.
 * [bg][0=Create,1=Modify,2=Originated].  Modify mirrors the base date colour. */
static const wc_color w_date_shade[2][3] = {
    /* DARK  */ { { 0x6c, 0x8a, 0x86,  66 },   /* Create     — teal-slate  */
                  { 0x7c, 0x84, 0x71, 101 },   /* Modify     — slate       */
                  { 0xa0, 0x8a, 0x5f, 137 } }, /* Originated — tan-slate   */
    /* LIGHT */ { { 0x3f, 0x74, 0x6e,  23 },   /* Create     — teal        */
                  { 0x6a, 0x6a, 0x60, 241 },   /* Modify     — grey        */
                  { 0x8a, 0x6a, 0x3a,  94 } }, /* Originated — tan         */
};

#define WC_RESET "\033[0m"
#define WC_BOLD  "\033[1m"
#define WC_DIM   "\033[2m"
#define OPT_W_COLOR    256   /* --color    (long option, force colour on)  */
#define OPT_W_NO_COLOR 257   /* --no-color (long option, force colour off) */
#define OPT_W_ANNOTATE 258   /* --annotate[=FILE]: enrich IPs in text via pWhoIs */
#define OPT_W_FIELDS   259   /* -F/--fields LIST: which pWhoIs fields the annotation shows */

static void w_print_pwhois_colorized(const char *reply);   /* defined below */
static void w_print_status_colorized(const char *reply);   /* defined below */

static char hostname[256];
#endif

/* END of OPTIONS and variable initialization */
#if defined(WIN32) || defined(_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

#if defined(WIN32) || defined(_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

typedef struct token_s {
    char    *ptr;
} token_t;

static token_t *
tokens(char *buf, const 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;

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

static 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 = (unsigned int)strtoul(mask, (char **)NULL, 10);
    if (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;
}

static int rm_spaces(char* str)
{
    /* Remove spaces (isspace()) from anywhere within a string
    ONLY operates on a null-terminated (\0) string!  */
    
    int j = -1;
    unsigned int i;     
    
    if (!str) return 0;  
    
    for (i=0; i<=strlen(str); i++)
        if (!(isspace((unsigned char)*(str+i))))
            *(str+(++j)) = *(str+i);
        else
            continue;
    
    return 1;     
}

static 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((unsigned char)*prefix) != tolower((unsigned char)*target))
            return NULL;
        prefix++;
        target++;
    }
    while (isspace((unsigned char)*target))
        target++;
    /* strip out the leading AS from the number */
    if (strncmp(target,"AS",2) == 0)
        target += 2;
    return strdup(target);
}

static 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((unsigned char)*target))
        target++;
    pos = target;
    while (*pos && !isspace((unsigned char)*pos))
        pos++;
    
    beforedash = strdup(target);
    beforedash[pos-target] = 0;
    
    dash = strchr(target, '-');
    if (!dash)
        return NULL;
    dash++;
    while (isspace((unsigned char)*dash))
        dash++;
    
    return w_blk2range(beforedash, dash);
}

static 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((unsigned char)*target))
        target++;
    pos = target;
    while (*pos && !isspace((unsigned char)*pos) && *pos != '/')
        pos++;
    beforeslash = strdup(target);
    beforeslash[pos - target] = 0;
    
    slash = strchr(target, '/');
    if (!slash) return NULL;
    
    slash++;
    
    return w_mask2range(beforeslash, slash);
}

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

static 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((unsigned char)*target)) target++;
	if (*target) return strdup(target);
	else return NULL;
}

static int stricontains(const char *haystack, const char *needle) 
{
	/* Search for a substring without a string library */
	int i, j, match;
	i = 0, j = 0;
	
	while (haystack[i] != '\0') {
	
	  while (tolower((unsigned char)haystack[i]) != tolower((unsigned char)needle[0]) && haystack[i] != '\0')
		 i++;
	
	  if (haystack[i] == '\0')
		 return (-1);
	
	  match = i;
	
	  while (tolower((unsigned char)haystack[i]) == tolower((unsigned char)needle[j]) && haystack[i] != '\0' && needle[j] != '\0') {
		 i++;
		 j++;
	  }
	
	  if (needle[j] == '\0')
		 return (match);
	  if (haystack[i] == '\0')
		 return (-1);
	
	  i = match + 1;
	  j = 0;
	}
	return 0;
}

#ifdef HAVE_CARES
#ifdef HAVE_ARES_PROCESS_FDS
/* fd-state tracking for w_prefetch()'s private ares_channel (event-model API) */
#define W_ARES_MAX_FDS 4
struct w_ares_state {
    struct {
        ares_socket_t fd;
        unsigned int  events; /* mask of ares_fd_eventflag_t */
    } fd_table[W_ARES_MAX_FDS];
    int nfds;
};

static void
w_ares_sock_state_cb(void *data, ares_socket_t fd, int readable, int writable)
{
    struct w_ares_state *st = (struct w_ares_state *)data;
    int i;
    for (i = 0; i < st->nfds; i++) {
        if (st->fd_table[i].fd == fd) {
            if (!readable && !writable) {
                st->fd_table[i] = st->fd_table[--st->nfds];
            } else {
                st->fd_table[i].events =
                    (readable ? (unsigned)ARES_FD_EVENT_READ  : 0u) |
                    (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u);
            }
            return;
        }
    }
    if (!readable && !writable) return;
    if (st->nfds >= W_ARES_MAX_FDS) return;
    st->fd_table[st->nfds].fd     = fd;
    st->fd_table[st->nfds].events =
        (readable ? (unsigned)ARES_FD_EVENT_READ  : 0u) |
        (writable ? (unsigned)ARES_FD_EVENT_WRITE : 0u);
    st->nfds++;
}
#endif /* HAVE_ARES_PROCESS_FDS */

/* Callback for w_prefetch() forward (A-record) lookups. */
static void
w_ares_callback(void *arg, int status, int timeouts, struct ares_addrinfo *ai)
{
    struct w_dns_entry *entry = (struct w_dns_entry *)arg;
    (void)timeouts;
    if (status == ARES_SUCCESS && ai && ai->nodes) {
        struct sockaddr_in *sin = (struct sockaddr_in *)ai->nodes->ai_addr;
        memcpy(&entry->addr, &sin->sin_addr, sizeof(struct in_addr));
    }
    if (ai) ares_freeaddrinfo(ai);
    entry->resolved = 1; /* mark done whether succeeded or failed */
}

/* Resolve a single whois server hostname on first use.
 * Finds the matching cache slot, fires one c-ares A-record lookup via
 * ares_getaddrinfo(), and drains via ares_process_fds() until resolved or
 * the 1-second deadline expires.  No-op if already resolved or the server
 * is not in the known-server list. */
static void
w_prefetch(whois_session_params *wsess, const char *serv)
{
    int i;
    struct w_dns_entry *entry = NULL;
    ares_channel wchan;
    struct ares_options opts;
#ifdef HAVE_ARES_PROCESS_FDS
    struct w_ares_state st;
#endif
    struct ares_addrinfo_hints hints;

    if (!wsess || !serv) return;

    /* find the cache slot for this server */
    for (i = 0; i < wsess->w_dns_cache_count; i++) {
        if (wsess->w_dns_cache[i].host &&
            strcmp(wsess->w_dns_cache[i].host, serv) == 0) {
            entry = &wsess->w_dns_cache[i];
            break;
        }
    }
    if (!entry) return;          /* not a known whois server; getaddrinfo handles it */
    if (entry->resolved) return; /* already resolved on a previous call */

    /* fire a single lookup and drain with a 1-second hard deadline */
    memset(&opts, 0, sizeof(opts));
    opts.timeout            = 500;
    opts.tries              = 2;
#ifdef HAVE_ARES_PROCESS_FDS
    memset(&st,   0, sizeof(st));
    opts.sock_state_cb      = w_ares_sock_state_cb;
    opts.sock_state_cb_data = &st;
    if (ares_init_options(&wchan, &opts,
                          ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES |
                          ARES_OPT_SOCK_STATE_CB) != ARES_SUCCESS)
        return;
#else
    if (ares_init_options(&wchan, &opts,
                          ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES) != ARES_SUCCESS)
        return;
#endif

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    ares_getaddrinfo(wchan, serv, NULL, &hints, w_ares_callback, entry);

    {
        struct timeval deadline, now;
        gettimeofday(&deadline, NULL);
        deadline.tv_sec += 1;
        for (;;) {
            struct timeval tv, remaining;
            int nfds = 0;
            fd_set rfd, wfd;
#ifdef HAVE_ARES_PROCESS_FDS
            ares_fd_events_t events[W_ARES_MAX_FDS];
            size_t nevents = 0;
            int j;
#endif

            if (entry->resolved) break;
            gettimeofday(&now, NULL);
            remaining.tv_sec  = deadline.tv_sec  - now.tv_sec;
            remaining.tv_usec = deadline.tv_usec - now.tv_usec;
            if (remaining.tv_usec < 0) { remaining.tv_sec--; remaining.tv_usec += 1000000; }
            if (remaining.tv_sec < 0) break;

            tv.tv_sec  = 0;
            tv.tv_usec = (remaining.tv_sec == 0 && remaining.tv_usec < 10000)
                          ? remaining.tv_usec : 10000;
            FD_ZERO(&rfd); FD_ZERO(&wfd);
#ifdef HAVE_ARES_PROCESS_FDS
            if (st.nfds == 0) {
                /* No fds yet — drive timeout processing only, then stop */
                ares_process_fds(wchan, NULL, 0, 0);
                break;
            }
            for (j = 0; j < st.nfds; j++) {
                if (st.fd_table[j].events & ARES_FD_EVENT_READ)
                    FD_SET(st.fd_table[j].fd, &rfd);
                if (st.fd_table[j].events & ARES_FD_EVENT_WRITE)
                    FD_SET(st.fd_table[j].fd, &wfd);
                if ((int)st.fd_table[j].fd + 1 > nfds)
                    nfds = (int)st.fd_table[j].fd + 1;
            }
            if (nfds > 0) select(nfds, &rfd, &wfd, NULL, &tv);
            for (j = 0; j < st.nfds; j++) {
                unsigned int ev = ARES_FD_EVENT_NONE;
                if (FD_ISSET(st.fd_table[j].fd, &rfd)) ev |= ARES_FD_EVENT_READ;
                if (FD_ISSET(st.fd_table[j].fd, &wfd)) ev |= ARES_FD_EVENT_WRITE;
                if (ev != ARES_FD_EVENT_NONE) {
                    events[nevents].fd     = st.fd_table[j].fd;
                    events[nevents].events = ev;
                    nevents++;
                }
            }
            ares_process_fds(wchan, events, nevents, 0);
#else       /* classic ares_fds()/ares_process() poll API (c-ares < 1.34) */
            /* Intentional for older c-ares; a newer shadowing header marks
             * these deprecated — expected in this branch, so silence it. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
            nfds = ares_fds(wchan, &rfd, &wfd);
            if (nfds == 0) { ares_process(wchan, &rfd, &wfd); break; }
            select(nfds, &rfd, &wfd, NULL, &tv);
            ares_process(wchan, &rfd, &wfd);
#pragma GCC diagnostic pop
#endif
        }
    }
    ares_destroy(wchan);
}
#endif /* HAVE_CARES */

/*---------------------------------------------------------------------------*/
/* Timed getaddrinfo() wrapper for whois server hostname resolution.
 * On Windows: spawns a thread and waits up to 1 s; kills if it doesn't finish.
 * On POSIX:   arms SIGALRM for 1 s, restores on return.
 * Keeps the call-sites in w_ask() clean and platform-independent.          */
#if defined(WIN32) || defined(_WIN32)
struct w_gai_args {
    const char     *serv;
    struct addrinfo hints;   /* copy — thread must not touch caller's stack */
    struct addrinfo **res;
    int              rc;
};
static DWORD WINAPI w_gai_thread(LPVOID arg)
{
    struct w_gai_args *a = (struct w_gai_args *)arg;
    a->rc = getaddrinfo(a->serv, NULL, &a->hints, a->res);
    return 0;
}
static int
w_getaddrinfo_timed(const char *serv, const struct addrinfo *hints,
                    struct addrinfo **res)
{
    struct w_gai_args a;
    HANDLE th;
    DWORD  tid;
    a.serv  = serv;
    a.hints = *hints;   /* copy the hints struct into our local */
    a.res   = res;
    a.rc    = EAI_AGAIN;
    th = CreateThread(NULL, 0, w_gai_thread, &a, 0, &tid);
    if (!th) return EAI_AGAIN;
    if (WaitForSingleObject(th, 1000) == WAIT_TIMEOUT)
        TerminateThread(th, 0);
    CloseHandle(th);
    return a.rc;
}
#else /* POSIX */
static volatile sig_atomic_t w_dns_timed_out;
static void w_sigalrm(int sig) { (void)sig; w_dns_timed_out = 1; }
static int
w_getaddrinfo_timed(const char *serv, const struct addrinfo *hints,
                    struct addrinfo **res)
{
    int rc;
    w_dns_timed_out = 0;
    signal(SIGALRM, w_sigalrm);
    alarm(1);
    rc = getaddrinfo(serv, NULL, hints, res);
    alarm(0);
    signal(SIGALRM, SIG_DFL);
    if (w_dns_timed_out && rc != 0)
        rc = EAI_AGAIN;
    return rc;
}
#endif /* WIN32 */
/*---------------------------------------------------------------------------*/

whois_session_params *w_init(void)
{
    /* int e; */
    whois_session_params *wsess = (whois_session_params *)malloc(sizeof(whois_session_params));
    wsess->w_noisy = 0;	/* Don't show debug msgs by default */
    memset(&(wsess->pw_serv), 0, sizeof(wsess->pw_serv));
    wsess->consolidated_asn[0] = wsess->consolidated_asp[0] =
    wsess->consolidated_route[0] = wsess->consolidated_orgname[0] =
    wsess->consolidated_netname[0] ='?';
    wsess->consolidated_asn[1] = wsess->consolidated_asp[1] =
    wsess->consolidated_route[1] = wsess->consolidated_orgname[1] =
    wsess->consolidated_netname[1] =0;
    memset(&wsess->tbuf, 0, sizeof(wsess->tbuf));
    wsess->logprintfCookie = 0;

#ifdef HAVE_CARES
    {
        /* Register all known whois server hostnames in the DNS cache so
         * w_prefetch() can find the right slot when w_ask() is first called
         * for each server.  No lookups are fired here; resolution happens
         * lazily on first use via w_prefetch(). */
        static const char *servers[] = {
            pwhois_server, myaddress_server, radb_server, cymru_server,
            arin_server, apnic_server, ripe_server, ripe_ris_server
        };
        int ns = (int)(sizeof(servers) / sizeof(servers[0]));
        int i;

        memset(wsess->w_dns_cache, 0, sizeof(wsess->w_dns_cache));
        wsess->w_dns_cache_count = 0;

        for (i = 0; i < ns && i < 8; i++) {
            wsess->w_dns_cache[i].host     = servers[i];
            wsess->w_dns_cache[i].resolved = 0;
            memset(&wsess->w_dns_cache[i].addr, 0, sizeof(wsess->w_dns_cache[i].addr));
        }
        wsess->w_dns_cache_count = (ns < 8) ? ns : 8;
    }
#endif /* HAVE_CARES */

    return wsess;
}

whois_session_params * w_reinit(whois_session_params * wsess)
{
    /* int e; */
    
    wsess->w_noisy = 0;                         /* Don't show debug msgs by default */
    memset(&(wsess->pw_serv), 0, sizeof(wsess->pw_serv));
    wsess->consolidated_asn[0] = wsess->consolidated_asp[0] =
    wsess->consolidated_route[0] = wsess->consolidated_orgname[0] =
    wsess->consolidated_netname[0] = '?';
    wsess->consolidated_asn[1] = wsess->consolidated_asp[1] =
    wsess->consolidated_route[1] = wsess->consolidated_orgname[1] =
    wsess->consolidated_netname[1] = 0;
    memset(&wsess->tbuf, 0, sizeof(wsess->tbuf));
    wsess->logprintfCookie = 0;
    return wsess;
}
__inline__ void w_close(whois_session_params * wsess)
{
    free(wsess);
}

static char *
w_ask(whois_session_params *wsess, const char *serv, const char *q, const char *port)
{
    int s;
    struct sockaddr_in sin4;
    struct addrinfo ai_hints, *ai_res;
    char *br;
    int q_s, br_s, cur, n, myport;
    char buf[128], *sendbuf;
#ifdef USE_WHOIS_TIMEOUT
#if defined(WIN32) || defined(_WIN32)
    int whreadtimeout = 20000;
#else
    struct timeval whreadtimeout;
    whreadtimeout.tv_sec = 20;
    whreadtimeout.tv_usec = 0;
#endif
#endif

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

    memset(&sin4, 0, sizeof(sin4));
    sin4.sin_family = AF_INET;

#ifdef HAVE_CARES
    {
        /* Resolve this server on first use (no-op if already cached). */
        w_prefetch(wsess, serv);
        /* Check DNS cache to avoid a blocking getaddrinfo() call;
         * falls through to getaddrinfo() for unknown/custom servers. */
        int _i, _found = 0;
        for (_i = 0; wsess && _i < wsess->w_dns_cache_count; _i++) {
            if (wsess->w_dns_cache[_i].host &&
                strcmp(wsess->w_dns_cache[_i].host, serv) == 0 &&
                wsess->w_dns_cache[_i].resolved &&
                wsess->w_dns_cache[_i].addr.s_addr != 0) {
                memcpy(&sin4.sin_addr, &wsess->w_dns_cache[_i].addr,
                       sizeof(sin4.sin_addr));
                _found = 1;
                break;
            }
        }
        if (!_found) {
            memset(&ai_hints, 0, sizeof(ai_hints));
            ai_hints.ai_family   = AF_INET;
            ai_hints.ai_socktype = SOCK_STREAM;
            if (w_getaddrinfo_timed(serv, &ai_hints, &ai_res) != 0)
                return NULL;
            memcpy(&sin4.sin_addr,
                   &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr,
                   sizeof(sin4.sin_addr));
            freeaddrinfo(ai_res);
        }
    }
#else
    memset(&ai_hints, 0, sizeof(ai_hints));
    ai_hints.ai_family   = AF_INET;
    ai_hints.ai_socktype = SOCK_STREAM;
    if (w_getaddrinfo_timed(serv, &ai_hints, &ai_res) != 0)
        return NULL;
    memcpy(&sin4.sin_addr,
           &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr,
           sizeof(sin4.sin_addr));
    freeaddrinfo(ai_res);
#endif /* HAVE_CARES */

    if (port) {
        if (!(myport = (int)strtol(port, (char **)NULL, 10)))
            return NULL;
        sin4.sin_port = htons(myport);
    } else {
        sin4.sin_port = htons(PORT_WHOIS);
    }
    
    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        return NULL;

#ifdef USE_WHOIS_TIMEOUT
	setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &whreadtimeout, sizeof(whreadtimeout));
#endif

    if (connect(s, (const struct sockaddr *)(const void *)&sin4, sizeof (sin4)) < 0) 
        return NULL;
    
    br_s = 512;
    if (!(br = (char *)malloc(br_s)))
        return NULL;
    
    	q_s = strlen(q);
	sendbuf = (char *)malloc(q_s+2);
	if(q[q_s-1]=='\r' || q[q_s-1]=='\n')
	{
		strncpy(sendbuf, q, q_s+1);
	}
	else
	{
		strncpy(sendbuf, q, q_s+1);
		sendbuf[q_s]='\n';
		q_s++;
		sendbuf[q_s]=0;
	}
    if (write(s, sendbuf, q_s) != q_s) { /* || write(s, "\r\n", 2) != 2)*/
        free(sendbuf);
        free(br);
        close(s);
        return NULL;
    }
    
    cur = 0;
    while ((n = read(s, buf, sizeof(buf))) > 0) {
        if ((cur + n) >= br_s) {
            char *tmp;
            br_s = br_s * 2;
            if (!(tmp = realloc(br, br_s))) {
                free(br);
                free(sendbuf);
                close(s);
                return NULL;
            }
            br = tmp;
        }
        strncpy((char *)&br[cur], buf, n);
        cur += n;   
    }
    br[cur] = 0;

    close(s);
    free(sendbuf);

    return br;
}

int 
w_lookup_all_pwhois(whois_session_params * wsess, char *addr)
{
    token_t *ls;
    char *serv, *reply;
    const char *format;
    int i;

    
    if (!addr)
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else 
        serv = pwhois_server;
    
    reply = w_ask(wsess, serv, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv);
            else
                fprintf(stderr,"No reply from %s.\n",serv);
        }
        return -1;
    }
    
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        char *value = NULL;
        if ((value = match_prefix("origin-as:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_asn,"?",1) == 0) strncpy(wsess->consolidated_asn,value,255);
        } else
        if ((value = match_prefix("prefix:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_route,"?",1) == 0) strncpy(wsess->consolidated_route,value,255);
        } else
        if ((value = match_prefix("as-path:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_asp,"?",1) == 0) strncpy(wsess->consolidated_asp,value,255);
        } else
        if ((value = match_prefix("org-name:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_orgname,"?",1) == 0) strncpy(wsess->consolidated_orgname,value,255);
        } else
        if ((value = match_prefix("net-name:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_netname,"?",1) == 0) strncpy(wsess->consolidated_netname,value,255);
        } else
        if ((value = match_prefix("cache-date:", ls[i].ptr))) {
            if ((wsess->tval = atol(value)) != 0) {
                format = "%d-%b-%y %H:%M:%S %Z";
                (void)strftime(wsess->tbuf, sizeof(wsess->tbuf), format, localtime(&wsess->tval));
            }
        }
        if(value)
            free(value);
    }
    
    free(ls); 
    free(reply); 
    return 0;
}

int 
w_lookup_all_riswhois(whois_session_params * wsess, char *addr)
{
    token_t *ls=NULL;
    char *serv=NULL, *reply=NULL;
    const char *risopts = "-1 -M ";  /* 1 object/prefix, Most specific */
    char *risquery = malloc((strlen(risopts)* sizeof(char)) + (strlen(addr) * sizeof(char)) + 1);
    unsigned int i;
    
    if (!addr) {
        free(risquery);
        return -1;
    }

    /* prepare the text-string-based query */
    risquery[0]=0;
    strcat(risquery,risopts);
    strcat(risquery,addr);
    
    reply = w_ask(wsess, ripe_ris_server, risquery, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server);
            else
                fprintf(stderr,"No reply from %s.\n",ripe_ris_server);
        }
        free(risquery);
        return -1;
    }
    
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        char *value = NULL;
        if ((value = match_prefix("origin:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_asn,"?",1) == 0) strncpy(wsess->consolidated_asn,value,255);
        } else
        if ((value = match_prefix("route:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_route,"?",1) == 0) strncpy(wsess->consolidated_route,value,255);
        } else
        if ((value = match_prefix("descr:", ls[i].ptr))) {
            if (strncmp(wsess->consolidated_orgname,"?",1) == 0) strncpy(wsess->consolidated_orgname,value,255);
            if (strncmp(wsess->consolidated_netname,"?",1) == 0) strncpy(wsess->consolidated_netname,value,255);
        }                                
        if(value)
            free(value);
    }
    
    free(ls); free(reply); free(risquery);
    return 0;
}

int
w_lookup_as_pwhois(whois_session_params * wsess, char *addr)
{
    token_t *ls;
    char *reply = NULL, *value = NULL;
    unsigned int i;
    int ans = 0;
    
    if (!addr)
        return 0;
    
    reply = w_ask(wsess, pwhois_server, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server);
            else
                fprintf(stderr,"No reply from %s.\n",pwhois_server);
        }
        return 0;
    }
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        if ((value = match_prefix("origin-as:", ls[i].ptr)))
            break;
    }
    
    free(ls); free(reply);
    
    if (!value)
        return 0;
    
    rm_spaces(value);
    
    for (i = 0; i < strlen(value); i++) {
        if (!isdigit((unsigned char)value[i])) {
            free(value);
            return 0;
        }
    }
    
    ans = strtol(value, (char **)NULL, 10);
    free(value);
    return ans;
}

int
w_lookup_as_riswhois(whois_session_params * wsess, char *addr)
{
    token_t *ls;
    char *reply = NULL, *value = NULL;
    const char *risopts = "-1 -M ";  /* 1 object/prefix, Most specific */
    char *risquery = malloc((strlen(risopts) * sizeof(char)) + (strlen(addr) * sizeof(char)) + 1);
    unsigned int i;
    int ans = 0;

    if (!addr) {
        free(risquery);
        return 0;
    }

    /* prepare the text-string-based query */
    risquery[0]=0;
    strcat(risquery,risopts);
    strcat(risquery,addr);
    
    reply = w_ask(wsess, ripe_ris_server, risquery, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server);
            else
                fprintf(stderr,"No reply from %s.\n",ripe_ris_server);
        }
        free(risquery);
        return 0;
    }
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        if ((value = match_prefix("origin:", ls[i].ptr)))
            break;
    }
    
    free(ls);
    free(reply);
    free(risquery);
    
    if (!value)
        return 0;
    
    rm_spaces(value);
    
    for (i = 0; i < strlen(value); i++) {
        if (!isdigit((unsigned char)value[i])) {
            free(value);
            return 0;
        }
    }
    
    ans = atoi(value);
    free(value);
    return ans;
}

int
w_lookup_all_riswhois_bulk(whois_session_params * wsess, struct ip_list_array *iplist)
{
    
    token_t *responses=0;
    char *reply=0;
    const char *bulk_begin = "-k -1 -M\n";  /* Keepalive, 1 object/prefix, Most specific */
    const char *bulk_end = "-k";
    char *bulk_ip_query = malloc((strlen(bulk_begin) * sizeof(char)) + ((strlen(bulk_end)+1) * sizeof(char)) + (16 * (*iplist).numItems));
    int i = 0;
    unsigned int j = 0;
    int k = 0;
    int entity_id = 0;
    unsigned int until = 0;
    char *value = NULL;
    
    bulk_ip_query[0]=0;
    if (!iplist) {
        free(bulk_ip_query);
        return -1;
    }
    
    /* clean up the response data set in case the caller doesn't (and we return error) */
    for (i = 0; i < (*iplist).numItems; i++) {
        (*iplist).asn[(i)] = 0;
        memset((*iplist).netName[i],0,sizeof((*iplist).netName[i]));
        memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i]));        
    }
    
    /* prepare the text-string-based query */
    strcat(bulk_ip_query,bulk_begin);
    for (i = 0; i < ((*iplist).numItems); i++) {
        strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i]));
        strcat(bulk_ip_query,"\n");
    }
    strcat(bulk_ip_query,bulk_end);
    
    reply = w_ask(wsess, ripe_ris_server, bulk_ip_query, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",ripe_ris_server);
            else
                fprintf(stderr,"No reply from %s.\n",ripe_ris_server);
        }
        /* clean up the response data set in case the caller doesn't */
        for (i = 0; i < (*iplist).numItems; i++)
            (*iplist).asn[(i)] = 0;
        free(bulk_ip_query);
        return -1;
    }
    responses = tokens(reply, "\n");

    for (i = 0; responses[i].ptr; i++) {
        value = NULL;
        if ((value = match_prefix("origin:", responses[i].ptr)) != NULL) {
            if (k > 0) { entity_id++; k = 0; }
            rm_spaces(value);       /* strip out any spaces from the ASN */
            for (j = 0; j < strlen(value); j++) {
                if (!isdigit((unsigned char)value[j])) {
                    if (wsess->w_noisy)
                    {
                        if(wsess->logprintfCookie)
                            lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                        else
                            printf("Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                    }
                    break;
                }
            }
            if(strtol(value, (char **) NULL, 10)) {
                (*iplist).asn[(entity_id)] = strtol(value, (char **)NULL, 10);
                k++; 
            } else if (wsess->w_noisy > 2)
            {
                if(wsess->logprintfCookie)
                    lft_printf(wsess->logprintfCookie,"Skipping additional object for same query.\n");
                else
                    fprintf(stderr,"Skipping additional object for same query.\n");
            }
        } else
        if ((value = match_prefix("descr:", responses[i].ptr))) {
            strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1);
            (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0';
            /* yes, this is duplicated.  riswhois adds a netname attribute here, so we reuse 'descr' */
            for (until = 0; until < strlen(value); until++) {
                if (isspace((unsigned char)value[until]))
                    break;
            }
            /* clamp the server-controlled descr length to the field width and
             * NUL-terminate (netName rows are fixed-size). */
            if (until > sizeof((*iplist).netName[entity_id]) - 1)
                until = sizeof((*iplist).netName[entity_id]) - 1;
            strncpy((*iplist).netName[entity_id],value,(until));
            (*iplist).netName[entity_id][until] = '\0';
            k++;
        } else
        if ((value = match_prefix("% ", responses[i].ptr)) != NULL) {
            if (i > 5 && k < 1) {   /* Weed out up to 5 leading lines from RIPE NCC RIS */
                if (wsess->w_noisy > 2)
                {
                    if(wsess->logprintfCookie)
                        lft_printf(wsess->logprintfCookie,"%% MATCHED on '%s'\n",responses[i].ptr);
                    else
                        printf("%% MATCHED on '%s'\n",responses[i].ptr);
                }
                /* (*iplist).asn[(entity_id)] = 0; */
                k++;
            } /* else printf("'%s'\n",responses[i].ptr); */
        } /* else printf("'%s'\n",responses[i].ptr); */
        if(value)
            free(value);
        if ((entity_id) >= (*iplist).numItems)
            break;
    }
    
    free(responses); free(reply); free(bulk_ip_query);
    
    return 0;
}

int
w_lookup_as(whois_session_params * wsess, char *addr)
{
    token_t *ls;
    ip_blk_t *a = NULL, *b = NULL;
    /* char *sa, *sb; */
    char *reply, *value = NULL;
    unsigned int i;
    int ans = 0;
    int use_this = 1;
    
    if (!addr)
        return 0;
    
    reply = w_ask(wsess, radb_server, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",radb_server);
            else
                fprintf(stderr,"No reply from %s.\n",radb_server);
        }
        return 0;
    }
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        value = NULL;
        if ((value = match_prefix("local-as:", ls[i].ptr)) != NULL) {
            break;
        } else
        if ((value = match_prefix("route:", ls[i].ptr)) != NULL ||
            (value = match_prefix("route6:", ls[i].ptr)) != NULL) {
            /* route6 prefixes yield a NULL range from the v4 mask helper;
             * the size-refinement below is simply skipped for them */
            a = match_ipprefix(value);

            if (b && a) {
                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;
                if (a) {
                    if (b) free(b);
                    b = a;
                }
                a = NULL;
            }
        } else
        if (use_this && (value = match_prefix("origin:", ls[i].ptr))) {
            break;
        }
        if(value != NULL)
            free(value);
    }
    
    free(ls);
    free(reply);
    if(b != NULL)
	free(b);
    
    if (!value)
        return 0;
    
    rm_spaces(value);
    
    for (i = 0; i < strlen(value); i++) {
        if (!isdigit((unsigned char)value[i])) {
            return 0;
        }
    }
    ans = strtol(value, (char **)NULL, 10);
    free(value);
    return ans;
}

int
w_lookup_as_cymru(whois_session_params * wsess, char *addr)
{
    /*
     *   Look up the ASN at the prefix-based Cymru whois server
     */
    token_t *ls;
    char *reply;
    unsigned int i;
    char value[6];
    memset(&value, 0, sizeof(value));
    
    if (!addr)
        return 0;
    
    reply = w_ask(wsess, cymru_server, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",cymru_server);
            else
                fprintf(stderr,"No reply from %s.\n",cymru_server);
        }
        return 0;
    }
    ls = tokens(reply, "\n");
    
    /* Set i to 1 to skip the first/header line of reply from cymru */
    strncpy(value,ls[1].ptr,5);
    rm_spaces(value);       /* strip out any spaces from the ASN */
    
    for (i = 0; i < strlen(value); i++) {
        if (!isdigit((unsigned char)value[i])) {
            return 0;
        }
    }
    
    free(ls); 
    free(reply);
    return (strtol(value, (char **)NULL, 10));
}

int
w_lookup_as_cymru_bulk(whois_session_params * wsess, struct ip_list_array *iplist)
{
    
    token_t *responses;
    char *reply;
    const char *bulk_begin = "begin\n";
    const char *bulk_end = "end\n";
    char *bulk_ip_query = malloc((strlen(bulk_begin) * sizeof(char)) + (strlen(bulk_end)* sizeof(char)) + (16 * (*iplist).numItems));
    int i;
    unsigned int j;
    char value[6];
    memset(&value, 0, sizeof(value));
    
    bulk_ip_query[0]=0;
    if (!iplist) {
        free(bulk_ip_query);
        return -1;
    }
    
    /* clean up the response data set in case the caller doesn't (and we return error) */
    for (i = 0; i < (*iplist).numItems; i++)
        (*iplist).asn[(i)] = 0; 
    
    /* prepare the text-string-based query */
    strcat(bulk_ip_query,bulk_begin);
    for (i = 0; i < ((*iplist).numItems); i++) {
        strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i]));
        strcat(bulk_ip_query,"\n");
    }
    strcat(bulk_ip_query,bulk_end);
    
    reply = w_ask(wsess, cymru_server, bulk_ip_query, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",cymru_server);
            else
                fprintf(stderr,"No reply from %s.\n",cymru_server);
        }
        free(bulk_ip_query);
        return -1;
    }
    responses = tokens(reply, "\n");
    
    /* Set i to 1 to skip the first/header line of reply from cymru */
    for (i = 1; responses[i].ptr; i++) {
        strncpy(value,responses[i].ptr,5);
        rm_spaces(value);       /* strip out any spaces from the ASN */
        for (j = 0; j < strlen(value); j++) {
            if (!isdigit((unsigned char)value[j])) {
                if (wsess->w_noisy)
                {
                    if(wsess->logprintfCookie)
                        lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                    else
                        fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                }
                break;
            }
        }
        if(strtol(value, (char **)NULL, 10)) { 
            (*iplist).asn[(i-1)] = strtol(value, (char **)NULL, 10);
        } else {
            (*iplist).asn[(i-1)] = 0;
        }
        if ((i+1) > (*iplist).numItems) 
            break;
    }
    
    free(responses); free(reply); free(bulk_ip_query); 
    
    return 0;
}

#ifdef STANDALONE
static int w_display_rvbyasn_pwhois(whois_session_params * wsess, char *asn)
{
    char *reply;
    const char *query_begin = "routeview source-as=";
    char *whob_query = NULL;
    char *serv;
    
    if (!asn) 
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    whob_query = malloc( (strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char) + 10);
    whob_query[0]=0;
    
    /* prepare the text-string-based query */
    strcat(whob_query,"app=\"");
    strcat(whob_query,appname);
    strcat(whob_query," ");
    strcat(whob_query,version);
    strcat(whob_query,"\" ");
    strcat(whob_query,query_begin);
    strcat(whob_query,asn);
    
    strcat(whob_query,"\n");
    
    reply = w_ask(wsess, serv, whob_query, NULL);
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }

    snprintf(w_highlight_asn, sizeof(w_highlight_asn), "%s", asn);
    w_print_pwhois_colorized(reply);
    w_highlight_asn[0] = '\0';

    free(reply); free(whob_query);

    return 0;
}

static int w_display_rvbytransitasn_pwhois(whois_session_params * wsess, char *asn)
{
    char *reply;
    const char *query_begin = "routeview transit-as=";
    char *whob_query = NULL;
    char *serv;
    
    if (!asn) 
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    whob_query = malloc( (strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char) + 10);
    whob_query[0]=0;
    
    /* prepare the text-string-based query */
    strcat(whob_query,"app=\"");
    strcat(whob_query,appname);
    strcat(whob_query," ");
    strcat(whob_query,version);
    strcat(whob_query,"\" ");
    strcat(whob_query,query_begin);
    strcat(whob_query,asn);
    
    strcat(whob_query,"\n");
    
    reply = w_ask(wsess, serv, whob_query, NULL);
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }

    snprintf(w_highlight_asn, sizeof(w_highlight_asn), "%s", asn);
    w_print_pwhois_colorized(reply);
    w_highlight_asn[0] = '\0';

    free(reply); free(whob_query);

    return 0;
}

static int w_display_contactsbyasn_pwhois(whois_session_params * wsess, char *asn)
{
    char *reply;
    const char *query_begin = "registry source-as=";
    char *whob_query = NULL;
    char *serv;
    
    if (!asn) 
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    whob_query = malloc( ((strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char)) + 10);
    whob_query[0]=0;
    
    /* prepare the text-string-based query */
    strcat(whob_query,"app=\"");
    strcat(whob_query,appname);
    strcat(whob_query," ");
    strcat(whob_query,version);
    strcat(whob_query,"\" ");
    strcat(whob_query,query_begin);
    strcat(whob_query,asn);
    
    strcat(whob_query,"\n");
    
    reply = w_ask(wsess, serv, whob_query, NULL);
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
    
    w_print_pwhois_colorized(reply);

    free(reply); free(whob_query);

    return 0;
}

static int w_display_networksbyasn_pwhois(whois_session_params * wsess, char *asn)
{
    char *reply;
    const char *query_begin = "netblock source-as=";
    char *whob_query = NULL;
    char *serv;
    
    if (!asn) 
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    whob_query =(char *)malloc(((strlen(appname) + strlen(version) + strlen(query_begin) + strlen(asn)) * sizeof(char)) + 10);
    whob_query[0]=0;

    /* prepare the text-string-based query */
    strcat(whob_query,"app=\"");
    strcat(whob_query,appname);
    strcat(whob_query," ");
    strcat(whob_query,version);
    strcat(whob_query,"\" ");
    strcat(whob_query,query_begin);
    strcat(whob_query,asn);
    
    strcat(whob_query,"\n");
    
    reply = w_ask(wsess, serv, whob_query, NULL);
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
    
    w_print_pwhois_colorized(reply);

    free(reply); free(whob_query);

    return 0;
}

static int w_display_rvbyprefix_pwhois(whois_session_params * wsess, char *prefix)
{
    char *reply;
    const char *query_begin = "routeview prefix=";
    char *whob_query = NULL;
    char *serv;
    
    if (!prefix) 
        return -1;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    whob_query = malloc(((strlen(appname) * sizeof(char))+10) + (strlen(version) * sizeof(char)) + (strlen(query_begin) * sizeof(char)) + (strlen(prefix))* sizeof(char));
    whob_query[0]=0;
    
    /* prepare the text-string-based query */
    strcat(whob_query,"app=\"");
    strcat(whob_query,appname);
    strcat(whob_query," ");
    strcat(whob_query,version);
    strcat(whob_query,"\" ");
    strcat(whob_query,query_begin);
    strcat(whob_query,prefix);
    
    strcat(whob_query,"\n");
    
    reply = w_ask(wsess, serv, whob_query, NULL);  
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
    
    w_print_pwhois_colorized(reply);

    free(reply); free(whob_query);

    return 0;
}

static int w_display_bulkfromfile_pwhois(whois_session_params * wsess, char *filespec)
{
    const char *query_begin = "begin\n";
    const char *query_end = "end\n";
    const char *appname_extras = "BULK_FILE";
    const char *format_instructions = "type=cymru\n";
    char *reply;
    char *serv;
    FILE *bulkFile;
    int num_lines = 0, i = 0;
    char *lines = malloc(line_size * max_lines);
    char *this_line = malloc(line_size);    
    size_t whob_query_len = ((strlen(appname) * sizeof(char) +10) + (strlen(appname_extras) * sizeof(char)) + (strlen(version) * sizeof(char))) + (strlen(query_begin) * sizeof(char)) + (strlen(format_instructions) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines);
    char *whob_query = (char *)malloc(whob_query_len);

    reply = NULL;
    *whob_query = '\0';
    
    if (!filespec) {
        fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n");
        free(lines);
	free(this_line);
	free(whob_query);
        exit(EXIT_FAILURE);
    }  
    
    if (!strncmp(filespec,"-",1) || use_stdin == 1) {
        bulkFile = stdin;
    } else 
        bulkFile = fopen(filespec, "r");
    
    if (!bulkFile) {
        fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec);
        free(lines);
	free(this_line); 
	free(whob_query);
        exit(EXIT_FAILURE);
    }
        
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    while (!feof(bulkFile)) {
        
        if (num_lines >= max_lines && wsess->w_noisy >= 2)
            fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1));
        
        memset(lines, 0, line_size * max_lines);
        memset(whob_query, 0, whob_query_len);
        
        for (i = 0; i < max_lines; i++) {
            
            if (fgets(this_line,line_size - 1,bulkFile)) {
                if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) {
                    strcat(lines,this_line);
                    num_lines++;
                    if (wsess->w_noisy >= 4) 
                        fprintf(stderr,"Line %d: %s",num_lines,this_line);
                }
            } else if (feof(bulkFile)) {
                if (wsess->w_noisy >= 2) 
                    fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines);
                break;
            } else if (ferror(bulkFile)) {
                if (wsess->w_noisy >= 1) 
                    fprintf(stderr,"Error in stream on line %d.\n",num_lines+1);
                break;
            }
            
        }
        
        /* prepare the text-string-based query */
        strcat(whob_query, query_begin);
        strcat(whob_query, "app=\"");
        strcat(whob_query, appname);
        strcat(whob_query, " ");
        strcat(whob_query, version);
        strcat(whob_query, " ");
        strcat(whob_query, appname_extras);
        strcat(whob_query, "\"\n");
        if (use_cymru) 
            strcat(whob_query, format_instructions);
        strcat(whob_query, lines);
        strcat(whob_query, query_end);
        
        reply = w_ask(wsess, serv, whob_query, NULL);
        if (!reply) {
            if (wsess->w_noisy)
		fprintf(stderr,"No reply from %s.\n",serv);
            free(this_line);
	    free(lines); 
	    free(whob_query);
	    free(reply);
            return -1;
        }
        
        w_print_pwhois_colorized(reply);
        
    }
    
    free(this_line);
    free(lines);
    free(whob_query);
    free(reply);
    fclose(bulkFile);
    return 0;
}

static int w_display_bulkfromfile_riswhois(whois_session_params * wsess, char *filespec)
{
    const char *query_begin = "-k -1 -M\n";  /* Keepalive, 1 object/prefix, Most specific */
    const char *query_end = "-k";
    char *reply;
    char *serv;
    FILE *bulkFile;
    int num_lines = 0, i = 0;
    char *lines = (char *)malloc(line_size * max_lines);
    char *this_line = (char *)malloc(line_size);    
    size_t whob_query_len = 10 + (strlen(query_begin) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines);
    char *whob_query = (char *)malloc(whob_query_len);

    reply = NULL;
    *whob_query = '\0';
    
    if (!filespec) {
        fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n");
        free(lines);
        free(this_line);
        free(whob_query);
        exit(EXIT_FAILURE);
    }

    if (!strncmp(filespec,"-",1) || use_stdin == 1) {
        bulkFile = stdin;
    } else
        bulkFile = fopen(filespec, "r");
    
    if (!bulkFile) {
        fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec);
        free(lines);
	free(this_line);
	free(whob_query);
        exit(EXIT_FAILURE);
    }
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = ripe_ris_server;
    
    while (!feof(bulkFile)) {
        
        if (num_lines >= max_lines && wsess->w_noisy >= 2)
            fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1));
        
        memset(lines, 0, line_size * max_lines);
        memset(whob_query, 0, whob_query_len);
        
        for (i = 0; i < max_lines; i++) {
            
            if (fgets(this_line,line_size - 1,bulkFile)) {
                if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) {
                    strcat(lines,this_line);
                    num_lines++;
                    if (wsess->w_noisy >= 4) 
                        fprintf(stderr,"Line %d: %s",num_lines,this_line);
                }
            } else if(feof(bulkFile)) {
                if (wsess->w_noisy >= 2) 
                    fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines);
                break;
            } else if (ferror(bulkFile)) {
                if (wsess->w_noisy >= 1) 
                    fprintf(stderr,"Error in stream on line %d.\n",num_lines+1);
                break;
            }
            
        }
        
        /* prepare the text-string-based query */
        strcat(whob_query, query_begin);
        strcat(whob_query, lines);
        strcat(whob_query, query_end);
        
        reply = w_ask(wsess, serv, whob_query, NULL);
        if (!reply) {
            if (wsess->w_noisy)
		fprintf(stderr,"No reply from %s.\n",serv);
            free(this_line);
	    free(lines);
	    free(whob_query);
	    free(reply);
            return -1;
        }
        
        printf("%s",reply);
        
    }
    
    free(this_line);
    free(lines);
    free(whob_query);
    free(reply);
    fclose(bulkFile);
    return 0;
}

static int w_display_bulkfromfile_cymru(whois_session_params * wsess, char *filespec)
{
    const char *query_begin = "begin\n";  
    const char *query_end = "end";
    char *reply;
    char *serv;
    FILE *bulkFile;
    int num_lines = 0, i = 0;
    char *lines = (char *)malloc(line_size * max_lines);
    char *this_line = (char *)malloc(line_size);    
    size_t whob_query_len = 10 + (strlen(query_begin) * sizeof(char)) + (strlen(query_end) * sizeof(char)) + (line_size * max_lines);
    char *whob_query = (char *)malloc(whob_query_len);
    
    reply = NULL;
    *whob_query = '\0';

    if (!filespec) {
        fprintf(stderr,"You must specify a file to use (or '-' for stdin) for bulk query input.\n");
        free(lines);
        free(this_line);
        free(whob_query);
        exit(EXIT_FAILURE);
    }

    if (!strncmp(filespec,"-",1) || use_stdin == 1) {
        bulkFile = stdin;
    } else
        bulkFile = fopen(filespec, "r");
    
    if (!bulkFile) {
        fprintf(stderr,"%s: Unable to open \'%s\' for reading.\n",appname,filespec);
        free(lines);
	free(this_line);
	free(whob_query);
        exit(EXIT_FAILURE);
    }
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = cymru_server;
    
    while (!feof(bulkFile)) {
        
        if (num_lines >= max_lines && wsess->w_noisy >= 2)
            fprintf(stderr,"Processing next batch of %d beginning at line %d.\n",max_lines,(num_lines+1));
        
        memset(lines, 0, line_size * max_lines);
        memset(whob_query, 0, whob_query_len);
        
        for (i = 0; i < max_lines; i++) {
            
            if (fgets(this_line, line_size - 1, bulkFile)) {
                if (strncmp(this_line, "#", 1) && strncmp(this_line, ";", 1)) {
                    strcat(lines, this_line);
                    num_lines++;
                    if (wsess->w_noisy >= 4) 
                        fprintf(stderr,"Line %d: %s",num_lines,this_line);
                }
            } else if(feof(bulkFile)) {
                if (wsess->w_noisy >= 2) 
                    fprintf(stderr,"End of file reached after reading %d lines.\n",num_lines);
                break;
            } else if(ferror(bulkFile)) {
                if (wsess->w_noisy >= 1) 
                    fprintf(stderr,"Error in stream on line %d.\n",num_lines+1);
                break;
            }
            
        }
        
        /* prepare the text-string-based query */
        strcat(whob_query, query_begin);
        strcat(whob_query, lines);
        strcat(whob_query, query_end);
        
        reply = w_ask(wsess, serv, whob_query, NULL);
        if (!reply) {
            if(wsess->w_noisy)
		fprintf(stderr,"No reply from %s.\n",serv);
            free(this_line); 
	    free(lines);
	    free(whob_query); 
	    free(reply);
            return -1;
        }
        
        printf("%s",reply);
        
    }
    
    free(this_line);
    free(lines);
    free(whob_query);
    free(reply);
    fclose(bulkFile);
    return 0;
}

static int w_display_pwhois_version(whois_session_params * wsess)
{
    char *reply;
    char *serv;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    if (wsess->w_noisy)
        fprintf(stderr,"Querying '%s' for version/status.\n",serv);
    
    reply = w_ask(wsess, serv, "version", NULL);
    if (!reply) {
        if(wsess->w_noisy)
		fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }

    w_print_status_colorized(reply);

    free(reply);
    return 0;
}

static int w_display_myaddress_http(whois_session_params * wsess)
{
    char *reply;
    char *serv;

    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = myaddress_server;

    if (wsess->w_noisy)
        fprintf(stderr,"Querying '%s' for my public address.\n",serv);

    reply = w_ask(wsess, serv, "Accept:*/*\nHost: myaddress.today\nUser-Agent: WhoB\nGET /\n\n", "80");
    if (!reply) {
        if(wsess->w_noisy)
                fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
  
    printf("%s",reply);

    free(reply);
    return 0;
}

static int w_display_myaddress(whois_session_params * wsess)
{
    char *reply;
    char *serv;

    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;

    if (wsess->w_noisy)
        fprintf(stderr,"Querying '%s' for my public address.\n",serv);

    reply = w_ask(wsess, serv, "whoami", NULL);
    if (!reply) {
        if(wsess->w_noisy)
                fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
   
    printf("%s",reply);

    free(reply);
    return 0;
}

/* Query the terminal's background colour via OSC 11 and decide light vs dark.
 * Returns 1 (light), 0 (dark), or -1 (unknown / not a terminal / no answer).
 * The round-trip runs ONLY when both stdout and stdin are real terminals, so
 * a redirect or pipe on either side never triggers it.  Terminal raw-mode is
 * saved and restored; a ~150 ms poll bounds the wait so an unresponsive
 * terminal (e.g. macOS Terminal.app) can't stall whob. */
static int w_query_bg_is_light(void)
{
#if defined(WIN32) || defined(_WIN32)
    return -1;
#else
    struct termios saved, raw;
    char buf[64];
    int n = 0, got = 0, light = -1;
    unsigned long r = 0, g = 0, b = 0, mx = 0;

    /* Only probe an interactive terminal on BOTH ends. */
    if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO))
        return -1;

    if (tcgetattr(STDIN_FILENO, &saved) != 0)
        return -1;
    raw = saved;
    raw.c_lflag &= ~(ICANON | ECHO);
    raw.c_cc[VMIN]  = 0;
    raw.c_cc[VTIME] = 0;
    if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) != 0)
        return -1;

    /* Ask: OSC 11 ; ? BEL  — reply is  ESC ] 11 ; rgb:RRRR/GGGG/BBBB (ST|BEL) */
    if (write(STDOUT_FILENO, "\033]11;?\007", 7) == 7) {
        struct pollfd pfd;
        pfd.fd = STDIN_FILENO;
        pfd.events = POLLIN;
        /* Drain the reply, allowing a short gap between bursts. */
        while (n < (int)sizeof(buf) - 1 && poll(&pfd, 1, 150) > 0) {
            ssize_t k = read(STDIN_FILENO, buf + n, sizeof(buf) - 1 - n);
            if (k <= 0) break;
            n += (int)k;
            if (memchr(buf, '\007', n) ||
                (n >= 2 && buf[n-2] == '\033' && buf[n-1] == '\\'))
                break;                 /* terminator seen */
        }
    }
    tcsetattr(STDIN_FILENO, TCSANOW, &saved);

    buf[n] = '\0';
    /* Parse "rgb:RRRR/GGGG/BBBB" (1-4 hex digits per channel). */
    {
        char *p = strstr(buf, "rgb:");
        if (p) {
            char cs[3][8];
            if (sscanf(p + 4, "%7[0-9a-fA-F]/%7[0-9a-fA-F]/%7[0-9a-fA-F]",
                       cs[0], cs[1], cs[2]) == 3) {
                int L0 = (int)strlen(cs[0]);
                r = strtoul(cs[0], NULL, 16);
                g = strtoul(cs[1], NULL, 16);
                b = strtoul(cs[2], NULL, 16);
                mx = (1UL << (4 * L0)) - 1;   /* full-scale for that width */
                got = (mx > 0);
            }
        }
    }
    if (got) {
        /* Perceived luminance on 0..1; > 0.5 => light background. */
        double lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / (double)mx;
        light = (lum > 0.5) ? 1 : 0;
    }
    return light;
#endif
}

/* COLORFGBG fallback ("fg;bg", e.g. "15;0"): bg 0-6 or 8 => dark, else light. */
static int w_colorfgbg_is_light(void)
{
    const char *e = getenv("COLORFGBG");
    const char *bg;
    int v;
    if (!e)
        return -1;
    bg = strrchr(e, ';');
    bg = bg ? bg + 1 : e;
    if (bg[0] < '0' || bg[0] > '9')   /* "default" etc. */
        return -1;
    v = atoi(bg);
    if (v == 8) return 0;
    return (v >= 7) ? 1 : 0;
}

/* Resolve colour state once, after option parsing and before any output.
 * Mirrors lft's -o detection (isatty + TERM + NO_COLOR); --color / --no-color
 * override.  Also resolves truecolor capability and the light/dark variant. */
static void w_detect_color(void)
{
    w_use_color = 0;
    w_truecolor = 0;
    w_bg_light  = 0;

    if (w_color_mode == 2)             /* --no-color: hard off */
        return;

#if !defined(WIN32) && !defined(_WIN32)
    {
        const char *term      = getenv("TERM");
        const char *colorterm = getenv("COLORTERM");

        if (w_color_mode == 1) {       /* --color: force on */
            w_use_color = 1;
        } else if (isatty(fileno(stdout)) && term &&
                   strncmp(term, "dumb",    4) != 0 &&
                   strncmp(term, "unknown", 7) != 0 &&
                   !getenv("NO_COLOR") &&
                   (colorterm ||
                    strncmp(term, "xterm",  5) == 0 ||
                    strncmp(term, "screen", 6) == 0 ||
                    strncmp(term, "tmux",   4) == 0 ||
                    strncmp(term, "rxvt",   4) == 0)) {
            w_use_color = 1;
        }

        if (w_use_color) {
            int bg;
            if (colorterm &&
                (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit")))
                w_truecolor = 1;
            /* Background probe is self-gated to TTY-on-both-ends. */
            bg = w_query_bg_is_light();
            if (bg < 0) bg = w_colorfgbg_is_light();
            w_bg_light = (bg == 1) ? 1 : 0;   /* default: dark */
        }
    }
#else
    if (w_color_mode == 1)
        w_use_color = 1;
#endif
}

/* Colour is allowed on the verbose/diagnostic stream (stderr) only when colour
 * is enabled AND stderr is itself a terminal, so redirected logs stay clean. */
static int w_ecolor(void)
{
#if defined(WIN32) || defined(_WIN32)
    return 0;
#else
    return w_use_color && isatty(fileno(stderr));
#endif
}

/* SGR sequence for a field group, honouring truecolor vs 256-colour and the
 * resolved light/dark variant.  Per-group static buffers so several calls can
 * coexist in one printf. */
static const char *w_field_seq(int field)
{
    static char buf[WF_NGROUPS][24];
    const wc_color *c;
    if (field < 0 || field >= WF_NGROUPS)
        return "";
    c = &w_palette[w_bg_light][field];
    if (w_truecolor)
        snprintf(buf[field], sizeof(buf[field]),
                 "\033[38;2;%u;%u;%um", c->r, c->g, c->b);
    else
        snprintf(buf[field], sizeof(buf[field]), "\033[38;5;%um", c->x256);
    return buf[field];
}

/* SGR for one of the three table date shades (0=Create,1=Modify,2=Originated). */
static const char *w_date_seq(int shade)
{
    static char buf[3][24];
    const wc_color *c;
    if (shade < 0 || shade > 2)
        return "";
    c = &w_date_shade[w_bg_light][shade];
    if (w_truecolor)
        snprintf(buf[shade], sizeof(buf[shade]),
                 "\033[38;2;%u;%u;%um", c->r, c->g, c->b);
    else
        snprintf(buf[shade], sizeof(buf[shade]), "\033[38;5;%um", c->x256);
    return buf[shade];
}

/* Case-insensitive "does s end with suf?" */
static int w_ends(const char *s, const char *suf)
{
    size_t ls = strlen(s), lf = strlen(suf);
    return ls >= lf && strcasecmp(s + ls - lf, suf) == 0;
}

/* Map a pWhoIs field label to a colour group.  Pattern-based (suffix/exact)
 * rather than an exhaustive table, so it covers the compact v4 record, the
 * larger v6 / type=all record (Geo-*, Abuse-0-*, Admin-0-*, NOC-0-*, Tech-0-*
 * contact blocks, Route-Prefix, Create-/Modify-Date, …) and future fields
 * uniformly.  Order matters: more specific tests come first. */
static int w_pwhois_field(const char *s)
{
    if (w_ends(s, "-Date") || strcasecmp(s, "Route-Originated-TS") == 0)
        return WF_DATE;
    if (w_ends(s, "Latitude") || w_ends(s, "Longitude"))
        return WF_GEO;
    if (w_ends(s, "-City")   || w_ends(s, "-Country") || w_ends(s, "-State") ||
        w_ends(s, "-Postal-Code") || w_ends(s, "-Street-1") ||
        w_ends(s, "-Street-2") || w_ends(s, "-Region") || w_ends(s, "-CC") ||
        strcasecmp(s, "City") == 0     || strcasecmp(s, "Region") == 0 ||
        strcasecmp(s, "Country") == 0  || strcasecmp(s, "Country-Code") == 0 ||
        strcasecmp(s, "State") == 0    || strcasecmp(s, "Postal-Code") == 0 ||
        strcasecmp(s, "Street-1") == 0 || strcasecmp(s, "Street-2") == 0)
        return WF_GEO;
    if (w_ends(s, "-Prefix") ||
        strcasecmp(s, "IP") == 0        || strcasecmp(s, "Prefix") == 0 ||
        strcasecmp(s, "Net-Range") == 0 || strcasecmp(s, "Next-Hop") == 0 ||
        strcasecmp(s, "Router-ID") == 0)
        return WF_PREFIX;
    if (strcasecmp(s, "Origin-AS") == 0 || strcasecmp(s, "Transit-AS") == 0 ||
        strcasecmp(s, "AS-Path") == 0   || strcasecmp(s, "ASN") == 0)
        return WF_ASN;
    if (w_ends(s, "-Name") || w_ends(s, "-Handle") || w_ends(s, "-Org-ID") ||
        strcasecmp(s, "Org-ID") == 0 || strcasecmp(s, "Org-Name") == 0 ||
        strcasecmp(s, "Net-Name") == 0)
        return WF_ORG;
    return WF_NONE;
}

/* True if the value is purely a count/number (digits, optional grouping
 * commas/spaces) — used to give unlabelled numeric values (e.g. the server
 * statistics counts) the ASN/number colour. */
static int w_value_numeric(const char *s, size_t len)
{
    size_t i;
    int digits = 0;
    if (!len) return 0;
    for (i = 0; i < len; i++) {
        if (s[i] >= '0' && s[i] <= '9') digits = 1;
        else if (s[i] != ',' && s[i] != ' ') return 0;
    }
    return digits;
}

/* Per-column render kinds for the -a / -A route tables. */
enum { CK_NONE = 0, CK_PREFIX, CK_ASN, CK_ASPATH, CK_ORG_HI, CK_ORG_LO,
       CK_GEO, CK_DATE_C, CK_DATE_M, CK_DATE_O };
#define W_MAXCOL 24

/* Map a (trimmed) table column header name to a render kind. */
static int w_col_kind(const char *name)
{
    static const struct { const char *n; int k; } map[] = {
        { "Prefix",               CK_PREFIX },
        { "Next-Hop",             CK_PREFIX },
        { "Origin-AS",            CK_ASN    },
        { "AS-Path",              CK_ASPATH },
        { "Org-Name",             CK_ORG_HI },   /* more specific -> higher-viz */
        { "AS-Org-Name",          CK_ORG_LO },
        { "CC",                   CK_GEO    },
        { "Country-Code",         CK_GEO    },
        { "Create-Date",          CK_DATE_C },
        { "Modify-Date",          CK_DATE_M },
        { "Originated-Date",      CK_DATE_O },
        { "Route-Originated-Date",CK_DATE_O },
        { NULL, CK_NONE }
    };
    int i;
    for (i = 0; map[i].n; i++)
        if (strcasecmp(name, map[i].n) == 0)
            return map[i].k;
    return CK_NONE;
}

/* Copy [p,end) with surrounding spaces trimmed into a bounded buffer. */
static void w_trim_copy(const char *p, const char *end, char *out, size_t outsz)
{
    size_t n;
    while (p < end && *p == ' ') p++;
    while (end > p && end[-1] == ' ') end--;
    n = (size_t)(end - p);
    if (n >= outsz) n = outsz - 1;
    memcpy(out, p, n);
    out[n] = '\0';
}

/* Emit the leading BGP status flag of a data row's first column: the best-path
 * '>' takes the prefix colour, '*' is dimmed, everything else prints as-is. */
static void w_emit_flag(const char *s, size_t len)
{
    size_t i;
    for (i = 0; i < len; i++) {
        if (s[i] == '>')
            printf("%s>%s", w_field_seq(WF_PREFIX), WC_RESET);
        else if (s[i] == '*')
            printf("%s*%s", WC_DIM, WC_RESET);
        else
            putchar(s[i]);
    }
}

/* Emit an AS-Path cell in the ASN colour, bolding whole-token occurrences of
 * the queried ASN so it can be traced through every path. */
static void w_emit_aspath(const char *s, size_t len)
{
    const char *seq = w_field_seq(WF_ASN);
    size_t hllen = strlen(w_highlight_asn);
    size_t i = 0;
    printf("%s", seq);
    while (i < len) {
        size_t j;
        if (s[i] == ' ' || s[i] == '\t') { putchar(s[i]); i++; continue; }
        j = i;
        while (j < len && s[j] != ' ' && s[j] != '\t') j++;
        if (hllen && (j - i) == hllen && strncmp(s + i, w_highlight_asn, hllen) == 0)
            printf("%s%.*s\033[22m", WC_BOLD, (int)(j - i), s + i);  /* bold, keep colour */
        else
            printf("%.*s", (int)(j - i), s + i);
        i = j;
    }
    printf("%s", WC_RESET);
}

/* Emit one table data cell (not the flag/prefix column 0) by its render kind. */
static void w_emit_cell(const char *s, size_t len, int kind)
{
    switch (kind) {
        case CK_PREFIX: printf("%s%.*s%s", w_field_seq(WF_PREFIX), (int)len, s, WC_RESET); break;
        case CK_ASN:    printf("%s%.*s%s", w_field_seq(WF_ASN),    (int)len, s, WC_RESET); break;
        case CK_GEO:    printf("%s%.*s%s", w_field_seq(WF_GEO),    (int)len, s, WC_RESET); break;
        case CK_ORG_HI: printf("%s%s%.*s%s", WC_BOLD, w_field_seq(WF_ORG), (int)len, s, WC_RESET); break;
        case CK_ORG_LO: printf("%s%s%.*s%s", WC_DIM,  w_field_seq(WF_ORG), (int)len, s, WC_RESET); break;
        case CK_DATE_C: printf("%s%.*s%s", w_date_seq(0), (int)len, s, WC_RESET); break;
        case CK_DATE_M: printf("%s%.*s%s", w_date_seq(1), (int)len, s, WC_RESET); break;
        case CK_DATE_O: printf("%s%.*s%s", w_date_seq(2), (int)len, s, WC_RESET); break;
        case CK_ASPATH: w_emit_aspath(s, len); break;
        default:        printf("%.*s", (int)len, s); break;
    }
}

/* Split a '|'-delimited line into cells and invoke cb(field, end, col).
 * Separators are emitted dimmed.  Preserves every space so column alignment
 * (which the server already produced) is untouched — only zero-width SGR
 * codes are inserted. */
static void w_render_row(const char *line, size_t len,
                         void (*cb)(const char *, const char *, int))
{
    const char *p = line, *end = line + len;
    int col = 0;
    while (p <= end) {
        const char *bar  = (const char *)memchr(p, '|', (size_t)(end - p));
        const char *fend = bar ? bar : end;
        cb(p, fend, col);
        if (!bar) break;
        printf("%s|%s", WC_DIM, WC_RESET);
        p = bar + 1;
        col++;
    }
}

/* Header/data column kinds shared between the header row and the rows below it.
 * A reply carries one header then its rows, so file-scope state is sufficient. */
static int  w_col_kinds[W_MAXCOL];
static int  w_col_ncols = 0;

static void w_header_cb(const char *p, const char *fend, int col)
{
    char name[40];
    w_trim_copy(p, fend, name, sizeof(name));
    if (col < W_MAXCOL) { w_col_kinds[col] = w_col_kind(name); w_col_ncols = col + 1; }
    printf("%s%s%.*s%s", WC_BOLD, w_field_seq(WF_LABEL), (int)(fend - p), p, WC_RESET);
}

static void w_data_cb(const char *p, const char *fend, int col)
{
    int kind = (col < w_col_ncols) ? w_col_kinds[col] : CK_NONE;
    if (col == 0) {
        /* Column 0 = BGP flag(s) + spaces + prefix (+ trailing spaces).  The
         * prefix is the last whitespace-delimited token, so a v6 prefix that
         * starts with a hex letter (e.g. fe80::/10) is not mis-split; the flag
         * region is everything before it, trailing spaces are preserved. */
        const char *pe = fend, *ps;
        while (pe > p && pe[-1] == ' ') pe--;
        ps = pe;
        while (ps > p && ps[-1] != ' ') ps--;
        w_emit_flag(p, (size_t)(ps - p));
        if (pe > ps)
            printf("%s%.*s%s", w_field_seq(WF_PREFIX), (int)(pe - ps), ps, WC_RESET);
        if (fend > pe)
            printf("%.*s", (int)(fend - pe), pe);
    } else {
        w_emit_cell(p, (size_t)(fend - p), kind);
    }
}

/* Print a raw pWhoIs reply with the Cartographer colour scheme.  Handles the
 * default "Label: value" output and the tabular -a / -A route views (header
 * row learns the columns, rows below are coloured per column).  Unrecognised
 * lines pass through; with colour off this is a straight fputs.  */
/* Colourise a JSON / JSONP (type=json, type=jsonp) reply.  Keys take the label
 * colour, values the colour of their field group (numeric values of unknown
 * keys read as numbers), structural punctuation is dimmed, and whitespace is
 * emitted verbatim so both the pretty and compact forms keep their shape. */
static void w_print_json_colorized(const char *reply)
{
    const char *p = reply;
    int  expect_key = 1;
    char key[64];

    key[0] = '\0';
    while (*p) {
        unsigned char c = (unsigned char)*p;

        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { putchar(c); p++; continue; }

        if (c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',') {
            printf("%s%c%s", WC_DIM, c, WC_RESET);
            if      (c == ':') expect_key = 0;
            else if (c == ',') expect_key = 1;
            else if (c == '{') expect_key = 1;
            else if (c == '[') expect_key = 0;
            p++;
            continue;
        }

        if (c == '"') {                       /* a quoted string: key or value */
            const char *s = p + 1, *q = s;
            while (*q && *q != '"') { if (*q == '\\' && q[1]) q += 2; else q++; }
            if (expect_key) {
                size_t klen = (size_t)(q - s);
                if (klen < sizeof(key)) { memcpy(key, s, klen); key[klen] = '\0'; }
                else key[0] = '\0';
                printf("%s\"%.*s\"%s", w_field_seq(WF_LABEL), (int)(q - s), s, WC_RESET);
            } else {
                int f = w_pwhois_field(key);
                if (f != WF_NONE)
                    printf("%s\"%.*s\"%s", w_field_seq(f), (int)(q - s), s, WC_RESET);
                else
                    printf("\"%.*s\"", (int)(q - s), s);
            }
            p = *q ? q + 1 : q;
            continue;
        }

        {                                     /* bare value: number/true/false/null */
            const char *s = p;
            int f;
            size_t tl;
            while (*p && *p != ',' && *p != '}' && *p != ']' && *p != ':' &&
                   *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r')
                p++;
            tl = (size_t)(p - s);
            f  = w_pwhois_field(key);
            if (f != WF_NONE)
                printf("%s%.*s%s", w_field_seq(f), (int)tl, s, WC_RESET);
            else if (tl && (s[0] == '-' || (s[0] >= '0' && s[0] <= '9')))
                printf("%s%.*s%s", w_field_seq(WF_ASN), (int)tl, s, WC_RESET);
            else
                printf("%.*s", (int)tl, s);    /* true / false / null */
        }
    }
}

static void w_print_pwhois_colorized(const char *reply)
{
    const char *p = reply;

    if (!w_use_color) {
        fputs(reply, stdout);
        return;
    }
    /* JSON / JSONP replies (type=json, type=jsonp) begin with '{' or '['. */
    {
        const char *j = reply;
        while (*j == ' ' || *j == '\t' || *j == '\n' || *j == '\r') j++;
        if (*j == '{' || *j == '[') { w_print_json_colorized(reply); return; }
    }
    w_col_ncols = 0;
    while (*p) {
        const char *eol     = strchr(p, '\n');
        size_t      linelen = eol ? (size_t)(eol - p) : strlen(p);
        const char *nl      = eol ? "\n" : "";
        const char *bar     = (const char *)memchr(p, '|', linelen);

        if (bar) {
            /* A table line: header if the first non-space char is a letter
             * (column names), otherwise a data row ('*' flag or a digit). */
            const char *q = p;
            while (q < p + linelen && *q == ' ') q++;
            int is_hdr = (q < p + linelen) &&
                         (((*q >= 'A') && (*q <= 'Z')) || ((*q >= 'a') && (*q <= 'z')));
            if (is_hdr)
                w_render_row(p, linelen, w_header_cb);
            else
                w_render_row(p, linelen, w_data_cb);
            fputs(nl, stdout);
        } else {
            const char *colon = memchr(p, ':', linelen);
            if (colon && colon > p) {
                char        label[64];
                size_t      llen = (size_t)(colon - p);
                const char *val  = colon + 1;
                size_t      vlen;
                while (val < p + linelen && *val == ' ') val++;
                vlen = (size_t)(p + linelen - val);
                if (llen < sizeof(label)) {
                    int f;
                    const char *lab;
                    memcpy(label, p, llen);
                    label[llen] = '\0';
                    lab = label;
                    while (*lab == ' ') lab++;   /* stats labels are indented */
                    f = w_pwhois_field(lab);
                    printf("%s%s:%s ", w_field_seq(WF_LABEL), label, WC_RESET);
                    if (f != WF_NONE)
                        printf("%s%.*s%s%s", w_field_seq(f), (int)vlen, val, WC_RESET, nl);
                    else if (w_value_numeric(val, vlen))
                        printf("%s%.*s%s%s", w_field_seq(WF_ASN), (int)vlen, val, WC_RESET, nl);
                    else
                        printf("%.*s%s", (int)vlen, val, nl);
                    if (!eol) break;
                    p = eol + 1;
                    continue;
                }
            }
            printf("%.*s%s", (int)linelen, p, nl);   /* passthrough */
        }
        if (!eol) break;
        p = eol + 1;
    }
}

/* --- free-form status colouring (whob -s) --------------------------------- */

static int w_tok_is_month(const char *t, size_t len)
{
    static const char *m[] = { "jan","feb","mar","apr","may","jun",
                               "jul","aug","sep","oct","nov","dec" };
    int i;
    if (len != 3) return 0;
    for (i = 0; i < 12; i++)
        if (strncasecmp(t, m[i], 3) == 0) return 1;
    return 0;
}

/* A date component after a month: a run of digits with optional ':' / '-' / ','. */
static int w_tok_is_datepart(const char *t, size_t len)
{
    size_t i;
    int digit = 0;
    if (!len) return 0;
    for (i = 0; i < len; i++) {
        char c = t[i];
        if (c >= '0' && c <= '9') digit = 1;
        else if (c != ':' && c != '-' && c != ',') return 0;
    }
    return digit;
}

static int w_tok_is_number(const char *t, size_t len)
{
    size_t i;
    if (!len) return 0;
    for (i = 0; i < len; i++)
        if (!(t[i] >= '0' && t[i] <= '9')) return 0;
    return 1;
}

/* Colour the pWhoIs server status/version banner (-s): counts in the number
 * colour, "Mon DD YYYY HH:MM:SS" date runs in the date colour, a leading
 * "(version)" token in the label colour; descriptive prose passes through. */
static void w_print_status_colorized(const char *reply)
{
    const char *p = reply;

    if (!w_use_color) {
        fputs(reply, stdout);
        return;
    }
    while (*p) {
        const char *eol   = strchr(p, '\n');
        const char *end   = eol ? eol : p + strlen(p);
        const char *q     = p;

        while (q < end) {
            const char *t;
            size_t tl;
            if (*q == ' ') { putchar(' '); q++; continue; }
            t = q;
            while (q < end && *q != ' ') q++;
            tl = (size_t)(q - t);

            if (w_tok_is_month(t, tl)) {
                /* month + following date-part tokens share the date colour */
                printf("%s%.*s", w_field_seq(WF_DATE), (int)tl, t);
                while (q < end) {
                    const char *sp = q, *t2;
                    size_t tl2;
                    while (q < end && *q == ' ') q++;
                    t2 = q;
                    while (q < end && *q != ' ') q++;
                    tl2 = (size_t)(q - t2);
                    if (tl2 && w_tok_is_datepart(t2, tl2)) {
                        fwrite(sp, 1, (size_t)(t2 - sp), stdout);   /* gap */
                        printf("%.*s", (int)tl2, t2);
                    } else {
                        printf("%s", WC_RESET);
                        q = sp;                     /* re-handle from the gap */
                        break;
                    }
                }
                printf("%s", WC_RESET);
            } else if (w_tok_is_number(t, tl)) {
                printf("%s%.*s%s", w_field_seq(WF_ASN), (int)tl, t, WC_RESET);
            } else if (tl > 2 && t[0] == '(' &&
                       memchr(t, '.', tl) && strchr("0123456789", t[1])) {
                /* a version like "(2.2.2.3-fix)" — not "(c)" */
                printf("%s%.*s%s", w_field_seq(WF_LABEL), (int)tl, t, WC_RESET);
            } else {
                printf("%.*s", (int)tl, t);
            }
        }
        if (!eol) break;
        putchar('\n');
        p = eol + 1;
    }
}

/* ==== --annotate: enrich IP addresses found in arbitrary text ==============
 * A Unix filter (`traceroute x | whob --annotate`, `grep denied fw.log | ...`).
 * Reads stdin (or --annotate=FILE) in chunks, finds IPv4/IPv6 literals with
 * inet_pton validation (so version numbers, times and domain names are never
 * mistaken for addresses; ip:port, CIDR and trailing punctuation are handled),
 * resolves the DISTINCT addresses through batched pWhoIs bulk lookups, and
 * re-emits every line verbatim with " [ASn Org-Name]" after each address
 * ("[?]" when pWhoIs has nothing).  Colourised on a terminal like the rest. */
static int  annotate_mode = 0;
static char annotate_file[1024] = "";
#define ANN_BATCH 500      /* addresses per pWhoIs bulk request */
#define ANN_CHUNK 2000     /* input lines buffered per pass */

struct ann_ent { char ip[INET6_ADDRSTRLEN]; int asn; int have; char org[100]; char asorg[100]; char net[32];
                 char prefix[48]; char city[50]; char region[50]; char country[50]; char cc[4]; };

/* -F/--fields: which pWhoIs fields the annotation carries, printed in this order */
enum { AF_ASN=1, AF_ORG=2, AF_ASORG=4, AF_NET=8, AF_PREFIX=16, AF_CITY=32, AF_REGION=64, AF_COUNTRY=128, AF_CC=256, AF_PLACE=512 };
#define AF_DEFAULT (AF_ASN|AF_ORG)
#define AF_ALL     (AF_ASN|AF_ORG|AF_ASORG|AF_NET|AF_PREFIX|AF_CITY|AF_REGION|AF_COUNTRY|AF_CC|AF_PLACE)
static unsigned ann_fields = AF_DEFAULT;
static int ann_parse_fields(const char *spec)      /* 0 = bad token */
{
    static const struct { const char *name; unsigned bit; } tok[] = {
        {"asn",AF_ASN},{"org",AF_ORG},{"asorg",AF_ASORG},{"net",AF_NET},{"prefix",AF_PREFIX},
        {"city",AF_CITY},{"region",AF_REGION},{"country",AF_COUNTRY},{"cc",AF_CC},{"place",AF_PLACE},{"all",AF_ALL} };
    unsigned f = (*spec == '+' || *spec == '-') ? AF_DEFAULT : 0;
    char buf[256], *p, *save = NULL;
    snprintf(buf, sizeof buf, "%s", spec);
    for (p = strtok_r(buf, ",", &save); p; p = strtok_r(NULL, ",", &save)) {
        int sign = 1; size_t i; unsigned bit = 0;
        if (*p == '+') p++; else if (*p == '-') { sign = 0; p++; }
        for (i = 0; i < sizeof tok / sizeof tok[0]; i++) if (strcasecmp(p, tok[i].name) == 0) bit = tok[i].bit;
        if (!bit) return 0;
        if (sign) f |= bit; else f &= ~bit;
    }
    if (!f) return 0;
    ann_fields = f;
    return 1;
}
static struct ann_ent *ann_cache = NULL;
static int ann_n = 0, ann_cap = 0, ann_sorted = 0;   /* [0,ann_sorted) is sorted */

static int ann_cmp(const void *a, const void *b)
{
    return strcmp(((const struct ann_ent *)a)->ip, ((const struct ann_ent *)b)->ip);
}
static int ann_find(const char *ip)                 /* index or -1 */
{
    struct ann_ent key, *e;
    int i;
    memset(&key, 0, sizeof key);
    snprintf(key.ip, sizeof key.ip, "%s", ip);
    if (ann_sorted && (e = bsearch(&key, ann_cache, (size_t)ann_sorted, sizeof *ann_cache, ann_cmp)))
        return (int)(e - ann_cache);
    for (i = ann_sorted; i < ann_n; i++)            /* unsorted tail */
        if (strcmp(ann_cache[i].ip, ip) == 0) return i;
    return -1;
}
static int ann_add(const char *ip)                  /* index or -1 */
{
    if (ann_n >= ann_cap) {
        int nc = ann_cap ? ann_cap * 2 : 512;
        struct ann_ent *p = realloc(ann_cache, (size_t)nc * sizeof *p);
        if (!p) return -1;
        ann_cache = p; ann_cap = nc;
    }
    memset(&ann_cache[ann_n], 0, sizeof ann_cache[ann_n]);
    snprintf(ann_cache[ann_n].ip, sizeof ann_cache[ann_n].ip, "%s", ip);
    return ann_n++;
}
static void ann_sort(void)
{
    if (ann_n > 1) qsort(ann_cache, (size_t)ann_n, sizeof *ann_cache, ann_cmp);
    ann_sorted = ann_n;
}

/* canonical text of an address literal in tok[0..len): 4, 6, or 0 if none */
static int ann_canon(const char *tok, size_t len, char *out, size_t outsz)
{
    char buf[INET6_ADDRSTRLEN + 8];
    struct in_addr a4; struct in6_addr a6;
    if (!len || len >= sizeof buf) return 0;
    memcpy(buf, tok, len); buf[len] = '\0';
    if (strchr(buf, ':')) {
        if (inet_pton(AF_INET6, buf, &a6) == 1 && inet_ntop(AF_INET6, &a6, out, (socklen_t)outsz)) return 6;
        return 0;
    }
    if (inet_pton(AF_INET, buf, &a4) == 1 && inet_ntop(AF_INET, &a4, out, (socklen_t)outsz)) return 4;
    return 0;
}
static int ann_ischar(char c)
{
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '.' || c == ':';
}

/* move the insertion point past an attached "/len" (CIDR) or "]" [+ ":port"]
 * so the annotation never lands inside a prefix or a bracketed address */
static size_t ann_extend(const char *s, size_t n, size_t ins)
{
    size_t k;
    if (ins < n && s[ins] == '/') {
        for (k = ins + 1; k < n && s[k] >= '0' && s[k] <= '9'; k++) ;
        if (k > ins + 1) ins = k;
    } else if (ins < n && s[ins] == ']') {
        ins++;
        if (ins < n && s[ins] == ':') {
            for (k = ins + 1; k < n && s[k] >= '0' && s[k] <= '9'; k++) ;
            if (k > ins + 1) ins = k;
        }
    }
    return ins;
}

/* Next address literal in s at/after *pos.  Returns the family and sets
 * *ins (where the annotation goes: right after the address, or after an
 * attached :port / /len / ]) and canon; advances *pos past the token. */
static int ann_next(const char *s, size_t *pos, size_t *ins, char *canon, size_t csz)
{
    size_t i = *pos, n = strlen(s);
    while (i < n) {
        size_t b, e, t, k;
        int fam, dig = 0, sep = 0;
        while (i < n && !ann_ischar(s[i])) i++;
        if (i >= n) break;
        b = i;
        while (i < n && ann_ischar(s[i])) i++;
        e = i;
        for (t = b; t < e; t++) { if (s[t] >= '0' && s[t] <= '9') dig = 1; if (s[t] == '.' || s[t] == ':') sep = 1; }
        if (!dig || !sep) continue;
        if ((fam = ann_canon(s + b, e - b, canon, csz))) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return fam; }
        for (t = e; t > b && t + 2 > e && (s[t-1] == '.' || s[t-1] == ':'); t--)   /* trailing punctuation */
            if ((fam = ann_canon(s + b, t - 1 - b, canon, csz))) { *ins = t - 1; *pos = e; return fam; }
        k = e;                                                                    /* v4 with :port */
        while (k > b && s[k-1] != ':') k--;
        if (k > b && ann_canon(s + b, k - 1 - b, canon, csz) == 4) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return 4; }
        {                                                                         /* BSD/macOS netstat "a.b.c.d.port" */
            int dots = 0; size_t p4 = b;
            for (t = b; t < e; t++) if (s[t] == '.') { if (++dots == 4) p4 = t; }
            if (dots == 4 && p4 > b && ann_canon(s + b, p4 - b, canon, csz) == 4) { *ins = ann_extend(s, n, e); *pos = *ins > e ? *ins : e; return 4; }
        }
    }
    *pos = n;
    return 0;
}

static int ann_prefix(const char *line, const char *pfx, const char **val)
{
    size_t l = strlen(pfx);
    if (strncasecmp(line, pfx, l) != 0) return 0;
    line += l;
    while (*line == ' ' || *line == '\t') line++;
    *val = line;
    return 1;
}

/* one pWhoIs bulk request for the cache entries whose indices are in pend[] */
/* copy a reply value, treating pWhoIs's literal "NULL" as absent */
static void ann_val(char *dst, size_t sz, const char *v)
{
    size_t n = strcspn(v, "\r");
    if (n == 4 && strncasecmp(v, "NULL", 4) == 0) { dst[0] = '\0'; return; }
    snprintf(dst, sz, "%.*s", (int)n, v);
}
static void ann_resolve(whois_session_params *wsess, const int *pend, int np)
{
    size_t qlen = 96 + (size_t)np * (INET6_ADDRSTRLEN + 1);
    char *q, *reply, *serv;
    token_t *ls;
    int i, cur = -1;

    if (np <= 0) return;
    serv = (strlen(wsess->pw_serv) > 0) ? wsess->pw_serv : pwhois_server;
    if (!(q = malloc(qlen))) return;
    snprintf(q, qlen, "begin\napp=\"%s %s ANNOTATE\"\n", appname, version);
    for (i = 0; i < np; i++) { strcat(q, ann_cache[pend[i]].ip); strcat(q, "\n"); }
    strcat(q, "end\n");
    reply = w_ask(wsess, serv, q, NULL);
    free(q);
    if (!reply) return;
    if (strncasecmp(reply, "Error:", 6) == 0) {         /* e.g. daily query limit exceeded */
        static int shown = 0;
        const char *m = reply;
        while (strncasecmp(m, "Error:", 6) == 0) { m += 6; while (*m == ' ') m++; }   /* server doubles the prefix */
        if (!shown) { fflush(stdout); fprintf(stderr, "%s: pWhoIs: %.*s\n", appname, (int)strcspn(m, "\r\n"), m); shown = 1; }
        free(reply); return;
    }
    ls = tokens(reply, "\n");
    for (i = 0; ls[i].ptr; i++) {
        const char *v;
        char canon[INET6_ADDRSTRLEN];
        if (ann_prefix(ls[i].ptr, "IP:", &v)) {
            int k;
            cur = -1;
            if (!ann_canon(v, strcspn(v, " \t\r"), canon, sizeof canon)) continue;
            for (k = 0; k < np; k++)
                if (strcmp(ann_cache[pend[k]].ip, canon) == 0) { cur = pend[k]; ann_cache[cur].have = 1; break; }
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Origin-AS:", &v)) {
            ann_cache[cur].asn = atoi(v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Org-Name:", &v)) {
            ann_val(ann_cache[cur].org, sizeof ann_cache[cur].org, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Net-Name:", &v)) {
            ann_val(ann_cache[cur].net, sizeof ann_cache[cur].net, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "AS-Org-Name:", &v)) {
            ann_val(ann_cache[cur].asorg, sizeof ann_cache[cur].asorg, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Prefix:", &v)) {
            ann_val(ann_cache[cur].prefix, sizeof ann_cache[cur].prefix, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "City:", &v)) {
            ann_val(ann_cache[cur].city, sizeof ann_cache[cur].city, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Region:", &v)) {
            ann_val(ann_cache[cur].region, sizeof ann_cache[cur].region, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Country-Code:", &v)) {
            ann_val(ann_cache[cur].cc, sizeof ann_cache[cur].cc, v);
        } else if (cur >= 0 && ann_prefix(ls[i].ptr, "Country:", &v)) {
            ann_val(ann_cache[cur].country, sizeof ann_cache[cur].country, v);
        }
    }
    free(ls);
    free(reply);
}

/* one selected field, coloured on a terminal; returns 1 if something was printed */
static int ann_emit_field(const char *v, int kind, int first)
{
    if (!v || !v[0]) return 0;
    if (!first) putchar(' ');
    if (w_use_color) printf("%s%s%s", w_field_seq(kind), v, WC_RESET); else fputs(v, stdout);
    return 1;
}
/* emit one input line with an annotation after each address */
static void ann_emit_line(const char *s)
{
    size_t pos = 0, ins, last = 0;
    char canon[INET6_ADDRSTRLEN];
    while (ann_next(s, &pos, &ins, canon, sizeof canon)) {
        int idx = ann_find(canon), n = 0;
        const struct ann_ent *e = (idx >= 0) ? &ann_cache[idx] : NULL;
        fwrite(s + last, 1, ins - last, stdout);
        if (w_use_color) printf(" %s[%s", WC_DIM, WC_RESET); else fputs(" [", stdout);
        if (e && e->have) {
            char asn[16] = "", place[200] = "";
            if (e->asn) snprintf(asn, sizeof asn, "AS%d", e->asn);
            if (ann_fields & AF_PLACE) {
                size_t l = 0;
                if (e->city[0])    { snprintf(place + l, sizeof place - l, "%s", e->city); l = strlen(place); }
                if (e->region[0] && strcmp(e->region, e->city) != 0) { snprintf(place + l, sizeof place - l, "%s%s", l ? ", " : "", e->region); l = strlen(place); }
                if (e->country[0]) { snprintf(place + l, sizeof place - l, "%s%s", l ? ", " : "", e->country); l = strlen(place); }
                if (e->cc[0])      snprintf(place + l, sizeof place - l, "%s(%s)", l ? " " : "", e->cc);
            }
            if (ann_fields & AF_ASN)     n += ann_emit_field(asn,        WF_ASN,    n == 0);
            if (ann_fields & AF_ORG)     n += ann_emit_field(e->org,     WF_ORG,    n == 0);
            if (ann_fields & AF_ASORG)   n += ann_emit_field(e->asorg,   WF_ORG,    n == 0);
            if (ann_fields & AF_NET)     n += ann_emit_field(e->net,     WF_LABEL,  n == 0);
            if (ann_fields & AF_PREFIX)  n += ann_emit_field(e->prefix,  WF_PREFIX, n == 0);
            if (ann_fields & AF_CITY)    n += ann_emit_field(e->city,    WF_GEO,    n == 0);
            if (ann_fields & AF_REGION)  n += ann_emit_field(e->region,  WF_GEO,    n == 0);
            if (ann_fields & AF_COUNTRY) n += ann_emit_field(e->country, WF_GEO,    n == 0);
            if (ann_fields & AF_CC)      n += ann_emit_field(e->cc,      WF_GEO,    n == 0);
            if (ann_fields & AF_PLACE)   n += ann_emit_field(place,      WF_GEO,    n == 0);
        }
        if (!n) { if (w_use_color) printf("%s?%s", WC_DIM, WC_RESET); else putchar('?'); }
        if (w_use_color) printf("%s]%s", WC_DIM, WC_RESET); else putchar(']');
        last = ins;
    }
    fputs(s + last, stdout);
}

/* chunked driver: collect -> resolve (batched) -> emit, repeat to EOF */
static void w_annotate_stream(whois_session_params *wsess, FILE *in)
{
    char **lines = malloc(sizeof(char *) * ANN_CHUNK);
    char buf[8192];
    int nl, i;
    if (!lines) return;
    for (;;) {
        int pend[ANN_BATCH], np = 0;
        for (nl = 0; nl < ANN_CHUNK && fgets(buf, sizeof buf, in); ) {
            if ((lines[nl] = strdup(buf))) nl++;
        }
        if (nl == 0) break;
        for (i = 0; i < nl; i++) {                       /* pass 1: new addresses */
            size_t pos = 0, ins;
            char canon[INET6_ADDRSTRLEN];
            while (ann_next(lines[i], &pos, &ins, canon, sizeof canon)) {
                int idx;
                if (ann_find(canon) >= 0) continue;
                if ((idx = ann_add(canon)) < 0) continue;
                pend[np++] = idx;
                if (np == ANN_BATCH) { ann_resolve(wsess, pend, np); np = 0; ann_sort(); }
            }
        }
        if (np) ann_resolve(wsess, pend, np);
        ann_sort();
        for (i = 0; i < nl; i++) { ann_emit_line(lines[i]); free(lines[i]); }   /* pass 2 */
        fflush(stdout);
    }
    free(lines);
}

static int w_display_pwhois_gigo(whois_session_params * wsess, char *user_query)
{
    char *reply;
    char *serv;
    
    if (strlen(wsess->pw_serv) > 0)
        serv = wsess->pw_serv;
    else
        serv = pwhois_server;
    
    if (wsess->w_noisy) {
        if (w_ecolor())
            fprintf(stderr, "Querying '%s%s%s' for: '%s%s%s'\n",
                    w_field_seq(WF_LABEL), serv, WC_RESET,
                    w_field_seq(WF_ASN), user_query, WC_RESET);
        else
            fprintf(stderr, "Querying '%s' for: '%s'\n", serv, user_query);
    }

    reply = w_ask(wsess, serv, user_query, NULL);
    if (!reply) {
        if (wsess->w_noisy) fprintf(stderr,"No reply from %s.\n",serv);
        return -1;
    }
    
    w_print_pwhois_colorized(reply);

    free(reply);
    return 0;
}

#endif

int
w_lookup_all_pwhois_bulk(whois_session_params * wsess, struct ip_list_array *iplist)
{
    
    token_t *responses;
    char *reply;
    const char *bulk_begin = "begin\n";     
    const char *bulk_end = "end\n";         
    char *bulk_ip_query = NULL;
    int i = 0, k = 0, entity_id = 0;
    unsigned int j = 0;
    char *value = NULL;
    
    if (!iplist)
        return -1;
    
    if ((uintptr_t)(*iplist).application) {
        bulk_ip_query = (char *)malloc(((strlen((*iplist).application) * sizeof(char)) +10) + ((strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems));
    } else 
	bulk_ip_query = (char *)malloc(((strlen(appname) * sizeof(char)) +10) + ((strlen(version) + strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems));
    *bulk_ip_query = '\0';
    
    /* clean up the response data set in case the caller doesn't (and we return error) */
    for (i = 0; i < (*iplist).numItems; i++) {
        (*iplist).asn[(i)] = 0;
        memset((*iplist).netName[i],0,sizeof((*iplist).netName[i]));
        memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i]));        
    }
    
    /* prepare the text-string-based query */
    strcat(bulk_ip_query,bulk_begin);
    if ((uintptr_t)(*iplist).application) {
        strcat(bulk_ip_query, "app=\"");
        strcat(bulk_ip_query, (*iplist).application);
        strcat(bulk_ip_query, "\"\n");
    } else {
        strcat(bulk_ip_query, "app=\"");
        strcat(bulk_ip_query, appname);
        strcat(bulk_ip_query, " ");
        strcat(bulk_ip_query, version);
        strcat(bulk_ip_query, "\"\n");
    }
    
    for (i = 0; i < ((*iplist).numItems); i++) {
        strcat(bulk_ip_query, inet_ntoa((*iplist).ipaddr[i]));
        strcat(bulk_ip_query, "\n");
    }
    strcat(bulk_ip_query, bulk_end);
    
    reply = w_ask(wsess, pwhois_server, bulk_ip_query, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server);
            else
                fprintf(stderr,"No reply from %s.\n",pwhois_server);
        }
        /* clean up the response data set in case the caller doesn't */
        for (i = 0; i < (*iplist).numItems; i++)
            (*iplist).asn[(i)] = 0;
        free(bulk_ip_query);
        return -1;
    }
    responses = tokens(reply, "\n");
    
    for(i = 0; responses[i].ptr; i++){
        value = NULL;
        // printf("LINE %d: '%s'\n",i, responses[i].ptr);
        if((value = match_prefix("IP:", responses[i].ptr)) != NULL){
            /* if any keys matched, increment the id of the array */
            if(k > 0){
		entity_id++; 
		k = 0;
	    }
        } else
        if((value = match_prefix("origin-as:", responses[i].ptr)) != NULL) {
            rm_spaces(value);       /* strip out any spaces from the ASN */
            for(j = 0; j < strlen(value); j++) {
                if(!isdigit((unsigned char)value[j])){
                    if (wsess->w_noisy)
                    {
                        if(wsess->logprintfCookie)
                            lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                        else
                            fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                    }
                    break;
                }
            }
            if ((int)strtol(value, (char **)NULL, 10)) {
                (*iplist).asn[(entity_id)] = strtol(value, (char **)NULL, 10);
                k++;
            } else {
                (*iplist).asn[(entity_id)] = 0;
                k++;
            }
        } else if ((value = match_prefix("org-name:", responses[i].ptr))) {
            strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1);
            (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0';
            k++;
        } else if ((value = match_prefix("net-name:", responses[i].ptr))) {
            strncpy((*iplist).netName[entity_id],value,sizeof((*iplist).netName[entity_id])-1);
            (*iplist).netName[entity_id][sizeof((*iplist).netName[entity_id])-1] = '\0';
            k++;
        }
        if(value)
            free(value);
        if ((entity_id+1) > (*iplist).numItems) 
            break;
    }
    
    free(responses);
    free(reply); 
    free(bulk_ip_query); 
    
    return 0;
}

int
w_lookup_all_pwhois_bulk_ext(whois_session_params * wsess, struct ext_ip_list_array *iplist)
{
    
    token_t *responses;
    char *reply;
    const char *bulk_begin = "begin\n";     
    const char *bulk_end = "end\n";         
    char *bulk_ip_query = NULL;
	int i=0;
	unsigned int j=0;
	int k=0;
    int pntcnt;
	int entity_id = 0;
    char *value = NULL;
    
    if (!iplist)
        return -1;
    iplist->geoavailable=0;

    if ((uintptr_t)(*iplist).application) {
        bulk_ip_query = malloc(((strlen((*iplist).application) * sizeof(char)) +10) + ((strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems));
    } else bulk_ip_query = malloc(((strlen(appname) * sizeof(char)) +10) + ((strlen(version) + strlen(bulk_begin) + strlen(bulk_end) + 1) * sizeof(char)) + (16 * (*iplist).numItems));
    bulk_ip_query[0]=0;
    
    /* clean up the response data set in case the caller doesn't (and we return error) */
    for (i = 0; i < (*iplist).numItems; i++) {
        (*iplist).asn[(i)] = 0;
        memset((*iplist).netName[i],0,sizeof((*iplist).netName[i]));
        memset((*iplist).orgName[i],0,sizeof((*iplist).orgName[i]));        
    }
    
    /* prepare the text-string-based query */
    strcat(bulk_ip_query,bulk_begin);
    if ((uintptr_t)(*iplist).application) {
        strcat(bulk_ip_query,"app=\"");
        strcat(bulk_ip_query,(*iplist).application);
        strcat(bulk_ip_query,"\"\n");
    } else {
        strcat(bulk_ip_query,"app=\"");
        strcat(bulk_ip_query,appname);
        strcat(bulk_ip_query," ");
        strcat(bulk_ip_query,version);
        strcat(bulk_ip_query,"\"\n");
    }
    
    for (i = 0; i < ((*iplist).numItems); i++) {
        strcat(bulk_ip_query,inet_ntoa((*iplist).ipaddr[i]));
        strcat(bulk_ip_query,"\n");
    }
    strcat(bulk_ip_query,bulk_end);
    
    reply = w_ask(wsess, pwhois_server, bulk_ip_query, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",pwhois_server);
            else
                fprintf(stderr,"No reply from %s.\n",pwhois_server);
        }
        /* clean up the response data set in case the caller doesn't */
        for (i = 0; i < (*iplist).numItems; i++)
            (*iplist).asn[(i)] = 0;
        free(bulk_ip_query);
        return -1;
    }
    responses = tokens(reply, "\n");
    
    for (i = 0; responses[i].ptr; i++) {
        value = NULL;
        // printf("LINE %d: '%s'\n",i, responses[i].ptr);
        if ((value = match_prefix("IP:", responses[i].ptr)) != NULL) {
            /* if any keys matched, increment the id of the array */
            if (k > 0) { entity_id++; k = 0; }
        } else
        if ((value = match_prefix("origin-as:", responses[i].ptr)) != NULL) {
            rm_spaces(value);       /* strip out any spaces from the ASN */
            for (j = 0; j < strlen(value); j++) {
                if (!isdigit((unsigned char)value[j])) {
                    if (wsess->w_noisy)
                    {
                        if(wsess->logprintfCookie)
                            lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                        else
                            fprintf(stderr,"Parse error at \'%c\': non-numeric value at position %d of %s).\n",value[j],j,value);
                    }
                    break;
                }
            }
            if(atoi(value)) {
                (*iplist).asn[(entity_id)] = atoi(value);
                k++;
            } else {
                (*iplist).asn[(entity_id)] = 0;
                k++;
            }
        } else if ((value = match_prefix("as-org-name-source:", responses[i].ptr))) {
            strncpy((*iplist).asOrgNameSource[entity_id],value,20);
            k++;
        } else if ((value = match_prefix("org-name-source:", responses[i].ptr))) {
            strncpy((*iplist).orgNameSource[entity_id],value,20);
            k++;
        } else if ((value = match_prefix("net-name-source:", responses[i].ptr))) {
            strncpy((*iplist).netNameSource[entity_id],value,20);
            k++;
        } else if ((value = match_prefix("prefix:", responses[i].ptr))) {
            strncpy((*iplist).prefix[entity_id],value,sizeof((*iplist).prefix[entity_id])-1);
            (*iplist).prefix[entity_id][sizeof((*iplist).prefix[entity_id])-1] = '\0';
            k++;
        } else if ((value = match_prefix("org-name:", responses[i].ptr))) {
            strncpy((*iplist).orgName[entity_id],value,sizeof((*iplist).orgName[entity_id])-1);
            (*iplist).orgName[entity_id][sizeof((*iplist).orgName[entity_id])-1] = '\0';
            k++;
        } else if ((value = match_prefix("net-name:", responses[i].ptr))) {
            strncpy((*iplist).netName[entity_id],value,sizeof((*iplist).netName[entity_id])-1);
            (*iplist).netName[entity_id][sizeof((*iplist).netName[entity_id])-1] = '\0';
            k++;
        } else if ((value = match_prefix("longitude:", responses[i].ptr))) {
            rm_spaces(value);       /* strip out any spaces from the LONGITUDE */
            for (j = 0, pntcnt = 0; j < strlen(value); j++) {
                if(value[j]=='.')
                    pntcnt++;
                if (!isdigit((unsigned char)value[j]) && (value[j]!='.' || pntcnt>1) && !(j==0 && value[j]=='-')) {
                    if (wsess->w_noisy)
                    {
                        if(wsess->logprintfCookie)
                            lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value);
                        else
                            fprintf(stderr,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value);
                    }
                    break;
                }
            }
            (*iplist).longitude[(entity_id)]=atof(value);
            iplist->geoavailable++;
            k++;
        } else if ((value = match_prefix("latitude:", responses[i].ptr))) {
            rm_spaces(value);       /* strip out any spaces from the LONGITUDE */
            for (j = 0, pntcnt = 0; j < strlen(value); j++) {
                if(value[j]=='.')
                    pntcnt++;
                if (!isdigit((unsigned char)value[j]) && (value[j]!='.' || pntcnt>1) && !(j==0 && value[j]=='-')) {
                    if (wsess->w_noisy)
                    {
                        if(wsess->logprintfCookie)
                            lft_printf(wsess->logprintfCookie,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value);
                        else
                           fprintf(stderr,"Parse error at \'%c\': can't parse value at position %d of %s).\n",value[j],j,value);
                    }
                    break;
                }
            }
            (*iplist).latitude[(entity_id)]=atof(value);
            iplist->geoavailable++;
            k++;
        } else if ((value = match_prefix("city:", responses[i].ptr))) {
            strncpy((*iplist).city[entity_id],value,sizeof((*iplist).city[entity_id])-1);
            (*iplist).city[entity_id][sizeof((*iplist).city[entity_id])-1] = '\0';
            iplist->geoavailable++;
            k++;
        } else if ((value = match_prefix("country-code:", responses[i].ptr))) {
            strncpy((*iplist).country_code[entity_id],value,sizeof((*iplist).country_code[entity_id])-1);
            (*iplist).country_code[entity_id][sizeof((*iplist).country_code[entity_id])-1] = '\0';
            k++;
        } else if ((value = match_prefix("country:", responses[i].ptr))) {
            strncpy((*iplist).country[entity_id],value,sizeof((*iplist).country[entity_id])-1);
            (*iplist).country[entity_id][sizeof((*iplist).country[entity_id])-1] = '\0';
            iplist->geoavailable++;
            k++;
        } else if ((value = match_prefix("region:", responses[i].ptr))) {
            strncpy((*iplist).state[entity_id],value,sizeof((*iplist).state[entity_id])-1);
            (*iplist).state[entity_id][sizeof((*iplist).state[entity_id])-1] = '\0';
            iplist->geoavailable++;
            k++;
        }
        if(value)
            free(value);
        if ((entity_id+1) > (*iplist).numItems) 
            break;
    }
    
    free(responses); free(reply); free(bulk_ip_query); 
    
    return 0;
}

static char *
w_lookup_netname_other(whois_session_params * wsess, 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(wsess, serv, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv);
            else
                fprintf(stderr,"No reply from %s.\n",serv);
        }
        return NULL;
    }
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        char *value=NULL;
        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;
            }
            free(value);
        } else
        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(whois_session_params * wsess, 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(wsess, arin_server, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server);
            else
                fprintf(stderr,"No reply from %s.\n",arin_server);
        }
        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(wsess, arin_server, na, NULL);
    free(na);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server);
            else
                fprintf(stderr,"No reply from %s.\n",arin_server);
        }
        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(wsess, addr, ripe_server);
        }
        
        if (ans && !strncmp(ans, "APNIC", 5)) {
            other = w_lookup_netname_other(wsess, addr, apnic_server);
        }
        
        if (other) {
            char *together = malloc(strlen(ans) + strlen(other) + 2);
            together[0]=0;
            strcpy(together, ans);
            strcat(together, "/");
            strcat(together, other);
            free(ans);
            ans = together;
        }
    }

    return ans;
}

static char *
w_lookup_orgname_other(whois_session_params * wsess, 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(wsess, serv, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",serv);
            else
                fprintf(stderr,"No reply from %s.\n",serv);
        }
        return NULL;
    }
    ls = tokens(reply, "\n");
    
    for (i = 0; ls[i].ptr; i++) {
        char *value=NULL;
        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;
            }
            free(value);
        }else
        if (use_this && (value = match_prefix("orgname:", ls[i].ptr))) {
            if (ans)
                free(ans);
            ans = value;
        }
    }
    
    if (!ans) {
        for (i = 0; ls[i].ptr; i++) {
            char *value;
            if (use_this && (value = match_prefix("descr:", ls[i].ptr))) {
                if (ans)
                    free(ans);
                ans = value;
                break;
            }
        }
    }
    
    free(ls); free(reply); if (b) free(b);
    return ans;
}

char *
w_lookup_orgname(whois_session_params * wsess, 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(wsess, arin_server, addr, NULL);
    if (!reply) {
        if (wsess->w_noisy)
        {
            if(wsess->logprintfCookie)
                lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server);
            else
                fprintf(stderr,"No reply from %s.\n",arin_server);
        }
        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;
        } 
    }
    
    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(wsess, arin_server, na, NULL);
        free(na);
        if (!reply) {
            if (wsess->w_noisy)
            {
                if(wsess->logprintfCookie)
                    lft_printf(wsess->logprintfCookie,"No reply from %s.\n",arin_server);
                else
                    fprintf(stderr,"No reply from %s.\n",arin_server);
            }
            return NULL;
        }

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

    }
        for (i = 0; ls[i].ptr; i++) {
            char *value;
            if ((value = match_prefix("orgname:", ls[i].ptr))) {
                if(ans)
                    free(ans);
                ans = value;
                break;
            }
        }

        free(ls); free(reply);

        {
            char *other = NULL;
            if (ans && strstr(ans, "RIPE")) {
                other = w_lookup_orgname_other(wsess, addr, ripe_server);
            }
            
            if (ans && (!strncmp(ans, "APNIC", 5) || strstr(ans, "Asia Pacific Net") )) {
                other = w_lookup_orgname_other(wsess, addr, apnic_server);
            }
            
            if (other) {
                /*            char *together = malloc(strlen(ans) + strlen(other) + 4);
                strcpy(together, other);
                strcat(together, " (");
                strcat(together, ans);
                strcat(together, ")");
                */
                free(ans);
                ans = other;
            }
        }

        return ans;
}

#ifdef STANDALONE
/*---------------------------------------------------------------------------*/
void lft_printf(lft_session_params * sess, const char *templ, ...)
{
  va_list ap;
  char buf[1024];

  (void)sess;

  va_start (ap, templ);
  vsprintf(buf, templ, ap);
  va_end (ap);
  printf("%s",buf);
}
/*---------------------------------------------------------------------------*/
static void
usage (char *prog)
{
    fprintf (stderr,
             "\nWhoB - version %s %s\n\n"
             "     - a likable whois client from the Prefix WhoIs project\n"
             "                                visit http://www.pwhois.org\n"
             "\nUsage: %s [-g] [<options>] <target>\n"
             "\nMainstream Options:\n"
             "  -g         Disable GIGO mode; enable other options then submit query\n"
             "  -R         Display the Origin-AS on file at the RADB/IRR also\n"
             "  -p         Display the AS-Path learned by the pWhoIs server (pWhoIs-only)\n"
             "  -n         Display the network name on file at the registrar\n"
             "  -t         Display the date the route was last updated (pWhoIs-only)\n"
             "  -u         Display the date the route was last updated in GMT (pWhoIs-only)\n"
             "  -o         Disable display of the organization\'s name on file at the registrar\n"
             "\nAdvanced Options:\n"             
             "  -h host    Specify your own pWhoIs-compatible server to query\n"
             "  -f file    Read from 'file' (or '-' as stdin) as bulk input to pWhoIs\n"
             "  -c         Use Cymru\'s whois server instead of pWhoIs\n"
             "  -r         Use RIPE NCC\'s RIS whois server instead of pWhoIs\n"
             "\nPrefix WhoIs Advanced Options:\n"
             "  -A         Display all routes transiting the target ASN (pWhoIs-only)\n"
             "  -a         Display all routes announced by the target ASN (pWhoIs-only)\n"
             "  -P         Display all routes respective to a target prefix (pWhoIs-only)\n"
             "  -N         Display all networks registered to the target ASN (pWhoIs-only)\n"
             "  -O         Display organizational contact info for the target ASN (pWhoIs-only)\n"
             "\nVerbosity Options and Status:\n"
             "  -s         Display the version and status of the pWhoIs server (pWhoIs-only)\n"
             "  -V         Display verbose/debug output.  Use more \'V\'s for additional detail\n"
             "  -v         Display WhoB\'s version information and exit\n"
             "  --color    Force colorized output even when not writing to a terminal\n"
             "  --no-color Disable colorized output (default: auto-detect the terminal)\n"
             "  -e, --enrich  Annotate IP addresses in text from stdin with pWhoIs ASN and org\n"
             "  --annotate[=FILE]  Same as -e; =FILE reads a file instead of stdin\n"
             "  -F, --fields=LIST  Fields for -e: asn,org,asorg,net,prefix,city,region,country,cc,place,all\n"
             "                (default asn,org; a leading + or - adjusts the default)\n"
             "  me|whoami  Display the public IP address of this host\n"
             "\n"
             ,version,version_date,prog);
    fprintf(stderr,"Example:     %s -gnp 1.2.3.4\n",prog);
    fprintf(stderr,"Returns:     IP Address | ASN-by-prefix (prefix) | AS-Path | NetName | OrgName\n\n"); 
    exit(EXIT_FAILURE);
}

static void
show_startup_msgs (whois_session_params * wsess)
{
    if (wsess->w_noisy) {
        fprintf(stderr,"WhoB version %s %s firing up...",version,version_date);
        if (wsess->w_noisy > 1) fprintf(stderr," (verbosity level %d)\n",wsess->w_noisy); else printf ("\n");
        if (wsess->w_noisy > 1) {
            const char *L = "", *R = "";
            if (w_ecolor()) { L = w_field_seq(WF_LABEL); R = WC_RESET; }
            fprintf(stderr,"Data sources:");
            if ((strlen(wsess->pw_serv) > 0) && (!use_cymru || read_from_file)) fprintf(stderr," %s%s%s (pWhoIs)",L,wsess->pw_serv,R);
            else if ((!use_cymru || read_from_file) && !use_riswhois) fprintf(stderr," %s%s%s (pWhoIs)",L, pwhois_server,R);
            else if (!use_cymru) fprintf(stderr," %s%s%s (RIPE NCC)",L, ripe_ris_server,R);
            if (use_cymru && !read_from_file) fprintf(stderr," %s%s%s (Cymru)",L,cymru_server,R);
            if (display_radb_as) fprintf(stderr,", %s%s%s (RADB)",L,radb_server,R);
            fprintf(stderr,".\n");
            if (read_from_file) {
                if (cymrufromfile) fprintf(stderr,"Using Cymru for bulk file resolution.\n");
                else if (riswhoisfromfile) fprintf(stderr,"Using RIPE NCC for bulk file resolution.\n");
                else fprintf(stderr,"Using Prefix WhoIs for bulk file resolution.\n");
            }
        }
        if (show_routes_byprefix == 1 && (show_routes_byasn == 1 || show_routes_bytransitasn == 1)) {
            fprintf(stderr,"You may only perform routeviews one at a time.  Using by-ASN.\n");
            show_routes_byprefix = 0;
        } 
    }
}

int main(int ac, char **av)
{
    struct addrinfo ai_hints, *ai_res;
    char *addr = NULL;
    char addrbuf[INET6_ADDRSTRLEN];
    struct in_addr in, pws;
    int ch;
    int user_asn = 0;
    char user_asn_buf[12];  /* fits full 32-bit ASN (10 digits) + NUL */
    whois_session_params * wsess;
    
    memset(&hostname, 0, sizeof(hostname));
    
    wsess = w_init();
    setbuf(stdout, NULL);
        
    {
    static struct option w_long_options[] = {
        { "color",    no_argument, 0, OPT_W_COLOR    },
        { "no-color", no_argument, 0, OPT_W_NO_COLOR },
        { "no-colour",no_argument, 0, OPT_W_NO_COLOR },
        { "annotate", optional_argument, 0, OPT_W_ANNOTATE },
        { "enrich",   optional_argument, 0, OPT_W_ANNOTATE },
        { "fields",   required_argument, 0, OPT_W_FIELDS },
        { 0, 0, 0, 0 }
    };
    int w_optidx = 0;
    while ((ch = getopt_long (ac, av, "AaCcefgNnOopPRrstuVvh:w:F:",
                              w_long_options, &w_optidx)) != EOF)
        switch (ch) {
            case OPT_W_COLOR:
                w_color_mode = 1;
                break;
            case OPT_W_NO_COLOR:
                w_color_mode = 2;
                break;
            case 'e':                       /* -e / --enrich / --annotate are one feature */
            case OPT_W_ANNOTATE:
                annotate_mode = 1;
                use_gigo = 0;
                if (optarg) snprintf(annotate_file, sizeof annotate_file, "%s", optarg);
                break;
            case 'F':
            case OPT_W_FIELDS:
                if (!ann_parse_fields(optarg)) {
                    fprintf(stderr, "%s: -F expects a comma list of asn,org,asorg,net,prefix,city,region,country,cc,place,all (+/- adjust the default)\n", appname);
                    exit(EXIT_FAILURE);
                }
                break;
            case 'a':
                use_gigo = 0;
                show_routes_byasn = 1;
                go_interactive = 1;
                break;
            case 'A':
                use_gigo = 0;
                show_routes_bytransitasn = 1;
                go_interactive = 1;
                break;
            case 'N':
                use_gigo = 0;
                show_networks_byasn = 1;
                go_interactive = 1;
                break;
            case 'O':
                use_gigo = 0;
                show_contacts_byasn = 1;
                go_interactive = 1;
                break;
            case 's':
                use_gigo = 0;
                show_server_status = 1;
                go_interactive = 1;
                break;
            case 'P':
                use_gigo = 0;
                show_routes_byprefix = 1;
                go_interactive = 1;
                break;
            case 'v':
                usage(av[0]);
                break;
            case 'u':   /* show all times in UTC */
#if defined(sun)
                if(putenv("TZ=GMT0") == -1) {
                    fprintf(stderr, "%s: Unable to set TZ to UTC.",appname);
                }                
#else
#if !defined(WIN32) && !defined(_WIN32)
                if (setenv("TZ", "GMT0", 1) == -1) {
                    fprintf(stderr, "%s: Unable to set TZ to UTC.",appname);
                }
#endif
#endif
                show_cache_date = 1;
                break;
            case 't':
                show_cache_date = 1;
                break;
            case 'w':
                if (strlen(optarg) > max_hostname_input) {
                    fprintf(stderr,"Sorry, the server name you supplied was unreasonably long.\n");
                    exit(EXIT_FAILURE);
                }
                if (inet_aton(optarg, &pws)) {
                    strncpy(wsess->pw_serv, optarg, strlen(optarg));
                } else {
                    memset(&ai_hints, 0, sizeof(ai_hints));
                    ai_hints.ai_family   = AF_INET;
                    ai_hints.ai_socktype = SOCK_STREAM;
                    if (getaddrinfo(optarg, NULL, &ai_hints, &ai_res) != 0) {
                        fprintf(stderr,"Sorry, I cannot resolve \'%s\' to use as your pWhoIs server.\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                    memcpy(&pws, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(pws));
                    freeaddrinfo(ai_res);
                    strncpy(wsess->pw_serv,inet_ntoa(pws),strlen(inet_ntoa(pws)));
                }
                break;
            case 'h':
                if (strlen(optarg) > max_hostname_input) {
                    fprintf(stderr,"Sorry, the server name you supplied was unreasonably long.\n");
                    exit(EXIT_FAILURE);
                }
                if (inet_aton(optarg, &pws)) {
                    strncpy(wsess->pw_serv,optarg,strlen(optarg));
                } else {
                    memset(&ai_hints, 0, sizeof(ai_hints));
                    ai_hints.ai_family   = AF_INET;
                    ai_hints.ai_socktype = SOCK_STREAM;
                    if (getaddrinfo(optarg, NULL, &ai_hints, &ai_res) != 0) {
                        fprintf(stderr,"Sorry, I cannot resolve \'%s\' to use as your pWhoIs server.\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                    memcpy(&pws, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr, sizeof(pws));
                    freeaddrinfo(ai_res);
                    strncpy(wsess->pw_serv,inet_ntoa(pws),strlen(inet_ntoa(pws)));
                }
                break;
            case 'c':
                use_cymru = 1;
                /* cymrufromfile = 1; */      /* Use pwhois Cymru compatibility mode by default */
                break;
            case 'C':
                use_cymru = 1;
                cymrufromfile = 1;
                break;
            case 'n':
                display_netname = 1;
                go_interactive = 1;
                break;
            case 'r':
                use_riswhois = 1;
                riswhoisfromfile = 1;
                break;
            case 'R':
                display_radb_as = 1;
                go_interactive = 1;
                break;
            case 'o':
                display_orgname = 0;
                go_interactive = 1;
                break;
            case 'p':
                display_aspath = 1;
                go_interactive = 1;
                break;
            case 'f':
                use_gigo = 0;
                read_from_file = 1;
                break;
            case 'V':
                wsess->w_noisy++;
                break;
            case 'g':
                use_gigo = 0;
                go_interactive = 1;
                break;
            default:
                usage (av[0]);
        }
    }   /* end w_long_options scope */

    /* Resolve stdout colourization exactly once, now that options are parsed
     * and before any output.  Self-gates to an interactive terminal. */
    w_detect_color();

    /* --annotate: a pure filter — enrich addresses in the text and exit. */
    if (annotate_mode) {
        FILE *in = stdin;
        if (annotate_file[0] && strcmp(annotate_file, "-") != 0) {
            in = fopen(annotate_file, "r");
            if (!in) {
                fprintf(stderr, "%s: unable to open '%s' for reading.\n", appname, annotate_file);
                exit(EXIT_FAILURE);
            }
        }
        w_annotate_stream(wsess, in);
        if (in != stdin) fclose(in);
        exit(EXIT_SUCCESS);
    }

    /* Catch hostname input without any arguments */
    if ((ac - optind) > 0)
        go_interactive = 1;
                            
    if (go_interactive > 0) {
    /*  Quickly check stdin for input to avoid David having to type   */
    /*  '-f -' to pipe into to whob, even if it has args :-)          */
        fd_set rfds;
        struct timeval timev;
        int selretval;
     
        /* Watch stdin (fd 0) briefly for input. */
        FD_ZERO(&rfds);
        FD_SET(0, &rfds);
        
        timev.tv_sec = 0;
        timev.tv_usec = 100;
     
        selretval = select(1, &rfds, NULL, NULL, &timev);

        if (selretval > 0) {
            /* select() also reports fd 0 readable at EOF / on a closed or
             * redirected-from-empty stdin (e.g. `whob target </dev/null`, or
             * any script/cron run with stdin redirected).  Peek one byte to
             * tell real piped data from EOF: only switch to stdin-bulk mode
             * when data is actually present, otherwise fall through and honor
             * the argv target.  The peek is on the same stdin FILE* the bulk
             * reader consumes, and ungetc() restores the byte, so nothing is
             * lost.  Preserves `echo x | whob [target]` and `-f -`. */
            int peek = getc(stdin);
            if (peek != EOF) {
                ungetc(peek, stdin);
                /* There's input in them there hills, treat it as the input file */
                use_gigo = 0;
                read_from_file = 1;
                use_stdin = 1;
            }
        }
        /*
          else
            if (wsess->w_noisy) fprintf(stderr,"No input found on stdin.\n");
        */
    } else if (read_from_file < 1) {
        /* No indication we should be interactive based on arguments, so wait for STDIN */
        use_gigo = 0;
        read_from_file = 1;
        use_stdin = 1;
    }
            
    if (((ac - optind) < 1) && (show_server_status != 1) && (go_interactive > 0))
        usage (av[0]);
        
    /* Show the verbose startup information if verbosity is enabled/requested */
    show_startup_msgs(wsess);
                    
    if (show_server_status == 1) {
        w_display_pwhois_version(wsess);
        exit(EXIT_FAILURE);
    } else if ((read_from_file > 0 && use_stdin < 1) || go_interactive > 0) { 
    
        if (strlen(av[optind]) > max_hostname_input) {        
            fprintf(stderr,"Sorry, the subject name you supplied was unreasonably long.\n");
            exit(EXIT_FAILURE);
        } else {
            strncpy(hostname,av[optind],strlen(av[optind]));
            optind++;
        }
    }
    
    if (read_from_file) {
        if (riswhoisfromfile) 
            w_display_bulkfromfile_riswhois(wsess, hostname);
        else if (cymrufromfile)
            w_display_bulkfromfile_cymru(wsess, hostname);
        else 
            w_display_bulkfromfile_pwhois(wsess, hostname);
        exit(EXIT_SUCCESS);
    }
    
    if (use_gigo > 0) {

        if (strncmp(hostname,"me",2) == 0 || strncmp(hostname,"whoami",6) == 0) {
            w_display_myaddress(wsess);
            exit(EXIT_SUCCESS);
        } else {
           w_display_pwhois_gigo(wsess, hostname);
           exit(EXIT_SUCCESS);
	}
    }
    
    if ((show_routes_byasn || show_routes_bytransitasn || show_contacts_byasn || show_networks_byasn) && (strlen(hostname) <= 10) && atoi(hostname) && stricontains(hostname,".") < 1) {
        user_asn = atoi(hostname);
        if (wsess->w_noisy > 1) fprintf(stderr,"Using user-supplied ASN %d for lookup.\n",user_asn);
    } else if (show_routes_byprefix == 1) {
        printf("Displaying all routes for prefix %s.\n",hostname);
    } else {

	if (strncmp(hostname,"me",2) == 0 || strncmp(hostname,"whoami",6) == 0) {
	    w_display_myaddress(wsess);
	    exit(EXIT_SUCCESS);
	}
        
        if (inet_aton(hostname, &in)) {
            addr = hostname;
        } else if (strchr(hostname, ':')) {
            /* IPv6 literal: pass the text form through to the lookup */
            addr = hostname;
        } else {
            /* Resolve as either family; a v6 answer is used verbatim */
            memset(&ai_hints, 0, sizeof(ai_hints));
            ai_hints.ai_family   = AF_UNSPEC;
            ai_hints.ai_socktype = SOCK_STREAM;
            if (getaddrinfo(hostname, NULL, &ai_hints, &ai_res) != 0) {
                fprintf(stderr,"Sorry, I cannot resolve \'%s\'\n", hostname);
                exit(EXIT_FAILURE);
            }
            if (ai_res->ai_family == AF_INET6)
                inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ai_res->ai_addr)->sin6_addr,
                          addrbuf, sizeof(addrbuf));
            else
                inet_ntop(AF_INET, &((struct sockaddr_in *)ai_res->ai_addr)->sin_addr,
                          addrbuf, sizeof(addrbuf));
            freeaddrinfo(ai_res);
            addr = addrbuf;
        }
    }
    
    if (show_routes_byasn) {
        if (user_asn > 0) {
            printf("Displaying all routes whose Origin-AS is %d.\n", user_asn);
            snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn);
            w_display_rvbyasn_pwhois(wsess, user_asn_buf);
        } else {
            w_lookup_all_pwhois(wsess, addr);
            if (atoi(wsess->consolidated_asn)) {
                printf("Displaying all routes whose Origin-AS is %s.\n", wsess->consolidated_asn);
                w_display_rvbyasn_pwhois(wsess, wsess->consolidated_asn);
            } else {
                printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr);
            }
        }
    } else if (show_routes_bytransitasn) {
        if (user_asn > 0) {
            printf("Displaying all routes whose Transit-AS is %d.\n", user_asn);
            snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn);
            w_display_rvbytransitasn_pwhois(wsess, user_asn_buf);
        } else {
            w_lookup_all_pwhois(wsess, addr);
            if (atoi(wsess->consolidated_asn)) {
                printf("Displaying all routes whose Transit-AS is %s.\n", wsess->consolidated_asn);
                w_display_rvbytransitasn_pwhois(wsess, wsess->consolidated_asn);
            } else {
                printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr);
            }
        }
    } else if (show_networks_byasn) {
        if (user_asn > 0) {
            printf("Displaying all networks registered to the Origin-AS %d.\n", user_asn);
            snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn);
            w_display_networksbyasn_pwhois(wsess, user_asn_buf);
        } else {
            w_lookup_all_pwhois(wsess, addr);
            if (atoi(wsess->consolidated_asn)) {
                printf("Displaying all networks registered to the Origin-AS %s.\n", wsess->consolidated_asn);
                w_display_networksbyasn_pwhois(wsess, wsess->consolidated_asn);
            } else {
                printf("Sorry, unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr);
            }
        }    
    } else if (show_contacts_byasn) {
        if (user_asn > 0) {
            printf("Displaying all contact info on file for Origin-AS %d.\n", user_asn);
            snprintf(user_asn_buf,sizeof(user_asn_buf),"%d",user_asn);
            w_display_contactsbyasn_pwhois(wsess, user_asn_buf);
        } else {
            w_lookup_all_pwhois(wsess, addr);
            if (atoi(wsess->consolidated_asn)) {
                printf("Displaying all contact info on file for Origin-AS %s.\n", wsess->consolidated_asn);
                w_display_contactsbyasn_pwhois(wsess, wsess->consolidated_asn);
            } else {
                printf("Unable to resolve the ASN for %s (%s) at this time.\n",hostname,addr);
            }
        }                
    } else if (show_routes_byprefix) {
        w_display_rvbyprefix_pwhois(wsess, hostname);
    } else {
        
        printf("%s | Searching...", addr);
        
        if (use_cymru) {
            printf("\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("origin-as %d ", w_lookup_as_cymru(wsess, addr));
        } else if (use_riswhois) {
            w_lookup_all_riswhois(wsess, addr);
            printf("\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("origin-as %s (%s) ", wsess->consolidated_asn, wsess->consolidated_route);
        } else {
            w_lookup_all_pwhois(wsess, addr);
            printf("\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("origin-as %s (%s) ", wsess->consolidated_asn, wsess->consolidated_route);
            if (show_cache_date && (strlen(wsess->tbuf) > 0))
                printf("| %s ", wsess->tbuf);
        }
        
        if (display_radb_as) 
            printf("| radb-as %d ", w_lookup_as(wsess, addr));
        
        if ((display_aspath) && (!use_cymru) && (!use_riswhois))
            printf("| as-path %s ", wsess->consolidated_asp);
        
        if (display_netname) {
            if (use_cymru) {
                printf("| %s ", w_lookup_netname(wsess, addr));
            } else if (use_riswhois) {
                if (!display_orgname) 
                    printf("| %s ", wsess->consolidated_netname);
            } else {
                printf("| %s ", wsess->consolidated_netname);
            }
        }
        
        if (display_orgname) {
            if (use_cymru) {
                printf("| %s ", w_lookup_orgname(wsess, addr));
            } else {
                printf("| %s ", wsess->consolidated_orgname);
            }
        }
        printf("\n");
    }
    w_close(wsess);

    exit(EXIT_SUCCESS);
}
#endif

