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