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