[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: How do I check who is at an IP address?



On 17 Mar 2000, at 2:00, Barrie Bremner wrote:

>  Someone recently ran a portscan on me...and I`m interested to find out
> who `owns` the IP address that it came from.

I've got a tool (written, IIRC, by Ronald F. Guilmette, but I could be 
wrong) called ipw that searches the databases of the IP address registries  
until it finds a match.  The source should be attached.  There's no 
guarantee that the information is up-to-date, though - there are way too 
many less-than-clueful network admins out there, and some who actively 
don't care.

Cheers

Richard



/*
NAME
	ipw - get IP address whois information

SYNOPSIS
	ipw [-a] [-n] [-c] [-C] [-t] [-v] [-T secs] address
	ipw [-a] [-n] [-c] [-C] [-t] [-v] [-T secs] host
	ipw [-a] [-n] [-c] [-C] [-t] [-v] [-T secs] handle

DESCRIPTION
	The ipw program attempts to obtain the most relevant IP address regis-
	tration record for a given IP address.  It does so by trying each of
	several major WHOIS servers, in turn, until it finds a relevant record.

	The WHOIS servers that are consulted for IP address registration
	records are:

		whois.arin.net
		whois.ripe.net
		whois.apnic.net
		whois.aunic.net

	If any of these contains a relevant registration record for the given
	IP address, that record will be printed to stdout.  By default, the
	entire registration record is printed to stdout, unless any combination
	of the -a, -n, or -t options are given; in which case only the specific
	information requested is displayed.

	If the case of ARIN registration records, if there are multiple regis-
	tration records covering address ranges which include the given IP
	address, then the record relating to the numerically smallest such
	IP address range is selected and then printed to stdout.

	Note that ``handle'' may be prefixed with "ARIN:", "RIPE:", "APNIC:",
	or "AUNIC:" (case sensitive) in order to manually select a whois
	server when searching by nic handle.  Normally, each of the whois
	servers is consulted consecutively until a match is found for a given
	nic handle, because a handle by itself does not include enough
	information to automatically select the most appropriate server.
	These prefixes are included in the output generated by the -n option.

	The -a option selects IP address range mode.  In this mode, the
	smallest enclosing address range is printed to stdout, rather than
	the entire registration record.

	The -n option selects nic-handle mode, where the "handle", or name,
	for the specified netblock is printed to stdout.  The -N option also
	selects nic-handle mode, but generates a prefix to the handle that
	indicates which registry the handle belongs to (see above).

	The -c option selects contacts mode.  In this mode, the relevant
	contact E-mail addresses are printed to stdout, rather than the entire
	registration record.  If there is more than one contact E-mail address
	in the relevant registration record, then sequential addresses will
	be separated by a comma and a space on stdout.

	The -C option is just like the -c option, except that the block con-
	tact E-mail addresses are output one per line, rather than all on a
	single line separated by commas.

	The -t option is present only for reasons of backward compatability.
	It has the exact same effect as the -c option described above.

	The -T option may be used to adjust the timeout period (in seconds)
	used when attempting to connect to the various WHOIS servers.  The
	default timeout used when no -t option is specified is 0, which is
	treated as actually representing infinity (i.e. no timeout).  Note
	however that the underlying TCP protocol may generate a timeout in
	some cases.

NOTES
	A valid Internet hostname may be given in place of the IP address
	argument, in which case that hostname will be looked-up using DNS
	and the registration record search will be applied to the first
	registered IP address associated with that hostname.

	There are many valid IP addresses for which no relevant registration
	records exist.  For example, addresses in the 10.0.0.0/8 address
	block and addresses in the 192.168/16 address block have no relevant
	registration records.  There are many other such ranges.

	Ideally, when the input is an ARIN, RIPE, or APNIC handle, we should
	check to see if it has a prefix or suffix that might tip us off as
	to which of these three registries we should look up the handle in
	first.  Normally, we will attempt lookups in ARIN, then RIPE, and then
	APNIC, but the following handle suffixes and prefixes could help us
	to avoid many pointless lookups:

	RIPE suffixes and prefixes:

		*-RIPE
		*-NO
		AT-*
		SE-*
		FR-*
		DE-*
		IT-*
		RU-*
		SK-*

	APNIC suffixes:

		*-AP
		*-JP
		*-AU	(Data actually in the AUNIC!)
		*-TW
		*-CN
		*-NZ
		*-TH
		*-MY
		*-MN
		*-ID
		*-HK
		*-SG

RETURN VALUE
	Ipw will exit with a zero (0) status code if all goes well, or with a
	one (1) if no relevant registration records for the given IP address
	were found, or two (2) if there were any sort of internal or communica-
	tions errors.

VERSION
	3.3a

AUTHOR
	Ronald F. Guilmette <rfg [at] monkeys.com>

	Contributions by Marty Bower <marty [at] mjhb.marina-del-rey.ca.us>
	mjhb $Id: ipw.c,v 1.13.2.9 1999/02/04 06:41:13 marty Exp $

COPYRIGHT
	Copyright (c) 1998 Ronald F. Guilmette; All rights reserved.
*/


#include <sys/types.h>
#if defined(WIN32)
#include <winsock.h>
#include <io.h>
#else /* !defined(WIN32) */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <unistd.h>
#include <stdarg.h>
#endif	/* defined(WIN32) */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <setjmp.h>
#include <signal.h>
#include <limits.h>

static char const tcp[] = "tcp";
static char const whois_service[] = "whois";

static char const arin_server[] = "whois.arin.net";
static char const ripe_server[] = "whois.ripe.net";
static char const apnic_server[] = "whois.apnic.net";
static char const aunic_server[] = "whois.aunic.net";

static char const arin_handle[] = "ARIN:";
static char const ripe_handle[] = "RIPE:";
static char const apnic_handle[] = "APNIC:";
static char const aunic_handle[] = "AUNIC:";

#ifdef __cplusplus
#define argname(x)
#else
#define argname(x) x
#endif

#if !defined(__GNUC__) && !defined(__GNUG__)
#define __attribute__(x)
#endif

#if defined(SunOS4)
extern int printf (const char *, ...);
extern int fprintf (FILE *, const char *, ...);
extern int socket (int, int, int);
extern int connect (int, struct sockaddr *, int);
extern int _filbuf (FILE *);
extern int _flsbuf (int, FILE *);

extern char *sys_errlist[];

static char *
strerror (int err)
{
  return sys_errlist[err];
}
#endif /* defined(SunOS4) */

typedef enum Bool { False, True } Bool;

typedef struct in_addr in_addr;
  
typedef struct in_addr_range { 
    in_addr     start;
    in_addr     end;
} in_addr_range;

static char *pname;
static Bool contacts1_mode = False;
static Bool contacts2_mode = False;
static Bool verbose_mode = False;
static Bool addr_mode = False;
static Bool handle_mode = False;
static Bool add_handle_prefixes = False;
static jmp_buf recovery;
static struct protoent *protocol;
static struct servent *service;
static unsigned timeout = 0; /* Default */

#if !defined(RANGE_FUNC)
/* Getopt & inet_aton code borrowed from BSD; ANSIfied and also reformatted 
   to meet GNU coding conventions.  Also modified to meet RFG coding style. */

/*
 * Copyright (c) 1987, 1993, 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

static int my_opterr = 1;	/* if error message should be printed */
static int my_optind = 1;	/* index into parent argv vector */
static int my_optopt;		/* character checked for validity */
static int optreset;		/* reset getopt */
static char *my_optarg;		/* argument associated with option */

#define	BADCH	(int)'?'
#define	BADARG	(int)':'
#define	EMSG	""

/*
 * getopt --
 *      Parse argc/argv argument vector.
 */
static int
my_getopt (register int const nargc, register char *const *const nargv,
	   register const char *const ostr)
{
  static char *place = EMSG;	/* option letter processing */
  register char *oli = NULL;	/* option letter list index */

  if (optreset || ! *place)
    {				/* update scanning pointer */
      optreset = 0;
      if (my_optind >= nargc || *(place = nargv[my_optind]) != '-')
	{
	  place = EMSG;
	  return -1;
	}
      if (place[1] && * ++place == '-')
	{			/* found "--" */
	  ++my_optind;
	  place = EMSG;
	  return -1;
	}
    }				/* option letter okay? */
  if ((my_optopt = (int) *place++) == (int) ':'
      || ! (oli = strchr (ostr, my_optopt)))
    {
      /*
       * if the user didn't specify '-' as an option,
       * assume it means -1.
       */
      if (my_optopt == (int) '-')
	return -1;
      if ( ! *place)
	++my_optind;
      if (my_opterr && *ostr != ':')
	fprintf (stderr, "%s: illegal option -- %c\n", pname, my_optopt);
      return BADCH;
    }
  if (* ++oli != ':')
    {				/* don't need argument */
      my_optarg = NULL;
      if ( ! *place)
	++my_optind;
    }
  else
    {				/* need an argument */
      if (*place)		/* no white space */
	my_optarg = place;
      else if (nargc <= ++my_optind)
	{			/* no arg */
	  place = EMSG;
	  if (*ostr == ':')
	    return BADARG;
	  if (my_opterr)
	    fprintf (stderr, "%s: option requires an argument -- %c\n",
		     pname, my_optopt);
	  return BADCH;
	}
      else			/* white space */
	my_optarg = nargv[my_optind];
      place = EMSG;
      ++my_optind;
    }
  return my_optopt;		/* dump back option letter */
}
#endif /* !defined(RANGE_FUNC) */

/* We provide our own version on inet_aton here because Solaris/SunOS
   doesn't have one and also because I'm not 100% sure that the one on
   Linux always yields the correct results.

   Note that we can't name this just `inet_aton' because if we do, the
   Linux standard library version of gethostbyname will malfunction for
   unknown reasons.  */

/*
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies, and that
 * the name of Digital Equipment Corporation not be used in advertising or
 * publicity pertaining to distribution of the document or software without
 * specific, written prior permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/*
static char sccsid[] = " [at] (#)inet_addr.c	8.1 (Berkeley) 6/17/93";
*/

/* 
 * Check whether "cp" is a valid ascii representation
 * of an Internet address and convert to a binary address.
 * Returns 1 if the address is valid, 0 if not.
 * This replaces inet_addr, the return value from which
 * cannot distinguish between failure and a local broadcast address.
 */
static int
my_inet_aton (register const char *cp, register struct in_addr *addr)
{
	register unsigned long val;
	register int base, n;
	register char c;
	u_int parts[4];
	register u_int *pp = parts;

	c = *cp;
	for (;;) {
		/*
		 * Collect number up to ``.''.
		 * Values are specified as for C:
		 * 0x=hex, 0=octal, isdigit=decimal.
		 */
		if (!isdigit(c))
			return (0);
		val = 0; base = 10;
		if (c == '0') {
			c = *++cp;
			if (c == 'x' || c == 'X')
				base = 16, c = *++cp;
			else
				base = 8;
		}
		for (;;) {
			if (isascii(c) && isdigit(c)) {
				val = (val * base) + (c - '0');
				c = *++cp;
			} else if (base == 16 && isascii(c) && isxdigit(c)) {
				val = (val << 4) |
					(c + 10 - (islower(c) ? 'a' : 'A'));
				c = *++cp;
			} else
				break;
		}
		if (c == '.') {
			/*
			 * Internet format:
			 *	a.b.c.d
			 *	a.b.c	(with c treated as 16 bits)
			 *	a.b	(with b treated as 24 bits)
			 */
			if (pp >= parts + 3)
				return (0);
			*pp++ = val;
			c = *++cp;
		} else
			break;
	}
	/*
	 * Check for trailing characters.
	 */
	if (c != '\0' && (!isascii(c) || !isspace(c)))
		return (0);
	/*
	 * Concoct the address according to
	 * the number of parts specified.
	 */
	n = pp - parts + 1;
	switch (n) {

	case 0:
		return (0);		/* initial nondigit */

	case 1:				/* a -- 32 bits */
		break;

	case 2:				/* a.b -- 8.24 bits */
		if (val > 0xffffff)
			return (0);
		val |= parts[0] << 24;
		break;

	case 3:				/* a.b.c -- 8.8.16 bits */
		if (val > 0xffff)
			return (0);
		val |= (parts[0] << 24) | (parts[1] << 16);
		break;

	case 4:				/* a.b.c.d -- 8.8.8.8 bits */
		if (val > 0xff)
			return (0);
		val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
		break;
	}
	if (addr)
		addr->s_addr = htonl(val);
	return (1);
}

/* Initialize and shutdown MS Windows Sockets interface.  */

static void
mswinsock_init (void)
{
#if defined(WIN32)
  auto WSADATA WSAData;
  register int const status = WSAStartup (MAKEWORD (1,1), &WSAData);

  if (status != 0)
    {
      fprintf (stderr, "%s: WSAStartup: %d", pname, status);
      exit(2);
    }
#endif	/* defined(WIN32) */
}

static void 
mswinsock_shutdown (void)
{
#if defined(WIN32)
  register int const status = WSACleanup ();

  if (status != 0)
    {
      fprintf (stderr, "%s: WSAShutdown: %d", pname, status);
      exit(2);
    }
#endif	/* defined(WIN32) */
}

/* Print error, properly terminate access to winsock (WIN32), then exit.  */

static void fatal (register int const exit_code, const char* fmt, ...)
    __attribute__ ((noreturn));

static void
fatal (register int const exit_code, const char* fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  exit (exit_code);
}

#if !defined(RANGE_FUNC)
static void
usage (void)
{
  fprintf (stderr, "%s: usage `%s [-a] [-n] [-t] [-v] [-T secs] address'\n", 
    pname, pname);
  exit (2);
}
#endif /* !defined(RANGE_FUNC) */

static void *
xmalloc (register unsigned size)
{
  register void *p;

  if ((p = malloc (size)) == 0)
    fatal (2, "%s: memory exhausted\n", pname);
  return p;
}

/* substr(s1,s2) - return a pointer to the first instance of the string
   s2 within the string s1, or NULL if s2 does not appear within s1.  */

static char const *
substr (register const char *s1, register char const *s2)
{
  for (; *s1 ; s1++)
    {
      register char const *p1;
      register char const *p2;
      register int c;

      for (p1 = s1, p2 = s2; (c = *p2); p1++, p2++)
        if (*p1 != c)
          goto outer;
      return s1;
outer:
      ;
    }
  return 0;
}

/* subnstr(s1,s2,n) - return a pointer to the first instance of the string
   s2 within the first n characters of string s1, or NULL if s2 does not
   appear within the first n characters of s1.  Note that if the first n
   characters of the string pointed to by s1 includes a nul bytes, then
   the search for the subscring s2 terminates at that point, even if the
   value of n would otherwise suggest that the search should continue
   further.  */

static char const *
subnstr (register char const *s1, register char const *s2,
	 register unsigned long n)
{
  register char const *const s1_limit = s1 + n;

  for (; *s1 && s1 < s1_limit ; s1++)
    {
      register char const *p1;
      register char const *p2;
      register int c;

      for (p1 = s1, p2 = s2; (c = *p2); p1++, p2++)
        if (*p1 != c)
          goto outer;
      return s1;
outer:
      ;
    }
  return 0;
}

/*  skip whitespace characters, blank & \t; return pointer to next char.
    note: isspace() also includes cr & lf, which we don't want  */

static char const *
skip_ws (register char const *startp)
{
  register char const *p = startp;
  while (*p)
    {
      register int const ch = *p;

      if ((ch != ' ') && (ch != '\t'))
	break;

      p++;
    }

  return p;
}

#if !defined(RANGE_FUNC)
/* Return True if the given string consists of only digits,
   and at least one period.  */

static Bool
vaguely_ipish (register char const *s)
{
  register char const *p;
  register int ndots = 0;

  for (p = s; *p; p++)
    {
      register int const ch = *p;

      if (!isdigit (ch))
	{
	  if (ch == '.')
	    ndots++;
	  else
	    return False;
	}
    }
  return ndots ? True : False;
}

/* We provide our own version of gethostbyname here because the one on
   Linux seems to improperly yield a non-null (success) result if and when
   it is passed such bogus strings as "24" or "24.0".  We avoid that here.  */

static struct hostent *
my_gethostbyname (register char const *const name)
{
  return vaguely_ipish (name) ? (struct hostent *)0 : gethostbyname (name);
}
#endif /* !defined(RANGE_FUNC) */

#if !defined(WIN32)
static void
alarm_handler (register int argname(signo))
{
  longjmp (recovery, 1);
}
#endif	/* !defined(WIN32) */

/* Try connecting to (and sending a specified query to) either a WHOIS server
   or an RWHOIS server.

   If we can't connect for any reason, log a suitable error message to stderr
   and then return NULL.  */

static char const *
query_server (register char const *const server,
	      register int const portno,
	      register char const *const arg)
{
  register struct hostent *hp;
  struct sockaddr_in sin;
  register int sock;
  register int con_result;
  auto unsigned long server_addr;

  /* Lookup the server's IP address.  */
  if (!(hp = gethostbyname (server)))
    fatal (2, "%s: Error: Unable to find %s server: %s\n",
	   pname, whois_service, server);

  /* Create socket.  */
  if ((sock = socket (AF_INET, SOCK_STREAM, protocol->p_proto)) < 0)
    fatal (2, "%s: Error opening %s socket: %s\n",
	   pname, tcp, strerror (errno));

  sin.sin_family = AF_INET;
  memcpy (&server_addr, hp->h_addr, sizeof server_addr);
  sin.sin_addr.s_addr = server_addr;
  sin.sin_port = htons (portno);

  if (verbose_mode)
    fprintf (stderr, "%s: Connecting to server: %s:%d\n",
	     pname, server, portno);

#if !defined(WIN32) /* no SIGALRM on WIN32 */
  signal (SIGALRM, alarm_handler);
  if (setjmp (recovery) != 0)
    {
      fprintf (stderr, "%s: Timeout connecting to server `%s'\n",
	       pname, server);
      return NULL;
    }
  else
#endif	/*	!defined(WIN32)	*/
    {
#if !defined(WIN32)
      if (timeout)
        alarm (timeout);
#endif /* !defined(WIN32) */
      con_result = connect (sock, (struct sockaddr *)&sin, sizeof sin);
#if !defined(WIN32)
      if (timeout)
        alarm (0);
#endif	/* !defined(WIN32) */
      if (con_result < 0)
	{
          fprintf (stderr, "%s: Error connecting to server `%s': %s\n",
                   pname, server, strerror (errno));
	  return NULL;
	}
      else
        {
	  enum { ibuf_size = (1 << 16) };
          char ibuf[ibuf_size];
	  register char const *const ibuf_limit = &ibuf[ibuf_size];
	  register char *inp = ibuf;
	  register unsigned long space_left = ibuf_size;
	  register unsigned long used;
	  register char *result;

	  if (verbose_mode)
	    fprintf (stderr, "%s: Query: %s\n", pname, arg);

	  send (sock, arg, strlen (arg), 0);
	  send (sock, "\n", 1, 0);

	  for (;;)
	    {
	      register int result;

	      if ((result = recv (sock, inp, space_left, 0)) <= 0)
		break;
	      if ((inp += result) >= ibuf_limit)
		fatal (2, "%s: Response from server `%s' too large\n",
		       pname, server);
	      space_left -= result;
	    }
          close (sock);
	  used = inp - ibuf;
	  result = (char *) xmalloc (used + 1);
	  memcpy (result, ibuf, used);
	  result[used] = '\0';
	  return result;
        }
    }
}

static char const *
whois (register char const *const server, register char const *const arg)
{
  return query_server (server, 43, arg);
}

/* The following routine allows us to get more specific data in the case
   of some specific networks that happen to put all of their sub-block
   registration data into their own local RWHOIS server, rather than
   SWIP'ing it all to ARIN.  Some examples include Digex (e.g. 209.116.1.1),
   Epoch (e.g. 207.168.1.1), and Exodus (e.g. 209.1.1.1).

   Note that CAIS Internet (cais.net) _claims_ to be running an rwhois server
   (e.g. in their ARIN registration record for their 207.226.0.0/16 address
   block) however the reality is that this this rwhois servers is dead, and
   not accepting connections.  In cases such as that, the following function
   will yield a NULL pointer.
*/

static char const *
fetch_rwhois_data (register char const *const whois_data,
		   register char const *const dotted_quad)
{
  static char const rwhois_prefix[] = "rwhois.";
  enum { rwhois_prefix_len = sizeof rwhois_prefix - 1 };
  register char const *const p = substr (whois_data, rwhois_prefix);
  register char const *q;
  register unsigned srvr_len;
  register char *server;
  register int portno = 0;
  register char const *result;

  if (!p)
    return NULL;

  for (q = p + rwhois_prefix_len; *q && !isspace (*q); q++)
    continue;
  srvr_len = q - p;
  server = (char *) xmalloc (srvr_len + 1);
  memcpy (server, p, srvr_len);
  server[srvr_len] = '\0';
  while (isspace (*q))
    q++;
  while (isdigit (*q))
    portno = (portno * 10) + (*q++ - '0');
  if (!portno)
    portno = 4321;  /* Default rwhois port.  */

  /* The following line will cause us to return NULL in cases where the
     designated rwhois server is either dead or not responding.  */

  if ((result = query_server (server, portno, dotted_quad)) == NULL)
    return NULL;

  /* In general, the first line of output from most rwhois servers will be
     a header line like "%rwhois V-blah blah blah".  We skip over that
     useless jizz here.  */

  if (*result == '%')
    {     
      do    
        result++;
      while (*result && *result != '\n');
      result++;
    } 

  /* The Exodus.Net rwhois server sometimes gives %error (and then no useful
     data) for IP addresses that do exist, that are in use, and that reside
     within Exodus's address blocks, like for example 216.32.210.46.  We
     deal with this bit of stupidity here.  */

  if (strncmp (result, "%error", 6) == 0)
    return NULL;

  return result;
}

static void
print_range (register in_addr_range const *rangep)
{
  /* WARNING: Do not try to combine the following two calls to printf into
     one.  doing so will result in wrong results because there is only one
     buffer for the results of the calls to inet_ntoa().  */
  printf ("%s-", inet_ntoa (rangep->start));
  printf ("%s\n", inet_ntoa (rangep->end));
}

static char const *
containing_block (register char const *const dotted)
{
  register char const *const last_dot = strrchr (dotted, '.');
  if (last_dot)
    {
      register unsigned const len = last_dot - dotted;
      register char *const result = (char *) xmalloc (len + 1);

      memcpy (result, dotted, len);
      result[len] = '\0';
      return result;
    }
  else
    return NULL; 
}

/* Return True if we were in fact able to find (or deduce) a valid IP address
   range in the individual summary record that was passed to us, or else
   return False if we were not able to find such an address range.  */

static Bool
get_in_addr_range (register char const *const rec_start,
		   register char const *const rec_end,
		   register unsigned const top_byte_digits,
		   register in_addr_range *resultp)
{
  static char aborting_search[] = "Aborting search";
  char dotted_quad_buf[16];
  auto in_addr first_addr;
  auto in_addr last_addr;
  register char const *p;
  register char const *start_addr_end;
  register char const *end_addr_end;
  register unsigned len;
  register unsigned periods_seen;

  /* The ARIN whois server has the annoying property that it will only yield
     a maximum of 256 records in response to any query.  Also annoying is the
     fact that when you hit this limit, the output is prefixed by a line
     telling you that you hit that limit.  We check for such lines here and
     return False in order to be able to ignore them when and if we come
     upon them.

     Note that in cases where we make a query which requries more than 256
     records from the ARIN as a response, this program may end up incorrectly
     saying (via a result status of 1) that there is no registration record
     pertaining to the IP address which the user asked for info about.  This
     can be seen to happen (for example) when doing `ipw 12.0.0.0'.  There
     really ain't much we can do about this problem.  If ARIN won't give us
     the data then we're basically screwed.  */

  if (strncmp (rec_start, aborting_search, sizeof aborting_search - 1) == 0)
    return False;

  for (p = rec_end; isspace (*p); p--)
    continue;
  end_addr_end = p + 1;

  /* Did I mention that the people running the ARIN are turkeys?  Try doing
     a lookup using their whois server on 205.182.50.5 and you'll see what
     I mean.  They have a tendency to allow the name part of records to be
     output in such a way that they come out jammed right up against either
     the starting address or else the end address of the range, which makes
     it just a leetle difficult to tell where those addresses actually start.
  */

  periods_seen = 0;
  for ( ; p > rec_start; p--)
    {
      if (!isdigit (*p) && *p != '.')
        goto found_end_addr_start;
      if (*p == '.')
	if (++periods_seen == 3)
	  {
	    p -= (1 + top_byte_digits);
	    goto found_end_addr_start;
	  }
    }

  fatal (2, "%s: Can't find start of ending IP addr in ARIN record\n", pname);

found_end_addr_start:
  p++;
  len = end_addr_end - p;
  if (len == 0)
    fatal (2, "%s: Ending IP addr missing in ARIN record\n", pname);
  if (len > 15)
    fatal (2, "%s: Ending IP too long in ARIN record\n", pname);
  memcpy (dotted_quad_buf, p, len);
  dotted_quad_buf[len] = '\0';
#if 0  /* We now filter out records for ASNs before this function is called.  */
  if (len <= 4 && strchr (dotted_quad_buf, '.') == NULL)
    /* We have found an ARIN record for an ASN.  We want to ignore this.  */
    return False;
#endif /* 0 */
  if (!my_inet_aton (dotted_quad_buf, &last_addr))
    fatal (2, "%s: Bad ending address in ARIN record: %s\n",
	   pname, dotted_quad_buf);
  resultp->end = last_addr;
  p--;  /* Point to space or non-digit/non-period again.  */
  if (*p != ' ' || *--p != '-')
    {
      register unsigned long address = ntohl (last_addr.s_addr);
      register unsigned long byte_mask = 0xff;
      register unsigned long const low_order_byte = address & byte_mask;

      if (low_order_byte)
	/* This is an annoying case that comes up rather infrequently.  The
	   record we were given most probably represents a single host, but
	   one whose handle fails to have the standard -HST suffix.  An
	   example of one such host can be found by doing an ARIN lookup
	   on the address 192.70.34.15.  We will just ignore such records,
	   because that is the Right Thing To Do.  */
	return False;
      else
	{
	  /* This is fairly screwed, but another major fuck up in the ARIN
	     records is that some of them only contain the starting address
	     for the relevant netblock, rather than specifying the range
	     entirely in the normal "first - last" format.  Of course this
	     leaves us to try to intuit what the ending address of the
	     range might be.  I believe that in all cases we can do that
	     reliably by just substituting .255 for any and all of the
	     trailing .0 components in the starting address spec.  */

	  first_addr = last_addr;
	  do
	    {
	      address |= byte_mask;
	      byte_mask <<= 8;
	    }
	  while ((address & byte_mask) == 0);
	  last_addr.s_addr = htonl (address);
	  resultp->start = first_addr;
	  resultp->end = last_addr;
	  goto done;
	}
    }
  if (*--p != ' ')
    fatal (2, "%s: Missing space before hyphen in ARIN record\n", pname);
  start_addr_end = p;

  periods_seen = 0;
  for (p--; p > rec_start; p--)
    {
      if (!isdigit (*p) && *p != '.')
        goto found_start_addr_start;
      if (*p == '.')
	if (++periods_seen == 3)
	  {
	    p -= (1 + top_byte_digits);
	    goto found_start_addr_start;
	  }
    }

  fatal (2, "%s: Missing space before starting IP in ARIN record\n", pname);

found_start_addr_start:
  p++;
  /* p should now be pointing to the first digit of the first component of
     the dotted quad representing the starting address of the IP address
     range.  */
  len = start_addr_end - p;
  if (len > 15)          
    fatal (2, "%s: Starting IP too long in ARIN record\n", pname);
  memcpy (dotted_quad_buf, p, len);
  dotted_quad_buf[len] = '\0'; 
  if (!my_inet_aton (dotted_quad_buf, &first_addr))
    fatal (2, "%s: Bad starting address in ARIN record: %s\n",
           pname, dotted_quad_buf);
  resultp->start = first_addr;
  p--;

done:

#if 0  /* This code is for debugging only.  */
  fprintf (stderr, "Name:  ");
  {
    register char const *pp;

    while (isspace (*p))
      p--;
    for (pp = rec_start; pp <= p; pp++)
      putc (*pp, stderr);
    putc ('\n', stderr);
  }
  /* WARNING: Do not try to combine the following two calls to fprintf into
     one.  doing so will result in wrong results because there is only one
     buffer for the results of the calls to inet_ntoa().  */
  fprintf (stderr, "Range: %s - ", inet_ntoa (resultp->start));
  fprintf (stderr, "%s\n", inet_ntoa (resultp->end));
#endif

  return True;
}

/* Quite a lot of address ranges are specified wrong in the ARIN data base.
   (A few are also screwed up in RIPE data base.)  Here we clean up any
   problems we see with the end address of the range.  */

static void
normalize_in_addr_range (register in_addr_range *const rangep)
{
  register unsigned long rstart = ntohl (rangep->start.s_addr);
  register unsigned long rend = ntohl (rangep->end.s_addr);

  if ((rend & 0xff000000) != (rstart & 0xff000000)) /* Have at least one /8. */
    if ((rend & 0x00ff0000) == 0)
      {
	rangep->end.s_addr = htonl (rend | 0xffffff);
	return;
      }

  if ((rend & 0x00ff0000) != (rstart & 0x00ff0000)) /* Have at least one /16. */
    if ((rend & 0x0000ff00) == 0)
      {
	rangep->end.s_addr = htonl (rend | 0xffff);
	return;
      }

  if ((rend & 0x0000ff00) != (rstart & 0x0000ff00)) /* Have at least one /24. */
    if ((rend & 0x000000ff) == 0)
      rangep->end.s_addr = htonl (rend | 0xff);
}

static unsigned long
sizeof_in_addr_range (register in_addr_range const range)
{
  return (range.end.s_addr - range.start.s_addr) + 1;
}

static int
within_in_addr_range (register in_addr_range const *const rangep,
		      register in_addr addr)
{
  register unsigned long rstart = ntohl (rangep->start.s_addr);
  register unsigned long rend = ntohl (rangep->end.s_addr);
  register unsigned long address = ntohl (addr.s_addr);

#if 0
  printf ("checking %s <= ", inet_ntoa (rangep->start));
  printf ("%s", inet_ntoa (addr));
  printf (" <= %s\n", inet_ntoa (rangep->end));
#endif

  return (address >= rstart && address <= rend);
}

/*  parse IP address, and convert into in_addr. 
    Return pointer to next non-space char if successful, null if not.  */

static char const *
scan_ip (register char const *const data, register in_addr *const addrp)
{
  register char const *startp;
  register char const *p;
  register unsigned len;
  auto char ip_buf[16];

  startp = skip_ws (data);
  p = startp;
  while (*p)
    {
      register int const ch = *p;

      if (!isdigit (ch) && ch != '.')
	break;
      p++;
    }

  len = p - startp;
  if (len < sizeof (ip_buf))
    {
      memcpy(ip_buf, startp, len);
      ip_buf[len] = '\0';
      if(!my_inet_aton (ip_buf, addrp))
        return NULL;
    }
    else
      return NULL;
	
  return skip_ws (p);
}

/*  Search whois data for address range or starting IP, prefixed with
    specified string on same line (e.g. "inetnum: 1.2.3.4 - 5.6.7.8").
    Return range, concocting ending IP if necessary.  */

static Bool
parse_range (register char const *const data, register char const *const prefix,
             register in_addr_range *const rangep)
{
  /*  find prefix  */
  register char const *p = substr (data, prefix);

  if(p)
    {
      p += strlen (prefix);

      if (! (p = scan_ip (p, &rangep->start)))
	fatal (2, "%s: Bad netblock starting address\n", pname);

      if (*p == '-')
	{
	  if (! (p = scan_ip (++p, &rangep->end)))
	    fatal (2, "%s: Bad netblock ending address\n", pname);
	}
      else
	{
	  /* Lifted from get_in_addr_range().  */
	  register unsigned long address = ntohl (rangep->start.s_addr);
	  register unsigned long byte_mask = 0xff;

	  do
	    {
	      address |= byte_mask;
	      byte_mask <<= 8;
	    }
	  while ((address & byte_mask) == 0);
	  rangep->end.s_addr = htonl (address);
	}
    }
  else
    return False;

  return True;
}

static char const *
arin_grunge (register char const *const old_data,
	     register struct in_addr const addr)
{
  register char const *this_rec_start = old_data;
  register char const *this_rec_end;
  register char const *best_rec_start = NULL;
  register char const *best_rec_end = NULL;
  register unsigned long best_size = 0xffffffff;
  register in_addr_range best_range;
  register unsigned long const top_byte = (ntohl (addr.s_addr) >> 24) & 0xff;
  register unsigned const top_byte_chars =
    (top_byte >= 100) ? 3 : (top_byte >= 10) ? 2 : 1;

  for (; *this_rec_start;)
    {
      auto in_addr_range this_range;
      register unsigned long rec_len;

      this_rec_end = this_rec_start;
      for (;; this_rec_end++)
	{
	  if (this_rec_end[0] == '\n' && this_rec_end[1] != '\t')
	    break;
	}

      rec_len = this_rec_end - this_rec_start;

      /* The first blank line we hit signals the end of the useful ARIN
	 record data.  */
      if (!rec_len)
	break;

      /* Ignore records that describe individual hosts and/or ASNs.  For all
	 other records, try to get the IP address range associated with that
	 record, and if it is smaller than the smallest containing address
	 range we have seen so far which contains the IP address of interest,
	 then remember it as our new smallest containing address range.  */

      if (subnstr (this_rec_start, "-ASN)", rec_len) == 0
	  && subnstr (this_rec_start, "-HST)", rec_len) == 0
          && get_in_addr_range (this_rec_start, this_rec_end,
				top_byte_chars, &this_range))
	{
          normalize_in_addr_range (&this_range);
          if (within_in_addr_range (&this_range, addr))
            {
              register unsigned long size = sizeof_in_addr_range (this_range);
    
              if (size < best_size)
                {
                  best_range = this_range;
                  best_size = size;
                  best_rec_start = this_rec_start;
                  best_rec_end = this_rec_end;
                }
            }
	}
      this_rec_start = this_rec_end + 1;
    }

  if (!best_rec_start || !best_rec_end)
    /* The address in question isn't actually registered to anybody.  */
    return NULL;

  {
    register char const *p;
    register char const *handle_end;
    register unsigned long len;
    register char *handle;

    for (p = best_rec_end; p > best_rec_start; p--)
      if (*p == ')')
	goto rparen;
    fatal (2, "%s: Missing right paren in ARIN record\n", pname);
rparen:
    handle_end = p;
    for (p--; p > best_rec_start; p--)
      if (*p == '(')
	goto lparen;
    fatal (2, "%s: Missing left paren in ARIN record\n", pname);
lparen:
    p++;
    len = handle_end - p;

    /* NOTE: We need to prefix ARIN handles with `!' in order to avoid
       having the ARIN WHOIS server improperly try to match the handle
       against things that are not in fact handles (and thus perhaps
       yielding screwy results.  For an example of this screwyness in
       action, just ask the ARIN WHOIS server about "NET-O2TECH".  */

    handle = (char *) xmalloc (1 + len + 1);
    handle[0] = '!';
    memcpy (&handle[1], p, len);
    handle[1+len] = '\0';
    return whois (arin_server, handle);
  }
}

static int
present_arin_style_results (register char const *const data)
{
  if (!data)
    return 1;

  if (handle_mode)
    {
      static char const prefix[] = "Netname:";
      register char const *p = substr (data, prefix);
      if (p)
	{
	  if (add_handle_prefixes)
	    printf ("%s", arin_handle);

	  p = skip_ws (p + sizeof prefix);
	  while (!isspace (*p))
	    putchar (*p++);
	  putchar ('\n');
	}
      else
	fatal (2, "%s: Unable to parse NIC handle from ARIN whois results\n",
	  pname);
    }
  if (addr_mode)
    {
      auto in_addr_range range;

      if (parse_range (data, "Netblock:", &range))
	{
	  normalize_in_addr_range (&range);
	  print_range (&range);
	}
      else if (parse_range (data, "Netnumber:", &range))
	{
	  normalize_in_addr_range (&range);
	  print_range (&range);
	}
      else
	fatal (2, "%s: Unable to find address range in ARIN whois results\n", 
	       pname);
    }
  if (contacts1_mode || contacts2_mode)
    {
      register Bool not_first = False;
      register char const *atsign;

      /* We have to allow here for screwy ARIN records for the  [at] Home network,
	 like for example the one covering the address 24.0.26.58.  */

      for (atsign = data + 1; *atsign; atsign++)
	{
	  if (atsign[0] == ' [at] ' && atsign[-1] != ' ')
	    {
	      register char const *startp;
	      register char const *endp;
	      register char const *p;

	      for (startp = atsign - 1; !isspace (*startp); startp--)
		continue;
	      startp++;
	      for (endp = atsign + 1; *endp && !isspace (*endp); endp++)
		continue;
	      if (not_first && contacts1_mode)
		putchar (','), putchar (' ');
	      for (p = startp; p < atsign; p++)
		putchar (*p);
	      putchar (' [at] ');
	      for (p = atsign + 1; p < endp; p++)
		putchar (tolower (*p));
	      if (contacts2_mode)
		putchar ('\n');
	      not_first = True;
	    }
	}
      if (not_first && contacts1_mode)
        putchar ('\n');
    }
  if (!contacts1_mode && !contacts2_mode && !addr_mode && !handle_mode)
    puts (data);

  return 0;
}

static int
present_ripe_style_results (register char const *const data,
			    register char const *which_registry)
{
  register char const *inetnum_line;
  static char inetnum[] = "\ninetnum:";
  register char const *last_inetnum;
  register char const *next_inetnum;
  
  /* In the case of RIPE style results, we will often get a hunk of output
     relating to the containing IP address block, as well as a hunk of
     output relating to the smaller and more specific block that we are
     actually interested in.  In such cases, we want to skip over the
     part of the output relating to the larger IP address block (which
     always appears first).  */

  last_inetnum = data;
  next_inetnum = substr (last_inetnum, inetnum);
  while (next_inetnum != NULL)
    {
      last_inetnum = next_inetnum;
      next_inetnum = substr (last_inetnum + sizeof inetnum - 1, inetnum);
    }
  inetnum_line = last_inetnum + 1;

  if (handle_mode)
    {
      static char const prefix[] = "netname:";
      register char const *p = substr (data, prefix);
      if (p)
	{
	  if (add_handle_prefixes)
	    printf ("%s", which_registry);

	  p = skip_ws (p + sizeof prefix);
	  while (!isspace (*p))
	    putchar (*p++);
	  putchar ('\n');
	}
      else
	fatal (2, "%s: Unable to parse NIC handle from %s whois results\n",
	  pname, which_registry);
    }
  if (addr_mode)
    {
      auto in_addr_range range;

      if (parse_range (inetnum_line, inetnum + 1, &range))
	{
	  normalize_in_addr_range (&range);
	  print_range (&range);
	}
      else
	fatal (2, "%s: Unable to parse address range from %s whois results\n", 
	  pname, which_registry);
    }
  if (contacts1_mode || contacts2_mode)
    {   
      register Bool not_first = False;
      register char const *tail;

      for (tail = inetnum_line;;)
	{
	  register char const *addr_line = substr (tail, "\ne-mail:");
	  register char const *p;

	  if (!addr_line)
	    break;
	  for (p = addr_line + sizeof "\ne-mail:" - 1; isspace (*p); p++)
	    continue;
	  if (not_first && contacts1_mode)
	    putchar (','), putchar (' ');
	  do
	    putchar (*p), p++;
	  while (!isspace (*p));
	  if (contacts2_mode)
	    putchar ('\n');
	  not_first = True;
	  tail = p;
	}
      if (not_first && contacts1_mode)
	putchar ('\n');
    }
  if (!contacts1_mode && !contacts2_mode && !addr_mode && !handle_mode)
    puts (inetnum_line);

  return 0;
}

#define BAD_CIDR(SPEC) \
do { \
  fprintf (stderr, "%s: Invalid CIDR spec: %s\n", pname, SPEC); \
  return NULL; \
} while (0)

static char const *
cidr_to_range (register char const *const cidr_spec)
{
  static char range_buf[60];
  auto char ip_buf[30];
  auto in_addr start_addr;
  auto in_addr end_addr;
  register char *q;
  register char const *p;
  register unsigned mask_bits = 0;
  register unsigned anti_mask_bits;

  for (p = cidr_spec, q = ip_buf; *p && *p != '/'; )
    *q++ = *p++;
  *q = '\0';

  if (*p != '/' || my_inet_aton (ip_buf, &start_addr) == 0)
    BAD_CIDR (cidr_spec);

  if (!*++p)
    BAD_CIDR (cidr_spec);

  for (; *p; p++)
    {
      register int ch = *p;

      if (!isdigit (ch))
	BAD_CIDR (cidr_spec);
      mask_bits = (mask_bits * 10) + (ch - '0');
    }

  if (mask_bits > 32)
    BAD_CIDR (cidr_spec);

  anti_mask_bits = 32u - mask_bits;
  if (anti_mask_bits == 0u)
    end_addr.s_addr = start_addr.s_addr;
  else
    {
      register unsigned long tmp1 = ntohl (start_addr.s_addr);
      register unsigned long tmp2 = tmp1 + ((1lu << anti_mask_bits) - 1);

      if (tmp2 < tmp1) /* Wrap around.  */
	 BAD_CIDR (cidr_spec);
      end_addr.s_addr = htonl (tmp2);
    }

  q = range_buf;
  for (p = inet_ntoa (start_addr); *p; )
    *q++ = *p++;
  *q++ = '-';
  for (p = inet_ntoa (end_addr); *p; )
    *q++ = *p++;
  *q = '\0';
  return range_buf;
}

static int
present_rwhois_style_results (register char const *const rwhois_data_start,
			      register char const *const whois_data_start)

{
  register char const *rwhois_data = rwhois_data_start;

  if (*rwhois_data == '\n')
    rwhois_data++;

  if (!contacts1_mode && !contacts2_mode && !addr_mode && !handle_mode)
    puts (rwhois_data);

  if (handle_mode)
    {
      static char const handle[] = "network:Handle:";
      enum { handle_length = sizeof handle - 1 };
      static char const network_name[] = "network:Network-Name:";
      enum { network_name_length = sizeof network_name - 1 };
      register char const *p;

      if ((p = substr (rwhois_data, handle)))
	{
	  for (p += handle_length; *p && !isspace(*p); p++)
	    putchar (*p);
	  putchar ('\n');
	}
      else if ((p = substr (rwhois_data, network_name)))
	{
	  for (p += network_name_length; *p && !isspace(*p); p++)
	    putchar (*p);
	  putchar ('\n');
	}
    }

  if (addr_mode)
    {
      static char const ip_network[] = "network:IP-Network:";
      enum { ip_network_length = sizeof ip_network - 1 };
      static char const network[] = "network:Network:";
      enum { network_length = sizeof network - 1 };
      register char const *p;
      auto char cidr_buf[100];
      register char *q;

      if ((p = substr (rwhois_data, ip_network)))
	{
	  q = cidr_buf;
	  for (p += ip_network_length; *p && !isspace(*p); )
	   *q++ = *p++;
	  *q = '\0';
	  puts (cidr_to_range (cidr_buf));
	}
      else if ((p = substr (rwhois_data, network)))
	{
	  q = cidr_buf;
	  for (p += network_length; *p && !isspace(*p); )
	   *q++ = *p++;
	  *q = '\0';
	  puts (cidr_to_range (cidr_buf));
	}
    }

  if (contacts1_mode || contacts2_mode)
    {
      /* Digex, at least, provides Domain: info.  */
      static char const domain[] = "network:Domain:";
      enum { domain_length = sizeof domain - 1 };
      register char const *p = substr (rwhois_data, domain);

      /* We have to be a little bit clever here.  If the rwhois data doesn't
	 contain any indication of anything that we might be able to turn
	 into a contact E-mail address. Then we drop back and display the
	 contact addresses given in the whois data instead.  */

      if (p)
	{
	  fputs ("postmaster [at] ", stdout);
	  for (p += domain_length; *p && !isspace (*p); p++)
	    putchar (*p);
	  putchar ('\n');
	}
      else
	return present_arin_style_results (whois_data_start);
    }

  return 0;
}

static int
try_ripe (register char const *const queryp)
{
  register char const *const whois_data = whois (ripe_server, queryp);

  if (!whois_data)
    return 1;
/*if (!strncmp (&whois_data[0120], "% No entries", 12)) */
  if (substr (whois_data, "No entries found"))
    return 1;
  return present_ripe_style_results (whois_data, ripe_handle);
}

/* APNIC has delegated registration authority for certain address ranges
   (e.g. the one which includes 203.34.97.2) to the AUNIC, so if APNIC
   says that a given address belongs to the AUNIC, we have to call the
   following function, after we get the APNIC info.  */

static int
try_aunic (register char const *const queryp)
{
  register char const *const whois_data = whois (aunic_server, queryp);

  if (!whois_data)
    return 1;
  if (!strncmp (&whois_data[1], "% No entries", 12))
    return 1;
  return present_ripe_style_results (whois_data, aunic_handle);
}

static int
try_apnic (register char const *const queryp)
{
  register char const *const whois_data = whois (apnic_server, queryp);

  if (!whois_data)
    return 1;
  if (!strncmp (&whois_data[1], "% No entries", 12))
    return 1;
  if (substr (whois_data, "\nnetname:     AUNIC-AU\n") != NULL)
    return try_aunic (queryp);
  else
    return present_ripe_style_results (whois_data, apnic_handle);
}

#if !defined(RANGE_FUNC)
static int
try_arin (register char const *const queryp)
{
  register char const *const whois_data = whois (arin_server, queryp);

  if (!whois_data)
    return 1;
  if (!strncmp (whois_data, "No match", 8))
    return 1;
  else
    return present_arin_style_results (whois_data);
}
#endif /* !defined(RANGE_FUNC) */

static void
initialize (void)
{
  mswinsock_init();

  if (!(protocol = getprotobyname (tcp)))
    fatal (1, "%s: Unknown protocol: %s\n", pname, tcp);

  if (!(service = getservbyname (whois_service, tcp)))
    fatal (1, "%s: Unknown service: %s\n", pname, whois_service);
}

static void
finalize (void)
{
  mswinsock_shutdown();
}

static int
process_ip_address (register in_addr addr)
{
  register char const *dotted_quad;
  register char const *dotted_blockname;
  register char const *whois_data;
  register int block_kind;
  register unsigned long top_byte;
  register unsigned long second_byte;

  dotted_quad = strdup (inet_ntoa (addr));

#if 0
  printf ("Checking %s\n", dotted_quad);
#endif

  /* Don't ask me why, but the ARIN record(s) for the entire 192.114.0.0 -
     192.118.255.255 block are snafued.  If you do a lookup on anything in
     that whole block, you will just get back a record describing the Israeli
     master NIC, but with a little note attached saying that the data that
     you were actually looking for is over at RIPE.  Go figure!  We just
     compensate for this bit of insanity here.  */

  top_byte = (ntohl (addr.s_addr) >> 24) & 0xff;
  second_byte = (ntohl (addr.s_addr) >> 16) & 0xff;
  if (top_byte == 192 && second_byte >= 114 && second_byte <= 118)
    {
      try_ripe (dotted_quad);
      return 0;
    }

#if 0
  /* Don't ask me why, but ARIN lookups seem to be totally broken for both
     the 12.0.0.0/8 block and also the 24.0.0.0/8 block.  The lookups just
     plain don't work right.  Try it for yourself and see.  Do a lookup
     using `whois -h whois.arin.net' on "24.2.4" and  you will get back just
     a failure indication which indicates that no matches were found.  But
     if you do a lookup on just "24", you will get back a boatload of matches
     one of which will be a record for the "24.2.4.0 - 24.2.4.255" address
     range.  Go figure!  Of course this proves that ARIN is totally fucked
     up, but then we knew that already, right?  Anyway, we try to compensate
     here for ARIN's brain damage.  */

  top_byte = (ntohl (addr.s_addr) >> 24) & 0xff;
  if (top_byte == 12)
    {
      /* In cases where none of the ranges of IP addresses that ARIN told
	 us about contains the original IP address we asked about, provide
	 the user with the WHOIS information for the onwer of this entire
	 /8 block, because we _do_ know who really owns this /8 block.  */
      if ((whois_data = whois (ARIN, "net 12")) == NULL)
	whois_data = whois (ARIN, "NET-ATT");
      else if ((whois_data = arin_grunge (whois_data, addr)) == NULL)
	whois_data = whois (ARIN, "NET-ATT");
      if (!whois_data)
	return 1;
      return present_arin_style_results (whois_data);
    }
  else
  if (top_byte == 24)
    {
      /* In cases where none of the ranges of IP addresses that ARIN told
	 us about contains the original IP address we asked about, provide
	 the user with the WHOIS information for the onwer of this entire
	 /8 block, because we _do_ know who really owns this whole /8 block.  */
      if ((whois_data = whois (ARIN, "net 24")) == NULL)
	whois_data = whois (ARIN, "HOME-NOC-ARIN");
      else if ((whois_data = arin_grunge (whois_data, addr)) == NULL)
	whois_data = whois (ARIN, "HOME-NOC-ARIN");
      if (!whois_data)
	return 1;
      return present_arin_style_results (whois_data);
    }
#endif /* 0 */

  dotted_blockname = dotted_quad;
  for (block_kind = 'D'; block_kind >= 'A'; block_kind--)
    {
      static char const ripe1[] = "European Regional Internet Registry/RIPE";
      static char const ripe2[] = "RIPE NCC (NET-RIPE-NCC-";
      static char const apnic[] = "Asia Pacific Network Information Center";
      static char const nomatch[] = "No match";
      static char const updated[] = "Record last updated on ";
      static char const hostname[] = "Hostname:";
      auto char arin_query[200];

try_next_addr:
      strcpy (arin_query, "net ");
      strcat (arin_query, dotted_blockname);
      whois_data = whois (arin_server, arin_query);
      if (whois_data == NULL)  /* Server must be down!  */
	return 1;
      if (strncmp (whois_data, nomatch, sizeof nomatch -1))
	{
	  register char const *rwhois_data;

          if (!strncmp (whois_data, ripe1, sizeof ripe1 - 1))
	    {
              if (try_ripe (dotted_quad))
                break;
              else
                return 0;
	    }

          if (!strncmp (whois_data, ripe2, sizeof ripe2 - 1))
	    {
              if (try_ripe (dotted_quad))
                break;
              else
                return 0;
	    }

          if (!strncmp (whois_data, apnic, sizeof apnic - 1))
	    {
              if (try_apnic (dotted_quad))
                break;
              else
                return 0;
	    }

	  /* This is rather messy, but when we are doing a lookup on a
	     complete address with ARIN, we can sometimes get back the
	     registration record for just the host at that address.  But
	     we really don't want that.  We really want the record for
	     the containing IP address block.  So we have to do some
	     fiddling here to handle such cases.  */

	  if (block_kind == 'D' && substr (whois_data, hostname))
	    {
	      register unsigned long haddr = ntohl (addr.s_addr);

	      haddr++;
	      addr.s_addr = htonl (haddr);
	      dotted_blockname = strdup (inet_ntoa (addr));
	      goto try_next_addr;
	    }

          if (!substr (whois_data, updated))
            {
              /* Handle a special case.  ARIN has returned to us a list of
                 the various address blocks that may or may not be related to
                 the one we are interested in.  Now we have to grunge around
                 in that list and find the smallest block that contains the
                 address we are interested in.  */

              if ((whois_data = arin_grunge (whois_data, addr)) == NULL)
		return 1;
            }
	  if ((rwhois_data = fetch_rwhois_data (whois_data, dotted_quad)))
	    return present_rwhois_style_results (rwhois_data, whois_data);
	  else
            return present_arin_style_results (whois_data);
	}
      dotted_blockname = containing_block (dotted_blockname);
      if (dotted_blockname == NULL)
	break;
    }

  fprintf (stderr, "%s: No registration record(s) for %s\n",
	   pname, dotted_quad);

  return 1;
}

#if defined(RANGE_FUNC)

extern int
get_containing_block_range (register in_addr const addr,
                            register in_addr *const startp,
                            register in_addr *const endp);

int
get_containing_block_range (register in_addr const addr,
                            register in_addr *const startp,
                            register in_addr *const endp)
{
  register int result = 0;
  
  initialize ();
  process_ip_address (addr);
  finalize ();
  return result;
} 

#else /* !defined(RANGE_FUNC) */

static int
process_nic_handle (register char const *const arg)
{
  /*  use as nic handle
      may include a prefix that identifies which registry it belongs to  */
  if (!strncmp (arin_handle, arg, sizeof arin_handle - 1))
    {
      if (try_arin (&arg[sizeof arin_handle - 1]))
        fatal (2, "%s: ARIN handle not found: %s\n", pname, arg);
    }
  else if (!strncmp (ripe_handle, arg, sizeof ripe_handle - 1))
    {
      if (try_ripe (&arg[sizeof ripe_handle - 1]))
        fatal (2, "%s: RIPE handle not found: %s\n", pname, arg);
    }
  else if (!strncmp (apnic_handle, arg, sizeof apnic_handle - 1))
    {
      if (try_apnic (&arg[sizeof apnic_handle - 1]))
        fatal (2, "%s: APNIC handle not found: %s\n", pname, arg);
    }
  else if (!strncmp (aunic_handle, arg, sizeof aunic_handle - 1))
    {
      if (try_aunic (&arg[sizeof aunic_handle - 1]))
        fatal (2, "%s: AUNIC handle not found: %s\n", pname, arg);
    }
  else /* no hint given, so we'll have to try 'em all */
    {
      if (try_arin (arg))
        if (try_ripe (arg))
          if (try_apnic (arg))
            fatal (2, "%s: Handle not found in any registry: %s\n", pname, arg);
    }

  return 0;
}

static int
process_cmd_arg (register char const *const arg)
{
  auto struct in_addr addr;

  if (my_inet_aton (arg, &addr))
    return process_ip_address (addr);

  /*  This test avoids unecessary DNS lookups, 
      at a loss of lookups on domain-less hostnames
      which will fall through and be treated as nic handles  */
  else if (strchr (arg, '.'))
    {
      register struct hostent *hp;

      if ((hp = my_gethostbyname (arg)) == NULL)
            fatal (2, "%s: Invalid hostname: %s\n", pname, arg);

      memcpy (&addr, hp->h_addr, sizeof addr);
      return process_ip_address (addr);
    }

  /*  use as nic handle
      may include a prefix that identifies which registry it belongs to  */
  else
    return process_nic_handle (arg);
}

int
main (register int const argc, register char *const argv[])
{
  register int ch;
  register int option_errors = 0;

  pname = strrchr (argv[0], '/');
  pname = pname ? pname+1 : argv[0];

  initialize ();
  atexit (finalize);

  while ((ch = my_getopt (argc, argv, "anNvctCT:")) != EOF)
    {
      switch (ch)
	{
	case 'a':
	  addr_mode = True;
	  break;
	case 'N':
	  add_handle_prefixes = True;
	  /* fall through */
	case 'n':
	  handle_mode = True;
	  break;
	case 'v':
	  verbose_mode = True;
	  break;
	case 'c':
	case 't':
	  contacts1_mode = True;
	  break;
	case 'C':
	  contacts2_mode = True;
	  break;
	case 'T':
	  timeout = atoi (my_optarg);
	  break;
	case '?':
	  fprintf (stderr, "%s: Invalid option: -%c\n", pname, ch);
	  option_errors++;
	  break;
	}
    }

  if (option_errors)
    usage ();

  if (my_optind == argc)
    {
      fprintf (stderr, "%s: Missing argument\n", pname);
      usage ();
    }
  else if ((my_optind + 1) < argc)
    {
      fprintf (stderr, "%s: Too many arguments\n", pname);
      usage ();
    }

  return process_cmd_arg (argv[my_optind]);
}

#endif /* !defined(RANGE_FUNC) */