1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#ifndef lint
33static const char copyright[] =
34"@(#) Copyright (c) 1980, 1993\n\
35	The Regents of the University of California.  All rights reserved.\n";
36#endif /* not lint */
37
38#if 0
39#ifndef lint
40static char sccsid[] = "@(#)whois.c	8.1 (Berkeley) 6/6/93";
41#endif /* not lint */
42#endif
43
44#include <sys/cdefs.h>
45__FBSDID("$FreeBSD$");
46
47#include <sys/types.h>
48#include <sys/socket.h>
49#include <sys/poll.h>
50#include <netinet/in.h>
51#include <arpa/inet.h>
52#include <ctype.h>
53#include <err.h>
54#include <netdb.h>
55#include <stdarg.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59#include <sysexits.h>
60#include <unistd.h>
61#include <fcntl.h>
62#include <errno.h>
63
64#define	ABUSEHOST	"whois.abuse.net"
65#define	ANICHOST	"whois.arin.net"
66#define	DENICHOST	"whois.denic.de"
67#define	DKNICHOST	"whois.dk-hostmaster.dk"
68#define	FNICHOST	"whois.afrinic.net"
69#define	GNICHOST	"whois.nic.gov"
70#define	IANAHOST	"whois.iana.org"
71#define	INICHOST	"whois.internic.net"
72#define	KNICHOST	"whois.krnic.net"
73#define	LNICHOST	"whois.lacnic.net"
74#define	MNICHOST	"whois.ra.net"
75#define	PDBHOST		"whois.peeringdb.com"
76#define	PNICHOST	"whois.apnic.net"
77#define	QNICHOST_TAIL	".whois-servers.net"
78#define	RNICHOST	"whois.ripe.net"
79#define	VNICHOST	"whois.verisign-grs.com"
80
81#define	DEFAULT_PORT	"whois"
82
83#define WHOIS_RECURSE	0x01
84#define WHOIS_QUICK	0x02
85#define WHOIS_SPAM_ME	0x04
86
87#define CHOPSPAM	">>> Last update of WHOIS database:"
88
89#define ishost(h) (isalnum((unsigned char)h) || h == '.' || h == '-')
90
91#define SCAN(p, end, check)					\
92	while ((p) < (end))					\
93		if (check) ++(p);				\
94		else break
95
96static struct {
97	const char *suffix, *server;
98} whoiswhere[] = {
99	/* Various handles */
100	{ "-ARIN", ANICHOST },
101	{ "-NICAT", "at" QNICHOST_TAIL },
102	{ "-NORID", "no" QNICHOST_TAIL },
103	{ "-RIPE", RNICHOST },
104	/* Nominet's whois server doesn't return referrals to JANET */
105	{ ".ac.uk", "ac.uk" QNICHOST_TAIL },
106	{ ".gov.uk", "ac.uk" QNICHOST_TAIL },
107	{ "", IANAHOST }, /* default */
108	{ NULL, NULL } /* safety belt */
109};
110
111#define WHOIS_REFERRAL(s) { s, sizeof(s) - 1 }
112static struct {
113	const char *prefix;
114	size_t len;
115} whois_referral[] = {
116	WHOIS_REFERRAL("whois:"), /* IANA */
117	WHOIS_REFERRAL("Whois Server:"),
118	WHOIS_REFERRAL("Registrar WHOIS Server:"), /* corporatedomains.com */
119	WHOIS_REFERRAL("ReferralServer:  whois://"), /* ARIN */
120	WHOIS_REFERRAL("ReferralServer:  rwhois://"), /* ARIN */
121	WHOIS_REFERRAL("descr:          region. Please query"), /* AfriNIC */
122	{ NULL, 0 }
123};
124
125/*
126 * We have a list of patterns for RIRs that assert ignorance rather than
127 * providing referrals. If that happens, we guess that ARIN will be more
128 * helpful. But, before following a referral to an RIR, we check if we have
129 * asked that RIR already, and if so we make another guess.
130 */
131static const char *actually_arin[] = {
132	"netname:        ERX-NETBLOCK\n", /* APNIC */
133	"netname:        NON-RIPE-NCC-MANAGED-ADDRESS-BLOCK\n",
134	NULL
135};
136
137static struct {
138	int loop;
139	const char *host;
140} try_rir[] = {
141	{ 0, ANICHOST },
142	{ 0, RNICHOST },
143	{ 0, PNICHOST },
144	{ 0, FNICHOST },
145	{ 0, LNICHOST },
146	{ 0, NULL }
147};
148
149static void
150reset_rir(void) {
151	int i;
152
153	for (i = 0; try_rir[i].host != NULL; i++)
154		try_rir[i].loop = 0;
155}
156
157static const char *port = DEFAULT_PORT;
158
159static const char *choose_server(char *);
160static struct addrinfo *gethostinfo(const char *, const char *, int);
161static void s_asprintf(char **ret, const char *format, ...) __printflike(2, 3);
162static void usage(void);
163static void whois(const char *, const char *, const char *, int);
164
165int
166main(int argc, char *argv[])
167{
168	const char *country, *host;
169	int ch, flags;
170
171#ifdef	SOCKS
172	SOCKSinit(argv[0]);
173#endif
174
175	country = host = NULL;
176	flags = 0;
177	while ((ch = getopt(argc, argv, "aAbc:fgh:iIklmp:PQrRS")) != -1) {
178		switch (ch) {
179		case 'a':
180			host = ANICHOST;
181			break;
182		case 'A':
183			host = PNICHOST;
184			break;
185		case 'b':
186			host = ABUSEHOST;
187			break;
188		case 'c':
189			country = optarg;
190			break;
191		case 'f':
192			host = FNICHOST;
193			break;
194		case 'g':
195			host = GNICHOST;
196			break;
197		case 'h':
198			host = optarg;
199			break;
200		case 'i':
201			host = INICHOST;
202			break;
203		case 'I':
204			host = IANAHOST;
205			break;
206		case 'k':
207			host = KNICHOST;
208			break;
209		case 'l':
210			host = LNICHOST;
211			break;
212		case 'm':
213			host = MNICHOST;
214			break;
215		case 'p':
216			port = optarg;
217			break;
218		case 'P':
219			host = PDBHOST;
220			break;
221		case 'Q':
222			flags |= WHOIS_QUICK;
223			break;
224		case 'r':
225			host = RNICHOST;
226			break;
227		case 'R':
228			flags |= WHOIS_RECURSE;
229			break;
230		case 'S':
231			flags |= WHOIS_SPAM_ME;
232			break;
233		case '?':
234		default:
235			usage();
236			/* NOTREACHED */
237		}
238	}
239	argc -= optind;
240	argv += optind;
241
242	if (!argc || (country != NULL && host != NULL))
243		usage();
244
245	/*
246	 * If no host or country is specified, rely on referrals from IANA.
247	 */
248	if (host == NULL && country == NULL) {
249		if ((host = getenv("WHOIS_SERVER")) == NULL &&
250		    (host = getenv("RA_SERVER")) == NULL) {
251			if (!(flags & WHOIS_QUICK))
252				flags |= WHOIS_RECURSE;
253		}
254	}
255	while (argc-- > 0) {
256		if (country != NULL) {
257			char *qnichost;
258			s_asprintf(&qnichost, "%s%s", country, QNICHOST_TAIL);
259			whois(*argv, qnichost, port, flags);
260			free(qnichost);
261		} else
262			whois(*argv, host != NULL ? host :
263			      choose_server(*argv), port, flags);
264		reset_rir();
265		argv++;
266	}
267	exit(0);
268}
269
270static const char *
271choose_server(char *domain)
272{
273	size_t len = strlen(domain);
274	int i;
275
276	for (i = 0; whoiswhere[i].suffix != NULL; i++) {
277		size_t suffix_len = strlen(whoiswhere[i].suffix);
278		if (len > suffix_len &&
279		    strcasecmp(domain + len - suffix_len,
280			       whoiswhere[i].suffix) == 0)
281			return (whoiswhere[i].server);
282	}
283	errx(EX_SOFTWARE, "no default whois server");
284}
285
286static struct addrinfo *
287gethostinfo(const char *host, const char *hport, int exit_on_noname)
288{
289	struct addrinfo hints, *res;
290	int error;
291
292	memset(&hints, 0, sizeof(hints));
293	hints.ai_flags = AI_CANONNAME;
294	hints.ai_family = AF_UNSPEC;
295	hints.ai_socktype = SOCK_STREAM;
296	res = NULL;
297	error = getaddrinfo(host, hport, &hints, &res);
298	if (error && (exit_on_noname || error != EAI_NONAME))
299		err(EX_NOHOST, "%s: %s", host, gai_strerror(error));
300	return (res);
301}
302
303/*
304 * Wrapper for asprintf(3) that exits on error.
305 */
306static void
307s_asprintf(char **ret, const char *format, ...)
308{
309	va_list ap;
310
311	va_start(ap, format);
312	if (vasprintf(ret, format, ap) == -1) {
313		va_end(ap);
314		err(EX_OSERR, "vasprintf()");
315	}
316	va_end(ap);
317}
318
319static int
320connect_to_any_host(struct addrinfo *hostres)
321{
322	struct addrinfo *res;
323	nfds_t i, j;
324	size_t count;
325	struct pollfd *fds;
326	int timeout = 180, s = -1;
327
328	for (res = hostres, count = 0; res; res = res->ai_next)
329		count++;
330	fds = calloc(count, sizeof(*fds));
331	if (fds == NULL)
332		err(EX_OSERR, "calloc()");
333
334	/*
335	 * Traverse the result list elements and make non-block
336	 * connection attempts.
337	 */
338	count = i = 0;
339	for (res = hostres; res != NULL; res = res->ai_next) {
340		s = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK,
341		    res->ai_protocol);
342		if (s < 0)
343			continue;
344		if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
345			if (errno == EINPROGRESS) {
346				/* Add the socket to poll list */
347				fds[i].fd = s;
348				fds[i].events = POLLERR | POLLHUP |
349						POLLIN | POLLOUT;
350				/*
351				 * From here until a socket connects, the
352				 * socket fd is owned by the fds[] poll array.
353				 */
354				s = -1;
355				count++;
356				i++;
357			} else {
358				close(s);
359				s = -1;
360
361				/*
362				 * Poll only if we have something to poll,
363				 * otherwise just go ahead and try next
364				 * address
365				 */
366				if (count == 0)
367					continue;
368			}
369		} else
370			goto done;
371
372		/*
373		 * If we are at the last address, poll until a connection is
374		 * established or we failed all connection attempts.
375		 */
376		if (res->ai_next == NULL)
377			timeout = INFTIM;
378
379		/*
380		 * Poll the watched descriptors for successful connections:
381		 * if we still have more untried resolved addresses, poll only
382		 * once; otherwise, poll until all descriptors have errors,
383		 * which will be considered as ETIMEDOUT later.
384		 */
385		do {
386			int n;
387
388			n = poll(fds, i, timeout);
389			if (n == 0) {
390				/*
391				 * No event reported in time.  Try with a
392				 * smaller timeout (but cap at 2-3ms)
393				 * after a new host have been added.
394				 */
395				if (timeout >= 3)
396					timeout >>= 1;
397
398				break;
399			} else if (n < 0) {
400				/*
401				 * errno here can only be EINTR which we would
402				 * want to clean up and bail out.
403				 */
404				s = -1;
405				goto done;
406			}
407
408			/*
409			 * Check for the event(s) we have seen.
410			 */
411			for (j = 0; j < i; j++) {
412				if (fds[j].fd == -1 || fds[j].events == 0 ||
413				    fds[j].revents == 0)
414					continue;
415				if (fds[j].revents & ~(POLLIN | POLLOUT)) {
416					close(fds[j].fd);
417					fds[j].fd = -1;
418					fds[j].events = 0;
419					count--;
420					continue;
421				} else if (fds[j].revents & (POLLIN | POLLOUT)) {
422					/* Connect succeeded. */
423					s = fds[j].fd;
424					fds[j].fd = -1;
425
426					goto done;
427				}
428
429			}
430		} while (timeout == INFTIM && count != 0);
431	}
432
433	/* All attempts were failed */
434	s = -1;
435	if (count == 0)
436		errno = ETIMEDOUT;
437
438done:
439	/* Close all watched fds except the succeeded one */
440	for (j = 0; j < i; j++)
441		if (fds[j].fd != -1)
442			close(fds[j].fd);
443	free(fds);
444	return (s);
445}
446
447static void
448whois(const char *query, const char *hostname, const char *hostport, int flags)
449{
450	FILE *fp;
451	struct addrinfo *hostres;
452	char *buf, *host, *nhost, *nport, *p;
453	int comment, s, f;
454	size_t len, i;
455
456	hostres = gethostinfo(hostname, hostport, 1);
457	s = connect_to_any_host(hostres);
458	if (s == -1)
459		err(EX_OSERR, "connect()");
460
461	/* Restore default blocking behavior.  */
462	if ((f = fcntl(s, F_GETFL)) == -1)
463		err(EX_OSERR, "fcntl()");
464	f &= ~O_NONBLOCK;
465	if (fcntl(s, F_SETFL, f) == -1)
466		err(EX_OSERR, "fcntl()");
467
468	fp = fdopen(s, "r+");
469	if (fp == NULL)
470		err(EX_OSERR, "fdopen()");
471
472	if (!(flags & WHOIS_SPAM_ME) &&
473	    (strcasecmp(hostname, DENICHOST) == 0 ||
474	     strcasecmp(hostname, "de" QNICHOST_TAIL) == 0)) {
475		const char *q;
476		int idn = 0;
477		for (q = query; *q != '\0'; q++)
478			if (!isascii(*q))
479				idn = 1;
480		fprintf(fp, "-T dn%s %s\r\n", idn ? "" : ",ace", query);
481	} else if (!(flags & WHOIS_SPAM_ME) &&
482		   (strcasecmp(hostname, DKNICHOST) == 0 ||
483		    strcasecmp(hostname, "dk" QNICHOST_TAIL) == 0))
484		fprintf(fp, "--show-handles %s\r\n", query);
485	else if ((flags & WHOIS_SPAM_ME) ||
486		 strchr(query, ' ') != NULL)
487		fprintf(fp, "%s\r\n", query);
488	else if (strcasecmp(hostname, ANICHOST) == 0) {
489		if (strncasecmp(query, "AS", 2) == 0 &&
490		    strspn(query+2, "0123456789") == strlen(query+2))
491			fprintf(fp, "+ a %s\r\n", query+2);
492		else
493			fprintf(fp, "+ %s\r\n", query);
494	} else if (strcasecmp(hostres->ai_canonname, VNICHOST) == 0)
495		fprintf(fp, "domain %s\r\n", query);
496	else
497		fprintf(fp, "%s\r\n", query);
498	fflush(fp);
499
500	comment = 0;
501	if (!(flags & WHOIS_SPAM_ME) &&
502	    (strcasecmp(hostname, ANICHOST) == 0 ||
503	     strcasecmp(hostname, RNICHOST) == 0)) {
504		comment = 2;
505	}
506
507	nhost = NULL;
508	while ((buf = fgetln(fp, &len)) != NULL) {
509		/* Nominet */
510		if (!(flags & WHOIS_SPAM_ME) &&
511		    len == 5 && strncmp(buf, "-- \r\n", 5) == 0)
512			break;
513		/* RIRs */
514		if (comment == 1 && buf[0] == '#')
515			break;
516		else if (comment == 2) {
517			if (strchr("#%\r\n", buf[0]) != NULL)
518				continue;
519			else
520				comment = 1;
521		}
522
523		printf("%.*s", (int)len, buf);
524
525		if ((flags & WHOIS_RECURSE) && nhost == NULL) {
526			for (i = 0; whois_referral[i].prefix != NULL; i++) {
527				p = buf;
528				SCAN(p, buf+len, *p == ' ');
529				if (strncasecmp(p, whois_referral[i].prefix,
530					           whois_referral[i].len) != 0)
531					continue;
532				p += whois_referral[i].len;
533				SCAN(p, buf+len, *p == ' ');
534				host = p;
535				SCAN(p, buf+len, ishost(*p));
536				if (p > host) {
537					char *pstr;
538
539					s_asprintf(&nhost, "%.*s",
540						   (int)(p - host), host);
541
542					if (*p != ':') {
543						s_asprintf(&nport, "%s", port);
544						break;
545					}
546
547					pstr = ++p;
548					SCAN(p, buf+len, isdigit(*p));
549					if (p > pstr && (p - pstr) < 6) {
550						s_asprintf(&nport, "%.*s",
551						    (int)(p - pstr), pstr);
552						break;
553					}
554
555					/* Invalid port; don't recurse */
556					free(nhost);
557					nhost = NULL;
558				}
559				break;
560			}
561			for (i = 0; actually_arin[i] != NULL; i++) {
562				if (strncmp(buf, actually_arin[i], len) == 0) {
563					s_asprintf(&nhost, "%s", ANICHOST);
564					s_asprintf(&nport, "%s", port);
565					break;
566				}
567			}
568		}
569		/* Verisign etc. */
570		if (!(flags & WHOIS_SPAM_ME) &&
571		    len >= sizeof(CHOPSPAM)-1 &&
572		    (strncasecmp(buf, CHOPSPAM, sizeof(CHOPSPAM)-1) == 0 ||
573		     strncasecmp(buf, CHOPSPAM+4, sizeof(CHOPSPAM)-5) == 0)) {
574			printf("\n");
575			break;
576		}
577	}
578	fclose(fp);
579	freeaddrinfo(hostres);
580
581	f = 0;
582	for (i = 0; try_rir[i].host != NULL; i++) {
583		/* Remember visits to RIRs */
584		if (try_rir[i].loop == 0 &&
585		    strcasecmp(try_rir[i].host, hostname) == 0)
586			try_rir[i].loop = 1;
587		/* Do we need to find an alternative RIR? */
588		if (try_rir[i].loop != 0 && nhost != NULL &&
589		    strcasecmp(try_rir[i].host, nhost) == 0) {
590			free(nhost);
591			nhost = NULL;
592			free(nport);
593			nport = NULL;
594			f = 1;
595		}
596	}
597	if (f) {
598		/* Find a replacement RIR */
599		for (i = 0; try_rir[i].host != NULL; i++) {
600			if (try_rir[i].loop == 0) {
601				s_asprintf(&nhost, "%s", try_rir[i].host);
602				s_asprintf(&nport, "%s", port);
603				break;
604			}
605		}
606	}
607	if (nhost != NULL) {
608		/* Ignore self-referrals */
609		if (strcasecmp(hostname, nhost) != 0) {
610			printf("# %s\n\n", nhost);
611			whois(query, nhost, nport, flags);
612		}
613		free(nhost);
614		free(nport);
615	}
616}
617
618static void
619usage(void)
620{
621	fprintf(stderr,
622	    "usage: whois [-aAbfgiIklmPQrRS] [-c country-code | -h hostname] "
623	    "[-p port] name ...\n");
624	exit(EX_USAGE);
625}
626