/* 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 Contributions by Marty Bower 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 #if defined(WIN32) #include #include #else /* !defined(WIN32) */ #include #include #include #include #include #include #include #endif /* defined(WIN32) */ #include #include #include #include #include #include #include #include #include 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[] = "@(#)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 @Home network, like for example the one covering the address 24.0.26.58. */ for (atsign = data + 1; *atsign; atsign++) { if (atsign[0] == '@' && 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 ('@'); 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@", 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) */