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 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 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 * 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 * 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 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 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 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 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