1283083Sdelphij/*-
21590Srgrimes * Copyright (c) 1980, 1993
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
291590Srgrimes
301590Srgrimes#ifndef lint
3191792Smikestatic const char copyright[] =
321590Srgrimes"@(#) Copyright (c) 1980, 1993\n\
331590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
341590Srgrimes#endif /* not lint */
351590Srgrimes
3691792Smike#if 0
371590Srgrimes#ifndef lint
381590Srgrimesstatic char sccsid[] = "@(#)whois.c	8.1 (Berkeley) 6/6/93";
3990131Smike#endif /* not lint */
4028792Scharnier#endif
411590Srgrimes
4290131Smike#include <sys/cdefs.h>
4390131Smike__FBSDID("$FreeBSD$");
4490131Smike
451590Srgrimes#include <sys/types.h>
461590Srgrimes#include <sys/socket.h>
47283083Sdelphij#include <sys/poll.h>
481590Srgrimes#include <netinet/in.h>
4936913Speter#include <arpa/inet.h>
5077368Sphk#include <ctype.h>
5128792Scharnier#include <err.h>
521590Srgrimes#include <netdb.h>
5380050Smike#include <stdarg.h>
541590Srgrimes#include <stdio.h>
5553291Sache#include <stdlib.h>
5628792Scharnier#include <string.h>
5733626Swollman#include <sysexits.h>
5828792Scharnier#include <unistd.h>
59283083Sdelphij#include <fcntl.h>
60283083Sdelphij#include <errno.h>
611590Srgrimes
62130479Sbms#define	ABUSEHOST	"whois.abuse.net"
6353291Sache#define	NICHOST		"whois.crsnic.net"
6454088Sache#define	INICHOST	"whois.networksolutions.com"
6543506Swollman#define	GNICHOST	"whois.nic.gov"
6633626Swollman#define	ANICHOST	"whois.arin.net"
67106735Smike#define	LNICHOST	"whois.lacnic.net"
68138681Sceri#define	KNICHOST	"whois.krnic.net"
6933626Swollman#define	RNICHOST	"whois.ripe.net"
7033626Swollman#define	PNICHOST	"whois.apnic.net"
7153291Sache#define	MNICHOST	"whois.ra.net"
7253291Sache#define	QNICHOST_TAIL	".whois-servers.net"
7387536Smike#define	BNICHOST	"whois.registro.br"
74112617Seivind#define NORIDHOST	"whois.norid.no"
75130466Sbms#define	IANAHOST	"whois.iana.org"
76134294Smbr#define GERMNICHOST	"de.whois-servers.net"
77154710Sjhay#define FNICHOST	"whois.afrinic.net"
7881165Smike#define	DEFAULT_PORT	"whois"
7978581Sdes#define	WHOIS_SERVER_ID	"Whois Server: "
80110159Sroberto#define	WHOIS_ORG_SERVER_ID	"Registrant Street1:Whois Server:"
811590Srgrimes
8253291Sache#define WHOIS_RECURSE		0x01
8384852Smike#define WHOIS_QUICK		0x02
8453291Sache
8584852Smike#define ishost(h) (isalnum((unsigned char)h) || h == '.' || h == '-')
8684852Smike
87227246Sedstatic const char *ip_whois[] = { LNICHOST, RNICHOST, PNICHOST, BNICHOST,
88227246Sed				  FNICHOST, NULL };
89227246Sedstatic const char *port = DEFAULT_PORT;
9078900Sdd
9179835Smikestatic char *choose_server(char *);
9280050Smikestatic struct addrinfo *gethostinfo(char const *host, int exit_on_error);
9390163Skrisstatic void s_asprintf(char **ret, const char *format, ...) __printflike(2, 3);
9478581Sdesstatic void usage(void);
9590131Smikestatic void whois(const char *, const char *, int);
9628792Scharnier
9728792Scharnierint
9878581Sdesmain(int argc, char *argv[])
991590Srgrimes{
10081165Smike	const char *country, *host;
10153291Sache	char *qnichost;
10280050Smike	int ch, flags, use_qnichost;
1031590Srgrimes
10415359Spst#ifdef	SOCKS
10515359Spst	SOCKSinit(argv[0]);
10615359Spst#endif
10715359Spst
10881165Smike	country = host = qnichost = NULL;
10981165Smike	flags = use_qnichost = 0;
110202280Sedwin	while ((ch = getopt(argc, argv, "aAbc:fgh:iIklmp:QrR6")) != -1) {
11178581Sdes		switch (ch) {
11233626Swollman		case 'a':
11333626Swollman			host = ANICHOST;
11433626Swollman			break;
11581165Smike		case 'A':
11681165Smike			host = PNICHOST;
11781165Smike			break;
118130479Sbms		case 'b':
119130479Sbms			host = ABUSEHOST;
120130479Sbms			break;
12181165Smike		case 'c':
12281165Smike			country = optarg;
12385067Smike			break;
124154710Sjhay		case 'f':
125154710Sjhay			host = FNICHOST;
126154710Sjhay			break;
12743506Swollman		case 'g':
12843506Swollman			host = GNICHOST;
12943506Swollman			break;
1301590Srgrimes		case 'h':
1311590Srgrimes			host = optarg;
1321590Srgrimes			break;
13353048Sache		case 'i':
13453048Sache			host = INICHOST;
13553048Sache			break;
136130466Sbms		case 'I':
137130466Sbms			host = IANAHOST;
138130466Sbms			break;
139138681Sceri		case 'k':
140138681Sceri			host = KNICHOST;
141138681Sceri			break;
142106735Smike		case 'l':
143106735Smike			host = LNICHOST;
144106735Smike			break;
14553291Sache		case 'm':
14653291Sache			host = MNICHOST;
14753291Sache			break;
14833626Swollman		case 'p':
14981165Smike			port = optarg;
15033626Swollman			break;
15153291Sache		case 'Q':
15253291Sache			flags |= WHOIS_QUICK;
15353291Sache			break;
15433626Swollman		case 'r':
15533626Swollman			host = RNICHOST;
15633626Swollman			break;
15743520Sache		case 'R':
15881165Smike			warnx("-R is deprecated; use '-c ru' instead");
15981165Smike			country = "ru";
16043520Sache			break;
161197725Sdougb		/* Remove in FreeBSD 10 */
16254172Sjoe		case '6':
163197725Sdougb			errx(EX_USAGE,
164197725Sdougb				"-6 is deprecated; use -[aAflr] instead");
16554172Sjoe			break;
1661590Srgrimes		case '?':
1671590Srgrimes		default:
1681590Srgrimes			usage();
16978581Sdes			/* NOTREACHED */
1701590Srgrimes		}
17154227Sjoe	}
1721590Srgrimes	argc -= optind;
1731590Srgrimes	argv += optind;
1741590Srgrimes
17581165Smike	if (!argc || (country != NULL && host != NULL))
1761590Srgrimes		usage();
1771590Srgrimes
17853291Sache	/*
17981165Smike	 * If no host or country is specified determine the top level domain
18081165Smike	 * from the query.  If the TLD is a number, query ARIN.  Otherwise, use
18178581Sdes	 * TLD.whois-server.net.  If the domain does not contain '.', fall
18278581Sdes	 * back to NICHOST.
18353291Sache	 */
18481165Smike	if (host == NULL && country == NULL) {
185268154Sume		if ((host = getenv("RA_SERVER")) == NULL) {
186268154Sume			use_qnichost = 1;
187268154Sume			host = NICHOST;
188268154Sume			if (!(flags & WHOIS_QUICK))
189268154Sume				flags |= WHOIS_RECURSE;
190268154Sume		}
19153291Sache	}
19290131Smike	while (argc-- > 0) {
19381165Smike		if (country != NULL) {
19481165Smike			s_asprintf(&qnichost, "%s%s", country, QNICHOST_TAIL);
19590131Smike			whois(*argv, qnichost, flags);
19685067Smike		} else if (use_qnichost)
19780050Smike			if ((qnichost = choose_server(*argv)) != NULL)
19890131Smike				whois(*argv, qnichost, flags);
19980050Smike		if (qnichost == NULL)
20090131Smike			whois(*argv, host, flags);
20178581Sdes		free(qnichost);
20278581Sdes		qnichost = NULL;
20390131Smike		argv++;
20453291Sache	}
20553291Sache	exit(0);
20653291Sache}
20753291Sache
20879835Smike/*
20979835Smike * This function will remove any trailing periods from domain, after which it
21079835Smike * returns a pointer to newly allocated memory containing the whois server to
21179835Smike * be queried, or a NULL if the correct server couldn't be determined.  The
21279835Smike * caller must remember to free(3) the allocated memory.
21379835Smike */
21479835Smikestatic char *
21579835Smikechoose_server(char *domain)
21679835Smike{
21779835Smike	char *pos, *retval;
21879835Smike
219202281Sedwin	if (strchr(domain, ':')) {
220202281Sedwin		s_asprintf(&retval, "%s", ANICHOST);
221202281Sedwin		return (retval);
222202281Sedwin	}
22379835Smike	for (pos = strchr(domain, '\0'); pos > domain && *--pos == '.';)
22479835Smike		*pos = '\0';
22579835Smike	if (*domain == '\0')
22679835Smike		errx(EX_USAGE, "can't search for a null string");
227112617Seivind	if (strlen(domain) > sizeof("-NORID")-1 &&
228112617Seivind	    strcasecmp(domain + strlen(domain) - sizeof("-NORID") + 1,
229112617Seivind		"-NORID") == 0) {
230112617Seivind		s_asprintf(&retval, "%s", NORIDHOST);
231112617Seivind		return (retval);
232112617Seivind	}
23379835Smike	while (pos > domain && *pos != '.')
23479835Smike		--pos;
23580155Smike	if (pos <= domain)
23680155Smike		return (NULL);
23780050Smike	if (isdigit((unsigned char)*++pos))
23880050Smike		s_asprintf(&retval, "%s", ANICHOST);
239117050Sache	else
24080050Smike		s_asprintf(&retval, "%s%s", pos, QNICHOST_TAIL);
24179835Smike	return (retval);
24279835Smike}
24379835Smike
24485067Smikestatic struct addrinfo *
24580050Smikegethostinfo(char const *host, int exit_on_error)
24680050Smike{
24780050Smike	struct addrinfo hints, *res;
24880050Smike	int error;
24980050Smike
25080050Smike	memset(&hints, 0, sizeof(hints));
25180050Smike	hints.ai_flags = 0;
25280050Smike	hints.ai_family = AF_UNSPEC;
25380050Smike	hints.ai_socktype = SOCK_STREAM;
25481165Smike	error = getaddrinfo(host, port, &hints, &res);
25580050Smike	if (error) {
25680050Smike		warnx("%s: %s", host, gai_strerror(error));
25780050Smike		if (exit_on_error)
25880050Smike			exit(EX_NOHOST);
25980050Smike		return (NULL);
26080050Smike	}
26180050Smike	return (res);
26285067Smike}
26380050Smike
26480050Smike/*
26580050Smike * Wrapper for asprintf(3) that exits on error.
26680050Smike */
26753291Sachestatic void
26880050Smikes_asprintf(char **ret, const char *format, ...)
26980050Smike{
27080050Smike	va_list ap;
27180050Smike
27280050Smike	va_start(ap, format);
27380050Smike	if (vasprintf(ret, format, ap) == -1) {
27480050Smike		va_end(ap);
27580050Smike		err(EX_OSERR, "vasprintf()");
27680050Smike	}
27780050Smike	va_end(ap);
27880050Smike}
27980050Smike
28080050Smikestatic void
28190131Smikewhois(const char *query, const char *hostname, int flags)
28253291Sache{
28353291Sache	FILE *sfi, *sfo;
28490131Smike	struct addrinfo *hostres, *res;
28584852Smike	char *buf, *host, *nhost, *p;
286283083Sdelphij	int s = -1, f;
287283083Sdelphij	nfds_t i, j;
288283083Sdelphij	size_t c, len, count;
289283083Sdelphij	struct pollfd *fds;
290283083Sdelphij	int timeout = 180;
29153291Sache
29290131Smike	hostres = gethostinfo(hostname, 1);
293283083Sdelphij	for (res = hostres, count = 0; res; res = res->ai_next)
294283083Sdelphij		count++;
295283083Sdelphij
296283083Sdelphij	fds = calloc(count, sizeof(*fds));
297283083Sdelphij	if (fds == NULL)
298283083Sdelphij		err(EX_OSERR, "calloc()");
299283083Sdelphij
300283083Sdelphij	/*
301283083Sdelphij	 * Traverse the result list elements and make non-block
302283083Sdelphij	 * connection attempts.
303283083Sdelphij	 */
304283083Sdelphij	count = i = 0;
305283083Sdelphij	for (res = hostres; res != NULL; res = res->ai_next) {
306283083Sdelphij		s = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK,
307283083Sdelphij		    res->ai_protocol);
30878581Sdes		if (s < 0)
30977585Sume			continue;
310283083Sdelphij		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
311283083Sdelphij			if (errno == EINPROGRESS) {
312283083Sdelphij				/* Add the socket to poll list */
313283083Sdelphij				fds[i].fd = s;
314283083Sdelphij				fds[i].events = POLLERR | POLLHUP |
315283083Sdelphij						POLLIN | POLLOUT;
316283083Sdelphij				count++;
317283083Sdelphij				i++;
318283083Sdelphij			} else {
319283083Sdelphij				close(s);
320283083Sdelphij				s = -1;
321283083Sdelphij
322283083Sdelphij				/*
323283083Sdelphij				 * Poll only if we have something to poll,
324283083Sdelphij				 * otherwise just go ahead and try next
325283083Sdelphij				 * address
326283083Sdelphij				 */
327283083Sdelphij				if (count == 0)
328283083Sdelphij					continue;
329283083Sdelphij			}
330283083Sdelphij		} else
331283083Sdelphij			goto done;
332283083Sdelphij
333283083Sdelphij		/*
334283083Sdelphij		 * If we are at the last address, poll until a connection is
335283083Sdelphij		 * established or we failed all connection attempts.
336283083Sdelphij		 */
337283083Sdelphij		if (res->ai_next == NULL)
338283083Sdelphij			timeout = INFTIM;
339283083Sdelphij
340283083Sdelphij		/*
341283083Sdelphij		 * Poll the watched descriptors for successful connections:
342283083Sdelphij		 * if we still have more untried resolved addresses, poll only
343283083Sdelphij		 * once; otherwise, poll until all descriptors have errors,
344283083Sdelphij		 * which will be considered as ETIMEDOUT later.
345283083Sdelphij		 */
346283083Sdelphij		do {
347283083Sdelphij			int n;
348283083Sdelphij
349283083Sdelphij			n = poll(fds, i, timeout);
350283083Sdelphij			if (n == 0) {
351283083Sdelphij				/*
352283083Sdelphij				 * No event reported in time.  Try with a
353283083Sdelphij				 * smaller timeout (but cap at 2-3ms)
354283083Sdelphij				 * after a new host have been added.
355283083Sdelphij				 */
356283083Sdelphij				if (timeout >= 3)
357283083Sdelphij					timeout <<= 1;
358283083Sdelphij
359283083Sdelphij				break;
360283083Sdelphij			} else if (n < 0) {
361283083Sdelphij				/*
362283083Sdelphij				 * errno here can only be EINTR which we would want
363283083Sdelphij				 * to clean up and bail out.
364283083Sdelphij				 */
365283083Sdelphij				s = -1;
366283083Sdelphij				goto done;
367283083Sdelphij			}
368283083Sdelphij
369283083Sdelphij			/*
370283083Sdelphij			 * Check for the event(s) we have seen.
371283083Sdelphij			 */
372283083Sdelphij			for (j = 0; j < i; j++) {
373283083Sdelphij				if (fds[j].fd == -1 || fds[j].events == 0 ||
374283083Sdelphij				    fds[j].revents == 0)
375283083Sdelphij					continue;
376283083Sdelphij				if (fds[j].revents & ~(POLLIN | POLLOUT)) {
377283083Sdelphij					close(s);
378283083Sdelphij					fds[j].fd = -1;
379283083Sdelphij					fds[j].events = 0;
380283083Sdelphij					count--;
381283083Sdelphij					continue;
382283083Sdelphij				} else if (fds[j].revents & (POLLIN | POLLOUT)) {
383283083Sdelphij					/* Connect succeeded. */
384283083Sdelphij					s = fds[j].fd;
385283083Sdelphij
386283083Sdelphij					goto done;
387283083Sdelphij				}
388283083Sdelphij
389283083Sdelphij			}
390283083Sdelphij		} while (timeout == INFTIM && count != 0);
39154227Sjoe	}
392283083Sdelphij
393283083Sdelphij	/* All attempts were failed */
394283083Sdelphij	s = -1;
395283083Sdelphij	if (count == 0)
396283083Sdelphij		errno = ETIMEDOUT;
397283083Sdelphij
398283083Sdelphijdone:
399283083Sdelphij	/* Close all watched fds except the succeeded one */
400283083Sdelphij	for (j = 0; j < i; j++)
401283083Sdelphij		if (fds[j].fd != s && fds[j].fd != -1)
402283083Sdelphij			close(fds[j].fd);
403283083Sdelphij
404283083Sdelphij	if (s != -1) {
405283083Sdelphij                /* Restore default blocking behavior.  */
406283083Sdelphij                if ((f = fcntl(s, F_GETFL)) != -1) {
407283083Sdelphij                        f &= ~O_NONBLOCK;
408283083Sdelphij                        if (fcntl(s, F_SETFL, f) == -1)
409283083Sdelphij                                err(EX_OSERR, "fcntl()");
410283083Sdelphij                } else
411283083Sdelphij			err(EX_OSERR, "fcntl()");
412283083Sdelphij        }
413283083Sdelphij
414283083Sdelphij	free(fds);
41590131Smike	freeaddrinfo(hostres);
416283083Sdelphij	if (s == -1)
41778581Sdes		err(EX_OSERR, "connect()");
41833626Swollman
4191590Srgrimes	sfi = fdopen(s, "r");
4201590Srgrimes	sfo = fdopen(s, "w");
42178581Sdes	if (sfi == NULL || sfo == NULL)
42278581Sdes		err(EX_OSERR, "fdopen()");
423134294Smbr	if (strcmp(hostname, GERMNICHOST) == 0) {
424134294Smbr		fprintf(sfo, "-T dn,ace -C US-ASCII %s\r\n", query);
425166103Sphk	} else if (strcmp(hostname, "dk" QNICHOST_TAIL) == 0) {
426166103Sphk		fprintf(sfo, "--show-handles %s\r\n", query);
427134294Smbr	} else {
428134294Smbr		fprintf(sfo, "%s\r\n", query);
429134294Smbr	}
43078581Sdes	fflush(sfo);
43153291Sache	nhost = NULL;
43278581Sdes	while ((buf = fgetln(sfi, &len)) != NULL) {
43384852Smike		while (len > 0 && isspace((unsigned char)buf[len - 1]))
43478581Sdes			buf[--len] = '\0';
43584852Smike		printf("%.*s\n", (int)len, buf);
43653291Sache
43778900Sdd		if ((flags & WHOIS_RECURSE) && nhost == NULL) {
43884852Smike			host = strnstr(buf, WHOIS_SERVER_ID, len);
43984852Smike			if (host != NULL) {
44084852Smike				host += sizeof(WHOIS_SERVER_ID) - 1;
44184852Smike				for (p = host; p < buf + len; p++) {
44284852Smike					if (!ishost(*p)) {
44384852Smike						*p = '\0';
44484852Smike						break;
44584852Smike					}
44678900Sdd				}
44784852Smike				s_asprintf(&nhost, "%.*s",
44884852Smike				     (int)(buf + len - host), host);
449111430Smike			} else if ((host =
450111430Smike			    strnstr(buf, WHOIS_ORG_SERVER_ID, len)) != NULL) {
451111430Smike				host += sizeof(WHOIS_ORG_SERVER_ID) - 1;
452110159Sroberto				for (p = host; p < buf + len; p++) {
453110159Sroberto					if (!ishost(*p)) {
454110159Sroberto						*p = '\0';
455110159Sroberto						break;
456110159Sroberto					}
457110159Sroberto				}
458110159Sroberto				s_asprintf(&nhost, "%.*s",
459111430Smike				    (int)(buf + len - host), host);
460111430Smike			} else if (strcmp(hostname, ANICHOST) == 0) {
461103530Smike				for (c = 0; c <= len; c++)
462168721Sache					buf[c] = tolower((unsigned char)buf[c]);
46378900Sdd				for (i = 0; ip_whois[i] != NULL; i++) {
46484852Smike					if (strnstr(buf, ip_whois[i], len) !=
46584852Smike					    NULL) {
46684852Smike						s_asprintf(&nhost, "%s",
46784852Smike						    ip_whois[i]);
46884852Smike						break;
46984852Smike					}
47078900Sdd				}
47153291Sache			}
47253291Sache		}
47353291Sache	}
47478581Sdes	if (nhost != NULL) {
47590131Smike		whois(query, nhost, 0);
47678581Sdes		free(nhost);
47753291Sache	}
4781590Srgrimes}
4791590Srgrimes
48028792Scharnierstatic void
48178581Sdesusage(void)
4821590Srgrimes{
48378581Sdes	fprintf(stderr,
484202280Sedwin	    "usage: whois [-aAbfgiIklmQrR6] [-c country-code | -h hostname] "
48581165Smike	    "[-p port] name ...\n");
48633626Swollman	exit(EX_USAGE);
4871590Srgrimes}
488