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