summaryrefslogtreecommitdiffstats
path: root/whois.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--whois.c1549
-rw-r--r--whois.conf11
-rw-r--r--whois.conf.549
3 files changed, 1609 insertions, 0 deletions
diff --git a/whois.c b/whois.c
new file mode 100644
index 0000000..cb9d4d0
--- /dev/null
+++ b/whois.c
@@ -0,0 +1,1549 @@
+/*
+ * Copyright (C) 1999-2022 Marco d'Itri <md@linux.it>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+/* for AI_IDN */
+#define _GNU_SOURCE
+
+/* System library */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#ifdef HAVE_GETOPT_LONG
+#include <getopt.h>
+#endif
+#ifdef HAVE_REGEXEC
+#include <regex.h>
+#endif
+#ifdef HAVE_LIBIDN2
+#include <idn2.h>
+#elif defined HAVE_LIBIDN
+#include <idna.h>
+#endif
+
+/* Application-specific */
+#include "version.h"
+#include "data.h"
+#include "whois.h"
+#include "utils.h"
+
+#ifdef HAVE_ICONV
+#include "simple_recode.h"
+#else
+#define recode_fputs(a, b) fputs(a, b)
+#endif
+
+/* hack */
+#define malloc(s) NOFAIL(malloc(s))
+#define realloc(p, s) NOFAIL(realloc(p, s))
+#ifdef strdup
+#undef strdup
+#define strdup(s) NOFAIL(__strdup(s))
+#else
+#define strdup(s) NOFAIL(strdup(s))
+#endif
+
+/* Global variables */
+int sockfd, verb = 0, no_recursion = 0;
+
+#ifdef ALWAYS_HIDE_DISCL
+int hide_discl = HIDE_NOT_STARTED;
+#else
+int hide_discl = HIDE_DISABLED;
+#endif
+
+const char *client_tag = IDSTRING;
+
+#ifndef HAVE_GETOPT_LONG
+extern char *optarg;
+extern int optind;
+#endif
+
+int main(int argc, char *argv[])
+{
+#ifdef HAVE_GETOPT_LONG
+ const struct option longopts[] = {
+ /* program flags */
+ {"version", no_argument, NULL, 1 },
+ {"verbose", no_argument, NULL, 2 },
+ {"help", no_argument, NULL, 3 },
+ {"no-recursion", no_argument, NULL, 4 },
+ {"server", required_argument, NULL, 'h'},
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ /* long RIPE flags */
+ {"exact", required_argument, NULL, 'x'},
+ {"all-more", required_argument, NULL, 'M'},
+ {"one-more", required_argument, NULL, 'm'},
+ {"all-less", required_argument, NULL, 'L'},
+ {"one-less", required_argument, NULL, 'l'},
+ {"reverse-domain", required_argument, NULL, 'd'},
+ {"irt", required_argument, NULL, 'c'},
+ {"abuse-contact", no_argument, NULL, 'b'},
+ {"brief", no_argument, NULL, 'F'},
+ {"primary-keys", no_argument, NULL, 'K'},
+ {"persistent-connection", no_argument, NULL, 'k'},
+ {"no-referenced", no_argument, NULL, 'r'},
+ {"no-filtering", no_argument, NULL, 'B'},
+ {"no-grouping", no_argument, NULL, 'G'},
+ {"select-types", required_argument, NULL, 'T'},
+ {"all-sources", no_argument, NULL, 'a'},
+ {"sources", required_argument, NULL, 's'},
+ {"types", no_argument, NULL, 12 }, /* -q */
+ {"ripe-version", no_argument, NULL, 12 }, /* -q */
+ {"list-sources", no_argument, NULL, 12 }, /* -q */
+ {"template", required_argument, NULL, 't'},
+ {"ripe-verbose", required_argument, NULL, 'v'},
+ /* long RIPE flags with no short equivalent */
+ {"list-versions", no_argument, NULL, 10 },
+ {"diff-versions", required_argument, NULL, 11 },
+ {"show-version", required_argument, NULL, 11 },
+ {"resource", no_argument, NULL, 10 },
+ {"show-personal", no_argument, NULL, 10 },
+ {"no-personal", no_argument, NULL, 10 },
+ {"show-tag-info", no_argument, NULL, 10 },
+ {"no-tag-info", no_argument, NULL, 10 },
+ {"filter-tag-include", required_argument, NULL, 11 },
+ {"filter-tag-exclude", required_argument, NULL, 11 },
+ {NULL, 0, NULL, 0 }
+ };
+ int longindex;
+#endif
+
+ int ch, nopar = 0;
+ size_t fstringlen = 64;
+ const char *server = NULL, *port = NULL;
+ char *qstring, *fstring;
+ int ret;
+
+#ifdef ENABLE_NLS
+ setlocale(LC_ALL, "");
+ bindtextdomain(NLS_CAT_NAME, LOCALEDIR);
+ textdomain(NLS_CAT_NAME);
+#endif
+
+ fstring = malloc(fstringlen + 1);
+ *fstring = '\0';
+
+ /* interface for American Fuzzy Lop */
+ if (AFL_MODE) {
+ FILE *fp = fdopen(0, "r");
+ char *buf = NULL;
+ size_t len = 0;
+
+ /* read one line from stdin */
+ if (getline(&buf, &len, fp) < 0)
+ err_sys("getline");
+ fflush(fp);
+ /* and use it as command line arguments */
+ argv = merge_args(buf, argv, &argc);
+ }
+
+ /* prepend options from environment */
+ argv = merge_args(getenv("WHOIS_OPTIONS"), argv, &argc);
+
+ while ((ch = GETOPT_LONGISH(argc, argv,
+ "abBcdFg:Gh:Hi:IKlLmMp:q:rRs:t:T:v:V:x",
+ longopts, &longindex)) > 0) {
+ /* RIPE flags */
+ if (strchr(ripeflags, ch)) {
+ if (strlen(fstring) + 3 > fstringlen) {
+ fstringlen += 3;
+ fstring = realloc(fstring, fstringlen + 1);
+ }
+ sprintf(fstring + strlen(fstring), "-%c ", ch);
+ continue;
+ }
+ if (strchr(ripeflagsp, ch)) {
+ int flaglen = 3 + strlen(optarg) + 1;
+ if (strlen(fstring) + flaglen > fstringlen) {
+ fstringlen += flaglen;
+ fstring = realloc(fstring, fstringlen + 1);
+ }
+ sprintf(fstring + strlen(fstring), "-%c %s ", ch, optarg);
+ if (ch == 't' || ch == 'v' || ch == 'q')
+ nopar = 1;
+ continue;
+ }
+ switch (ch) {
+#ifdef HAVE_GETOPT_LONG
+ /* long RIPE flags with no short equivalent */
+ case 12:
+ nopar = 1;
+ /* fall through */
+ case 10:
+ {
+ int flaglen = 2 + strlen(longopts[longindex].name) + 1;
+ if (strlen(fstring) + flaglen > fstringlen) {
+ fstringlen += flaglen;
+ fstring = realloc(fstring, fstringlen + 1);
+ }
+ sprintf(fstring + strlen(fstring), "--%s ",
+ longopts[longindex].name);
+ }
+ break;
+ case 11:
+ {
+ int flaglen = 2 + strlen(longopts[longindex].name) + 1
+ + strlen(optarg) + 1;
+ if (strlen(fstring) + flaglen > fstringlen) {
+ fstringlen += flaglen;
+ fstring = realloc(fstring, fstringlen + 1);
+ }
+ sprintf(fstring + strlen(fstring), "--%s %s ",
+ longopts[longindex].name, optarg);
+ }
+ break;
+#endif
+ /* program flags */
+ case 'h':
+ server = strdup(optarg);
+ break;
+ case 'V':
+ client_tag = optarg;
+ break;
+ case 'H':
+ hide_discl = HIDE_NOT_STARTED; /* enable disclaimers hiding */
+ break;
+ case 'I':
+ server = strdup("\x0E");
+ break;
+ case 'p':
+ port = strdup(optarg);
+ break;
+ case 4:
+ no_recursion = 1;
+ break;
+ case 3:
+ usage(EXIT_SUCCESS);
+ case 2:
+ verb = 1;
+ break;
+ case 1:
+ fprintf(stdout, _("Version %s.\n\nReport bugs to %s.\n"),
+ VERSION, "<md+whois@linux.it>");
+ exit(EXIT_SUCCESS);
+ default:
+ usage(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0 && !nopar) /* there is no parameter */
+ usage(EXIT_FAILURE);
+
+ /* On some systems realloc only works on non-NULL buffers */
+ /* I wish I could remember which ones they are... */
+ qstring = malloc(64);
+ *qstring = '\0';
+
+ /* parse other parameters, if any */
+ if (!nopar) {
+ int qstringlen = 0;
+
+ while (1) {
+ qstringlen += strlen(*argv) + 1;
+ qstring = realloc(qstring, qstringlen + 1);
+ strcat(qstring, *argv++);
+ if (argc == 1)
+ break;
+ strcat(qstring, " ");
+ argc--;
+ }
+ }
+
+ signal(SIGTERM, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGALRM, alarm_handler);
+
+ if (getenv("WHOIS_HIDE"))
+ hide_discl = HIDE_NOT_STARTED;
+
+ /* -v or -t or long flags have been used */
+ if (!server && (!*qstring || *fstring))
+ server = strdup("whois.ripe.net");
+
+ if (*qstring) {
+ char *tmp = normalize_domain(qstring);
+ free(qstring);
+ qstring = tmp;
+ }
+
+#ifdef CONFIG_FILE
+ if (!server)
+ server = match_config_file(qstring);
+#endif
+
+ if (!server)
+ server = guess_server(qstring);
+
+ ret = handle_query(server, port, qstring, fstring);
+
+ exit(ret);
+}
+
+/*
+ * Server may be a server name from the command line, a server name got
+ * from guess_server or an encoded command/message from guess_server.
+ * This function has multiple memory leaks.
+ */
+int handle_query(const char *hserver, const char *hport,
+ const char *query, const char *flags)
+{
+ char *server = NULL, *port = NULL;
+ char *p, *query_string;
+
+ if (hport) {
+ server = strdup(hserver);
+ port = strdup(hport);
+ } else if (hserver[0] < ' ')
+ server = strdup(hserver);
+ else
+ split_server_port(hserver, &server, &port);
+
+ retry:
+ switch (server[0]) {
+ case 0:
+ if (!(server = getenv("WHOIS_SERVER")))
+ server = strdup(DEFAULTSERVER);
+ break;
+ case 1:
+ puts(_("This TLD has no whois server, but you can access the "
+ "whois database at"));
+ puts(server + 1);
+ return 1;
+ case 3:
+ puts(_("This TLD has no whois server."));
+ return 1;
+ case 5:
+ puts(_("No whois server is known for this kind of object."));
+ return 1;
+ case 6:
+ puts(_("Unknown AS number or IP network. Please upgrade this program."));
+ return 1;
+ case 4:
+ if (verb)
+ printf(_("Using server %s.\n"), server + 1);
+ sockfd = openconn(server + 1, NULL);
+ free(server);
+ server = query_crsnic(sockfd, query);
+ if (no_recursion)
+ server[0] = '\0';
+ break;
+ case 8:
+ if (verb)
+ printf(_("Using server %s.\n"), server + 1);
+ sockfd = openconn(server + 1, NULL);
+ free(server);
+ server = query_afilias(sockfd, query);
+ if (no_recursion)
+ server[0] = '\0';
+ break;
+ case 0x0A:
+ p = convert_6to4(query);
+ printf(_("\nQuerying for the IPv4 endpoint %s of a 6to4 IPv6 address.\n\n"), p);
+ free(server);
+ server = guess_server(p);
+ query = p;
+ goto retry;
+ case 0x0B:
+ p = convert_teredo(query);
+ printf(_("\nQuerying for the IPv4 endpoint %s of a Teredo IPv6 address.\n\n"), p);
+ free(server);
+ server = guess_server(p);
+ query = p;
+ goto retry;
+ case 0x0C:
+ p = convert_inaddr(query);
+ free(server);
+ server = guess_server(p);
+ free(p);
+ goto retry;
+ case 0x0D:
+ p = convert_in6arpa(query);
+ free(server);
+ server = guess_server(p);
+ free(p);
+ goto retry;
+ case 0x0E:
+ if (verb)
+ printf(_("Using server %s.\n"), "whois.iana.org");
+ sockfd = openconn("whois.iana.org", NULL);
+ free(server);
+ server = query_iana(sockfd, query);
+ break;
+ default:
+ break;
+ }
+
+ if (!server)
+ return 1;
+
+ if (*server == '\0')
+ return 0;
+
+ query_string = queryformat(server, flags, query);
+ if (verb) {
+ printf(_("Using server %s.\n"), server);
+ printf(_("Query string: \"%s\"\n\n"), query_string);
+ }
+
+ sockfd = openconn(server, port);
+
+ server = do_query(sockfd, query_string);
+ free(query_string);
+
+ /* recursion is fun */
+ if (!no_recursion && server && !strchr(query, ' ')) {
+ printf(_("\n\nFound a referral to %s.\n\n"), server);
+ handle_query(server, NULL, query, flags);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_FILE
+const char *match_config_file(const char *s)
+{
+ FILE *fp;
+ char buf[512];
+ static const char delim[] = " \t";
+
+ if ((fp = fopen(CONFIG_FILE, "r")) == NULL) {
+ if (errno != ENOENT)
+ err_sys("Cannot open " CONFIG_FILE);
+ return NULL;
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *p;
+ const char *pattern, *server;
+#ifdef HAVE_REGEXEC
+ int i;
+ regex_t re;
+#endif
+
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+
+ p = buf;
+ while (*p == ' ' || *p == '\t') /* eat leading blanks */
+ p++;
+ if (!*p)
+ continue; /* skip empty lines */
+ if (*p == '#')
+ continue; /* skip comments */
+
+ pattern = strtok(p, delim);
+ server = strtok(NULL, delim);
+ if (!pattern || !server)
+ err_quit(_("Cannot parse this line: %s"), p);
+ p = strtok(NULL, delim);
+ if (p)
+ err_quit(_("Cannot parse this line: %s"), p);
+
+#ifdef HAVE_REGEXEC
+ i = regcomp(&re, pattern, REG_EXTENDED | REG_ICASE | REG_NOSUB);
+ if (i != 0) {
+ char m[1024];
+ regerror(i, &re, m, sizeof(m));
+ err_quit("Invalid regular expression '%s': %s", pattern, m);
+ }
+
+ i = regexec(&re, s, 0, NULL, 0);
+ if (i == 0) {
+ regfree(&re);
+ fclose(fp);
+ return strdup(server);
+ }
+ if (i != REG_NOMATCH) {
+ char m[1024];
+ regerror(i, &re, m, sizeof(m));
+ err_quit("regexec: %s", m);
+ }
+ regfree(&re);
+#else
+ if (endstrcaseeq(s, pattern)) {
+ fclose(fp);
+ return strdup(server);
+ }
+#endif
+ }
+ fclose(fp);
+ return NULL;
+}
+#endif
+
+/* Parses an user-supplied string and tries to guess the right whois server.
+ * Returns a dynamically allocated buffer.
+ */
+char *guess_server(const char *s)
+{
+ unsigned long ip, as32;
+ unsigned int i;
+ const char *colon, *tld;
+
+ /* IPv6 address */
+ if ((colon = strchr(s, ':'))) {
+ unsigned long v6prefix, v6net;
+
+ /* RPSL hierarchical objects */
+ if (strncaseeq(s, "as", 2)) {
+ if (isasciidigit(s[2]))
+ return strdup(whereas(atol(s + 2)));
+ else
+ return strdup("");
+ }
+
+ v6prefix = strtol(s, NULL, 16);
+
+ if (v6prefix == 0)
+ return strdup("\x05"); /* unknown */
+
+ v6net = (v6prefix << 16) + strtol(colon + 1, NULL, 16);/* second u16 */
+
+ for (i = 0; ip6_assign[i].serv; i++) {
+ if ((v6net & (~0UL << (32 - ip6_assign[i].masklen)))
+ == ip6_assign[i].net)
+ return strdup(ip6_assign[i].serv);
+ }
+
+ return strdup("\x06"); /* unknown allocation */
+ }
+
+ /* email address */
+ if (strchr(s, '@'))
+ return strdup("\x05");
+
+ if (!strpbrk(s, ".")) {
+ /* if it is a TLD or a new gTLD then ask IANA */
+ for (i = 0; tld_serv[i]; i += 2)
+ if (strcaseeq(s, tld_serv[i]))
+ return strdup("whois.iana.org");
+
+ for (i = 0; new_gtlds[i]; i++)
+ if (strcaseeq(s, new_gtlds[i]))
+ return strdup("whois.iana.org");
+ }
+
+ /* no dot and no hyphen means it's a NSI NIC handle or ASN (?) */
+ if (!strpbrk(s, ".-")) {
+ if (strncaseeq(s, "as", 2) && /* it's an AS */
+ (isasciidigit(s[2]) || s[2] == ' '))
+ return strdup(whereas(atol(s + 2)));
+ if (*s == '!') /* NSI NIC handle */
+ return strdup("whois.networksolutions.com");
+ else
+ return strdup("\x05"); /* probably a unknown kind of nic handle */
+ }
+
+ /* ASN32? */
+ if (strncaseeq(s, "as", 2) && s[2] && (as32 = asn32_to_long(s + 2)) != 0)
+ return strdup(whereas32(as32));
+
+ /* smells like an IP? */
+ if ((ip = myinet_aton(s))) {
+ for (i = 0; ip_assign[i].serv; i++)
+ if ((ip & ip_assign[i].mask) == ip_assign[i].net)
+ return strdup(ip_assign[i].serv);
+ return strdup("\x05"); /* not in the unicast IPv4 space */
+ }
+
+ /* check the TLDs list */
+ for (i = 0; tld_serv[i]; i += 2)
+ if (in_domain(s, tld_serv[i]))
+ return strdup(tld_serv[i + 1]);
+
+ /* use the default server name for "new" gTLDs */
+ if ((tld = is_new_gtld(s))) {
+ char *server = malloc(strlen("whois.nic.") + strlen(tld) + 1);
+ strcpy(server, "whois.nic.");
+ strcat(server, tld);
+ return server;
+ }
+
+ /* no dot but hyphen */
+ if (!strchr(s, '.')) {
+ /* search for strings at the start of the word */
+ for (i = 0; nic_handles[i]; i += 2)
+ if (strncaseeq(s, nic_handles[i], strlen(nic_handles[i])))
+ return strdup(nic_handles[i + 1]);
+
+ /* search for strings at the end of the word */
+ for (i = 0; nic_handles_post[i]; i += 2)
+ if (endstrcaseeq(s, nic_handles_post[i]))
+ return strdup(nic_handles_post[i + 1]);
+
+ /* it's probably a network name */
+ return strdup("");
+ }
+
+ /* has dot and maybe a hyphen and it's not in tld_serv[], WTF is it? */
+ /* either a TLD or a NIC handle we don't know about yet */
+ return strdup("\x05");
+}
+
+const char *whereas32(const unsigned long asn)
+{
+ int i;
+
+ for (i = 0; as32_assign[i].serv; i++)
+ if (asn >= as32_assign[i].first && asn <= as32_assign[i].last)
+ return as32_assign[i].serv;
+ return "\x06";
+}
+
+const char *whereas(const unsigned long asn)
+{
+ int i;
+
+ if (asn > 65535)
+ return whereas32(asn);
+
+ for (i = 0; as_assign[i].serv; i++)
+ if (asn >= as_assign[i].first && asn <= as_assign[i].last)
+ return as_assign[i].serv;
+ return "\x06";
+}
+
+/*
+ * Construct the query string.
+ * Determines the server character set as a side effect.
+ * Returns a malloc'ed string which needs to be freed by the caller.
+ */
+char *queryformat(const char *server, const char *flags, const char *query)
+{
+ char *buf;
+ int i, isripe = 0;
+
+ /* 64 bytes reserved for server-specific flags added later */
+ buf = malloc(strlen(flags) + strlen(query) + strlen(client_tag) + 64);
+ *buf = '\0';
+
+ for (i = 0; ripe_servers[i]; i++)
+ if (streq(server, ripe_servers[i])) {
+ sprintf(buf + strlen(buf), "-V %s ", client_tag);
+ isripe = 1;
+ break;
+ }
+
+ if (*flags) {
+ if (!isripe)
+ puts(_("Warning: RIPE flags used with a traditional server."));
+ strcat(buf, flags);
+ }
+
+#ifdef HAVE_ICONV
+ simple_recode_iconv_close();
+ for (i = 0; servers_charset[i].name; i++)
+ if (streq(server, servers_charset[i].name)) {
+ simple_recode_input_charset = servers_charset[i].charset;
+ if (servers_charset[i].options) {
+ strcat(buf, servers_charset[i].options);
+ strcat(buf, " ");
+ }
+ break;
+ }
+
+ /* Use UTF-8 by default for "new" gTLDs */
+ if (!simple_recode_input_charset && /* was not in the database */
+ !strchr(query, ' ') && /* and has no parameters */
+ is_new_gtld(query)) /* and is a "new" gTLD: */
+ simple_recode_input_charset = "utf-8"; /* then try UTF-8 */
+#endif
+
+#if defined HAVE_LIBIDN || defined HAVE_LIBIDN2
+# define DENIC_PARAM_ACE ",ace"
+#else
+# define DENIC_PARAM_ACE ""
+#endif
+#ifdef HAVE_ICONV
+# define DENIC_PARAM_CHARSET ""
+#else
+# define DENIC_PARAM_CHARSET " -C US-ASCII"
+#endif
+
+ /* add useful default flags if there are no flags or multiple arguments */
+ if (isripe) { }
+ else if (strchr(query, ' ') || *flags) { }
+ else if (streq(server, "whois.denic.de") && in_domain(query, "de"))
+ strcat(buf, "-T dn" DENIC_PARAM_ACE DENIC_PARAM_CHARSET " ");
+ else if (streq(server, "whois.dk-hostmaster.dk") && in_domain(query, "dk"))
+ strcat(buf, "--show-handles ");
+
+ /* mangle and add the query string */
+ if (!isripe && streq(server, "whois.nic.ad.jp") &&
+ strncaseeq(query, "AS", 2) && isasciidigit(query[2])) {
+ strcat(buf, "AS ");
+ strcat(buf, query + 2);
+ }
+ else if (!isripe && streq(server, "whois.arin.net") &&
+ !strrchr(query, ' ')) {
+ if (strncaseeq(query, "AS", 2) && isasciidigit(query[2])) {
+ strcat(buf, "a ");
+ strcat(buf, query + 2);
+ } else if (myinet_aton(query) || strchr(query, ':')) {
+ if (strchr(query, '/'))
+ strcat(buf, "r + = ");
+ else
+ strcat(buf, "n + ");
+ strcat(buf, query);
+ } else
+ strcat(buf, query);
+ }
+ else
+ strcat(buf, query);
+
+ /* ask for english text */
+ if (!isripe && (streq(server, "whois.nic.ad.jp") ||
+ streq(server, "whois.jprs.jp")) && japanese_locale())
+ strcat(buf, "/e");
+
+ return buf;
+}
+
+/* the first parameter contains the state of this simple state machine:
+ * HIDE_DISABLED: hidden text finished
+ * HIDE_NOT_STARTED: hidden text not seen yet
+ * >= 0: currently hiding message hide_strings[*hiding]
+ */
+int hide_line(int *hiding, const char *const line)
+{
+ int i;
+
+ if (*hiding == HIDE_TO_THE_END) {
+ return 1;
+ } else if (*hiding == HIDE_DISABLED) {
+ return 0;
+ } else if (*hiding == HIDE_NOT_STARTED) { /* looking for smtng to hide */
+ for (i = 0; hide_strings[i] != NULL; i += 2) {
+ if (strneq(line, hide_strings[i], strlen(hide_strings[i]))) {
+ if (hide_strings[i + 1] == NULL)
+ *hiding = HIDE_TO_THE_END; /* all the remaining output */
+ else
+ *hiding = i; /* start hiding */
+ return 1; /* and hide this line */
+ }
+ }
+ return 0; /* don't hide this line */
+ } else if (*hiding > HIDE_NOT_STARTED) { /* hiding something */
+ if (*hide_strings[*hiding + 1] == '\0') { /*look for a blank line?*/
+ if (*line == '\n' || *line == '\r' || *line == '\0') {
+ *hiding = HIDE_NOT_STARTED; /* stop hiding */
+ return 0; /* but do not hide the blank line */
+ }
+ } else { /*look for a matching string*/
+ if (strneq(line, hide_strings[*hiding + 1],
+ strlen(hide_strings[*hiding + 1]))) {
+ *hiding = HIDE_NOT_STARTED; /* stop hiding */
+ return 1; /* but hide the last line */
+ }
+ }
+ return 1; /* we are hiding, so do it */
+ } else
+ return 0;
+}
+
+/* returns a string which should be freed by the caller, or NULL */
+char *do_query(const int sock, const char *query)
+{
+ char *temp, *p, buf[2000];
+ FILE *fi;
+ int hide = hide_discl;
+ char *referral_server = NULL;
+
+ temp = malloc(strlen(query) + 2 + 1);
+ strcpy(temp, query);
+ strcat(temp, "\r\n");
+
+ fi = fdopen(sock, "r");
+ if (write(sock, temp, strlen(temp)) < 0)
+ err_sys("write");
+ free(temp);
+
+ while (fgets(buf, sizeof(buf), fi)) {
+ /* 6bone-style referral:
+ * % referto: whois -h whois.arin.net -p 43 as 1
+ */
+ if (!referral_server && strneq(buf, "% referto:", 10)) {
+ char nh[256], np[16], nq[1024];
+
+ if (sscanf(buf, REFERTO_FORMAT, nh, np, nq) == 3) {
+ /* XXX we are ignoring the new query string */
+ referral_server = malloc(strlen(nh) + 1 + strlen(np) + 1);
+ sprintf(referral_server, "%s:%s", nh, np);
+ }
+ }
+
+ /* ARIN referrals:
+ * ReferralServer: rwhois://rwhois.fuse.net:4321/
+ * ReferralServer: whois://whois.ripe.net
+ */
+ if (!referral_server && strneq(buf, "ReferralServer:", 15)) {
+ if ((p = strstr(buf, "rwhois://")))
+ referral_server = strdup(p + 9);
+ else if ((p = strstr(buf, "whois://")))
+ referral_server = strdup(p + 8);
+ if (referral_server && (p = strpbrk(referral_server, "/\r\n")))
+ *p = '\0';
+ }
+
+ if (hide_line(&hide, buf))
+ continue;
+
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+ recode_fputs(buf, stdout);
+ fputc('\n', stdout);
+ }
+
+ if (ferror(fi))
+ err_sys("fgets");
+ fclose(fi);
+
+ if (hide > HIDE_NOT_STARTED && hide != HIDE_TO_THE_END)
+ err_quit(_("Catastrophic error: disclaimer text has been changed.\n"
+ "Please upgrade this program.\n"));
+
+ return referral_server;
+}
+
+char *query_crsnic(const int sock, const char *query)
+{
+ char *temp, *p, buf[2000];
+ FILE *fi;
+ int hide = hide_discl;
+ char *referral_server = NULL;
+ int state = 0;
+ int dotscount = 0;
+
+ temp = malloc(strlen("domain ") + strlen(query) + 2 + 1);
+ *temp = '\0';
+
+ /* if this has more than one dot then it is a name server */
+ for (p = (char *) query; *p != '\0'; p++)
+ if (*p == '.')
+ dotscount++;
+
+ if (dotscount == 1 && !strpbrk(query, "=~ "))
+ strcpy(temp, "domain ");
+ strcat(temp, query);
+ strcat(temp, "\r\n");
+
+ fi = fdopen(sock, "r");
+ if (write(sock, temp, strlen(temp)) < 0)
+ err_sys("write");
+ free(temp);
+
+ while (fgets(buf, sizeof(buf), fi)) {
+ /* If there are multiple matches only the server of the first record
+ is queried */
+ if (state == 0 && strneq(buf, " Domain Name:", 15))
+ state = 1;
+ if (state == 0 && strneq(buf, " Server Name:", 15)) {
+ referral_server = strdup("");
+ state = 2;
+ }
+ if (state == 1 && strneq(buf, " Registrar WHOIS Server:", 26)) {
+ for (p = buf; *p != ':'; p++); /* skip until the colon */
+ for (p++; *p == ' '; p++); /* skip the spaces */
+ referral_server = strdup(p);
+ if ((p = strpbrk(referral_server, "\r\n ")))
+ *p = '\0';
+ state = 2;
+ }
+
+ /* the output must not be hidden or no data will be shown for
+ host records and not-existing domains */
+ if (hide_line(&hide, buf))
+ continue;
+
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+ recode_fputs(buf, stdout);
+ fputc('\n', stdout);
+ }
+
+ if (ferror(fi))
+ err_sys("fgets");
+ fclose(fi);
+
+ return referral_server;
+}
+
+char *query_afilias(const int sock, const char *query)
+{
+ char *temp, *p, buf[2000];
+ FILE *fi;
+ int hide = hide_discl;
+ char *referral_server = NULL;
+ int state = 0;
+
+ temp = malloc(strlen(query) + 2 + 1);
+ strcpy(temp, query);
+ strcat(temp, "\r\n");
+
+ fi = fdopen(sock, "r");
+ if (write(sock, temp, strlen(temp)) < 0)
+ err_sys("write");
+ free(temp);
+
+ while (fgets(buf, sizeof(buf), fi)) {
+ /* If multiple attributes are returned then use the first result.
+ This is not supposed to happen. */
+ if (state == 0 && strneq(buf, "Domain Name:", 12))
+ state = 1;
+ if (state == 1 && strneq(buf, "Registrar WHOIS Server:", 23)) {
+ for (p = buf; *p != ':'; p++); /* skip until colon */
+ for (p++; *p == ' '; p++); /* skip colon and spaces */
+ referral_server = strdup(p);
+ if ((p = strpbrk(referral_server, "\r\n ")))
+ *p = '\0';
+ state = 2;
+ }
+
+ /* the output must not be hidden or no data will be shown for
+ host records and not-existing domains */
+ if (hide_line(&hide, buf))
+ continue;
+
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+ recode_fputs(buf, stdout);
+ fputc('\n', stdout);
+ }
+
+ if (ferror(fi))
+ err_sys("fgets");
+ fclose(fi);
+
+ if (hide > HIDE_NOT_STARTED && hide != HIDE_TO_THE_END)
+ err_quit(_("Catastrophic error: disclaimer text has been changed.\n"
+ "Please upgrade this program.\n"));
+
+ return referral_server;
+}
+
+char *query_iana(const int sock, const char *query)
+{
+ char *temp, *p, buf[2000];
+ FILE *fi;
+ char *referral_server = NULL;
+ int state = 0;
+
+ temp = malloc(strlen(query) + 2 + 1);
+ strcpy(temp, query);
+ strcat(temp, "\r\n");
+
+ fi = fdopen(sock, "r");
+ if (write(sock, temp, strlen(temp)) < 0)
+ err_sys("write");
+ free(temp);
+
+ while (fgets(buf, sizeof(buf), fi)) {
+ /* If multiple attributes are returned then use the first result.
+ This is not supposed to happen. */
+ if (state == 0 && strneq(buf, "refer:", 6)) {
+ for (p = buf; *p != ':'; p++); /* skip until colon */
+ for (p++; *p == ' '; p++); /* skip colon and spaces */
+ referral_server = strdup(p);
+ if ((p = strpbrk(referral_server, "\r\n ")))
+ *p = '\0';
+ state = 2;
+ }
+
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+ recode_fputs(buf, stdout);
+ fputc('\n', stdout);
+ }
+
+ if (ferror(fi))
+ err_sys("fgets");
+ fclose(fi);
+
+ return referral_server;
+}
+
+int openconn(const char *server, const char *port)
+{
+ int fd = -1;
+ int timeout = 10;
+#ifdef HAVE_GETADDRINFO
+ int err;
+ struct addrinfo hints, *res, *ai;
+#else
+ struct hostent *hostinfo;
+ struct servent *servinfo;
+ struct sockaddr_in saddr;
+#endif
+
+ /*
+ * When using American Fuzzy Lop get the data from it using stdin
+ * instead of connecting to the actual whois server.
+ */
+ if (AFL_MODE)
+ return dup(0);
+
+ alarm(60);
+
+#ifdef HAVE_GETADDRINFO
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+ hints.ai_flags |= AI_IDN;
+
+ if ((err = getaddrinfo(server, port ? port : "nicname", &hints, &res))
+ != 0) {
+ if (err == EAI_SYSTEM)
+ err_sys("getaddrinfo(%s)", server);
+ else
+ err_quit("getaddrinfo(%s): %s", server, gai_strerror(err));
+ }
+
+ for (ai = res; ai; ai = ai->ai_next) {
+ /* no timeout for the last address. is this a good idea? */
+ if (!ai->ai_next)
+ timeout = 0;
+ if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
+ continue; /* ignore */
+ if (connect_with_timeout(fd, (struct sockaddr *)ai->ai_addr,
+ ai->ai_addrlen, timeout) == 0)
+ break; /* success */
+ close(fd);
+ }
+ freeaddrinfo(res);
+
+ if (!ai)
+ err_sys("connect");
+#else
+ if ((hostinfo = gethostbyname(server)) == NULL)
+ err_quit(_("Host %s not found."), server);
+ if ((fd = socket(PF_INET, SOCK_STREAM, IPPROTO_IP)) < 0)
+ err_sys("socket");
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
+ saddr.sin_family = AF_INET;
+ if (!port) {
+ saddr.sin_port = htons(43);
+ } else if ((saddr.sin_port = htons(atoi(port))) == 0) {
+ if ((servinfo = getservbyname(port, "tcp")) == NULL)
+ err_quit(_("%s/tcp: unknown service"), port);
+ saddr.sin_port = servinfo->s_port;
+ }
+ if (connect_with_timeout(fd, (struct sockaddr *)&saddr, sizeof(saddr),
+ timeout) < 0)
+ err_sys("connect");
+#endif
+
+ return fd;
+}
+
+int connect_with_timeout(int fd, const struct sockaddr *addr,
+ socklen_t addrlen, int timeout)
+{
+ int savedflags, rc, connect_errno, opt;
+ unsigned int len;
+ fd_set fd_w;
+ struct timeval tv;
+
+ if (timeout <= 0)
+ return connect(fd, addr, addrlen);
+
+ if ((savedflags = fcntl(fd, F_GETFL, 0)) < 0)
+ return -1;
+
+ /* set the socket non-blocking, so connect(2) will return immediately */
+ if (fcntl(fd, F_SETFL, savedflags | O_NONBLOCK) < 0)
+ return -1;
+
+ rc = connect(fd, addr, addrlen);
+
+ /* set the socket to block again */
+ connect_errno = errno;
+ if (fcntl(fd, F_SETFL, savedflags) < 0)
+ return -1;
+ errno = connect_errno;
+
+ if (rc == 0 || errno != EINPROGRESS)
+ return rc;
+
+ FD_ZERO(&fd_w);
+ FD_SET(fd, &fd_w);
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ /* loop until an error or the timeout has expired */
+ do {
+ rc = select(fd + 1, NULL, &fd_w, NULL, &tv);
+ } while (rc == -1 && errno == EINTR);
+
+ if (rc == 0) { /* timed out */
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ if (rc < 0 || rc > 1) /* select failed */
+ return rc;
+
+ /* rc == 1: success. check for errors */
+ len = sizeof(opt);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &opt, &len) < 0)
+ return -1;
+
+ /* and report them */
+ if (opt != 0) {
+ errno = opt;
+ return -1;
+ }
+
+ return 0;
+}
+
+void NORETURN alarm_handler(int signum)
+{
+ close(sockfd);
+ err_quit(_("Timeout."));
+}
+
+void NORETURN sighandler(int signum)
+{
+ close(sockfd);
+ err_quit(_("Interrupted by signal %d..."), signum);
+}
+
+int japanese_locale(void)
+{
+ char *lang;
+
+ lang = getenv("LC_MESSAGES");
+ if (lang) {
+ if (strneq(lang, "ja", 2))
+ return 0;
+ return 1;
+ }
+
+ lang = getenv("LANG");
+ if (lang && strneq(lang, "ja", 2))
+ return 0;
+ return 1;
+}
+
+/* check if dom ends with tld */
+int endstrcaseeq(const char *dom, const char *tld)
+{
+ size_t dom_len, tld_len;
+ const char *p = NULL;
+
+ if ((dom_len = strlen(dom)) == 0)
+ return 0;
+
+ if ((tld_len = strlen(tld)) == 0)
+ return 0;
+
+ /* dom cannot be shorter than what we are looking for */
+ if (tld_len > dom_len)
+ return 0;
+
+ p = dom + dom_len - tld_len;
+
+ return strcaseeq(p, tld);
+}
+
+/* check if dom is a subdomain of tld */
+int in_domain(const char *dom, const char *tld)
+{
+ size_t dom_len, tld_len;
+ const char *p = NULL;
+
+ if ((dom_len = strlen(dom)) == 0)
+ return 0;
+
+ if ((tld_len = strlen(tld)) == 0)
+ return 0;
+
+ /* dom cannot be shorter than what we are looking for */
+ /* -1 to ignore dom containing just a dot and tld */
+ if (tld_len >= dom_len - 1)
+ return 0;
+
+ p = dom + dom_len - tld_len;
+
+ /* fail if the character before tld is not a dot */
+ if (*(p - 1) != '.')
+ return 0;
+
+ return strcaseeq(p, tld);
+}
+
+const char *is_new_gtld(const char *s)
+{
+ int i;
+
+ for (i = 0; new_gtlds[i]; i++)
+ if (in_domain(s, new_gtlds[i]))
+ return new_gtlds[i];
+
+ return NULL;
+}
+
+/*
+ * Attempt to normalize a query by removing trailing dots and whitespace,
+ * then convert the domain to punycode.
+ * The function assumes that the domain is the last token of the query.
+ * Returns a malloc'ed string which needs to be freed by the caller.
+ */
+char *normalize_domain(const char *dom)
+{
+ char *p, *ret;
+#if defined HAVE_LIBIDN || defined HAVE_LIBIDN2
+ char *domain_start = NULL;
+#endif
+
+ ret = strdup(dom);
+ /* start from the last character */
+ p = ret + strlen(ret) - 1;
+ /* and then eat trailing dots and blanks */
+ while (p > ret) {
+ if (!(*p == '.' || *p == ' ' || *p == '\t'))
+ break;
+ *p = '\0';
+ p--;
+ }
+
+#if defined HAVE_LIBIDN || defined HAVE_LIBIDN2
+ /* find the start of the last word if there are spaces in the query */
+ for (p = ret; *p; p++)
+ if (*p == ' ')
+ domain_start = p + 1;
+
+ if (domain_start) {
+ char *q, *r;
+ int prefix_len;
+
+#ifdef HAVE_LIBIDN2
+ if (idn2_lookup_ul(domain_start, &q, IDN2_NONTRANSITIONAL) != IDN2_OK)
+ return ret;
+#else
+ if (idna_to_ascii_lz(domain_start, &q, 0) != IDNA_SUCCESS)
+ return ret;
+#endif
+
+ /* reassemble the original query in a new buffer */
+ prefix_len = domain_start - ret;
+ r = malloc(prefix_len + strlen(q) + 1);
+ strncpy(r, ret, prefix_len);
+ r[prefix_len] = '\0';
+ strcat(r, q);
+
+ free(q);
+ free(ret);
+ return r;
+ } else {
+ char *q;
+
+#ifdef HAVE_LIBIDN2
+ if (idn2_lookup_ul(ret, &q, IDN2_NONTRANSITIONAL) != IDN2_OK)
+ return ret;
+#else
+ if (idna_to_ascii_lz(ret, &q, 0) != IDNA_SUCCESS)
+ return ret;
+#endif
+
+ free(ret);
+ return q;
+ }
+#else
+ return ret;
+#endif
+}
+
+/* server and port have to be freed by the caller */
+void split_server_port(const char *const input,
+ char **server, char **port)
+{
+ char *p;
+
+ if (*input == '[' && (p = strchr(input, ']'))) { /* IPv6 */
+ char *s;
+ int len = p - input - 1;
+
+ *server = s = malloc(len + 1);
+ memcpy(s, input + 1, len);
+ *(s + len) = '\0';
+
+ p = strchr(p, ':');
+ if (p && *(p + 1) != '\0')
+ *port = strdup(p + 1); /* IPv6 + port */
+ } else if ((p = strchr(input, ':')) && /* IPv6, no port */
+ strchr(p + 1, ':')) { /* and no brackets */
+ *server = strdup(input);
+ } else if ((p = strchr(input, ':'))) { /* IPv4 + port */
+ char *s;
+ int len = p - input;
+
+ *server = s = malloc(len + 1);
+ memcpy(s, input, len);
+ *(s + len) = '\0';
+
+ p++;
+ if (*p != '\0')
+ *port = strdup(p);
+ } else { /* IPv4, no port */
+ *server = strdup(input);
+ }
+
+ /* change the server name to lower case */
+ for (p = (char *) *server; *p; p++)
+ *p = tolower(*p);
+}
+
+char *convert_6to4(const char *s)
+{
+ char *new;
+ int items;
+ unsigned int a, b;
+ char c;
+
+ items = sscanf(s, "2002:%x:%x%c", &a, &b, &c);
+
+ if (items <= 0 || items == 2 || (items == 3 && c != ':'))
+ return strdup("0.0.0.0");
+
+ if (items == 1) {
+ items = sscanf(s, "2002:%x:%c", &a, &c);
+ if (items != 2 || c != ':')
+ return strdup("0.0.0.0");
+ b = 0;
+ }
+
+ new = malloc(sizeof("255.255.255.255"));
+ sprintf(new, "%u.%u.%u.%u", a >> 8, a & 0xff, b >> 8, b & 0xff);
+
+ return new;
+}
+
+char *convert_teredo(const char *s)
+{
+ char *new;
+ unsigned int a, b;
+
+ if (sscanf(s, "2001:%*[^:]:%*[^:]:%*[^:]:%*[^:]:%*[^:]:%x:%x", &a, &b) != 2)
+ return strdup("0.0.0.0");
+
+ a ^= 0xffff;
+ b ^= 0xffff;
+ new = malloc(sizeof("255.255.255.255"));
+ sprintf(new, "%u.%u.%u.%u", a >> 8, a & 0xff, b >> 8, b & 0xff);
+
+ return new;
+}
+
+char *convert_inaddr(const char *s)
+{
+ char *new;
+ char *endptr;
+ long int a, b = 0, c = 0;
+
+ errno = 0;
+
+ a = strtol(s, &endptr, 10);
+ if (errno || a < 0 || a > 255 || *endptr != '.')
+ return strdup("0.0.0.0");
+
+ if (in_domain(endptr + 1, "in-addr.arpa")) {
+ b = strtol(endptr + 1, &endptr, 10); /* 1.2. */
+ if (errno || b < 0 || b > 255 || *endptr != '.')
+ return strdup("0.0.0.0");
+
+ if (in_domain(endptr + 1, "in-addr.arpa")) {
+ c = strtol(endptr + 1, &endptr, 10); /* 1.2.3. */
+ if (errno || c < 0 || c > 255 || *endptr != '.')
+ return strdup("0.0.0.0");
+
+ if (in_domain(endptr + 1, "in-addr.arpa"))
+ return strdup("0.0.0.0");
+ } else {
+ c = b; b = a; a = 0;
+ }
+ } else {
+ c = a; a = 0;
+ }
+
+ new = malloc(sizeof("255.255.255.255"));
+ sprintf(new, "%ld.%ld.%ld.0", c, b, a);
+ return new;
+}
+
+char *convert_in6arpa(const char *s)
+{
+ char *ip, *p;
+ int character = 0;
+ int digits = 1;
+
+ ip = malloc(40);
+
+ p = strstr(s, ".ip6.arpa");
+ if (!p || p == s) {
+ ip[character] = '\0';
+ return ip;
+ }
+
+ /* start from the first character before ".ip6.arpa" */
+ p--;
+
+ while (1) {
+ /* check that this is a valid digit for an IPv6 address */
+ if (!((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') ||
+ (*p >= 'A' && *p <= 'F'))) {
+ ip[character] = '\0';
+ return ip;
+ }
+
+ /* copy the digit to the IP address */
+ ip[character++] = *p;
+
+ /* stop if we have reached the beginning of the string */
+ if (p == s)
+ break;
+
+ /* stop if we have parsed a complete address */
+ if (character == 39)
+ break;
+
+ /* add the colon separator every four digits */
+ if ((digits++ % 4) == 0)
+ ip[character++] = ':';
+
+ /* go to the precedent character and abort if it is not a dot */
+ p--;
+ if (*p != '.') {
+ ip[character] = '\0';
+ return ip;
+ }
+
+ /* abort if the string starts with the dot */
+ if (p == s) {
+ ip[character] = '\0';
+ return ip;
+ }
+
+ /* go to the precedent character and continue */
+ p--;
+ }
+
+ /* terminate the string */
+ ip[character] = '\0';
+ return ip;
+}
+
+unsigned long myinet_aton(const char *s)
+{
+ unsigned long a, b, c, d;
+ int elements;
+ char junk;
+
+ if (!s)
+ return 0;
+ elements = sscanf(s, "%lu.%lu.%lu.%lu%c", &a, &b, &c, &d, &junk);
+ if (!(elements == 4 || (elements == 5 && junk == '/')))
+ return 0;
+ if (a > 255 || b > 255 || c > 255 || d > 255)
+ return 0;
+ return (a << 24) + (b << 16) + (c << 8) + d;
+}
+
+unsigned long asn32_to_long(const char *s)
+{
+ unsigned long a, b;
+ char junk;
+
+ if (!s)
+ return 0;
+ if (sscanf(s, "%lu.%lu%c", &a, &b, &junk) != 2)
+ return 0;
+ if (a > 65535 || b > 65535)
+ return 0;
+ return (a << 16) + b;
+}
+
+int isasciidigit(const char c)
+{
+ return (c >= '0' && c <= '9') ? 1 : 0;
+}
+
+/* http://www.ripe.net/ripe/docs/databaseref-manual.html */
+
+void NORETURN usage(int error)
+{
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"Usage: whois [OPTION]... OBJECT...\n\n"
+"-h HOST, --host HOST connect to server HOST\n"
+"-p PORT, --port PORT connect to PORT\n"
+"-I query whois.iana.org and follow its referral\n"
+"-H hide legal disclaimers\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+" --verbose explain what is being done\n"
+" --no-recursion disable recursion from registry to registrar servers\n"
+" --help display this help and exit\n"
+" --version output version information and exit\n"
+"\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"These flags are supported by whois.ripe.net and some RIPE-like servers:\n"
+"-l find the one level less specific match\n"
+"-L find all levels less specific matches\n"
+"-m find all one level more specific matches\n"
+"-M find all levels of more specific matches\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"-c find the smallest match containing a mnt-irt attribute\n"
+"-x exact match\n"
+"-b return brief IP address ranges with abuse contact\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"-B turn off object filtering (show email addresses)\n"
+"-G turn off grouping of associated objects\n"
+"-d return DNS reverse delegation objects too\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"-i ATTR[,ATTR]... do an inverse look-up for specified ATTRibutes\n"
+"-T TYPE[,TYPE]... only look for objects of TYPE\n"
+"-K only primary keys are returned\n"
+"-r turn off recursive look-ups for contact information\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"-R force to show local copy of the domain object even\n"
+" if it contains referral\n"
+"-a also search all the mirrored databases\n"
+"-s SOURCE[,SOURCE]... search the database mirrored from SOURCE\n"
+"-g SOURCE:FIRST-LAST find updates from SOURCE from serial FIRST to LAST\n"
+ ));
+ fprintf((EXIT_SUCCESS == error) ? stdout : stderr, _(
+"-t TYPE request template for object of TYPE\n"
+"-v TYPE request verbose template for object of TYPE\n"
+"-q [version|sources|types] query specified server info\n"
+));
+ exit(error);
+}
+
diff --git a/whois.conf b/whois.conf
new file mode 100644
index 0000000..620894c
--- /dev/null
+++ b/whois.conf
@@ -0,0 +1,11 @@
+# whois configuration file
+#
+# This file can contain details of alternative whois servers to use if
+# the compiled in servers are not suitable. Each entry is a single
+# text line and consists of a regular expression pattern to match and
+# the whois server to be used for it, separated by blank space.
+# IDN domains must use the ACE format.
+#
+# Eg:
+# \.nz$ nz.whois-servers.net
+#
diff --git a/whois.conf.5 b/whois.conf.5
new file mode 100644
index 0000000..7ec1b50
--- /dev/null
+++ b/whois.conf.5
@@ -0,0 +1,49 @@
+.TH "WHOIS.CONF" "5" "2019-12-30" "Petr Písař" "Debian GNU/Linux"
+
+.SH NAME
+whois.conf \- alternative WHOIS servers list for whois client
+
+.SH SYNOPSIS
+.B /etc/whois.conf
+
+.SH DESCRIPTION
+This file contains a list of WHOIS servers which can augment or override
+the built-in list of the client.
+
+It's a plain text file in ASCII encoding. Each line consists of two fields:
+a pattern to match WHOIS object identifier and a corresponding WHOIS server
+domain name.
+
+Fields are separated by non-empty sequence of space or a tabular characters.
+A line starting with a hash character is a free comment and it's not
+considered.
+
+The pattern is case-insensitive extended regular expression if whois client
+has been compiled with POSIX regular expressions support. Otherwise, simple
+case-insensitive suffix comparison against WHOIS object identifier is used.
+
+Internationalized domain names (IDN) must be specified in ascii-compatible
+encoding (ACE) format.
+
+.SH EXAMPLE
+\\.nz$ nz.whois-servers.net
+.br
+# Hangul Korean TLD
+.br
+\\.xn--3e0b707e$ whois.kr
+.br
+# Private ASNs
+.br
+^as645(1[2-9]|2[0-9]|3[0-4])$ whois.example.net
+
+.SH FILES
+/etc/whois.conf
+
+.SH "SEE ALSO"
+.IR whois (1)
+
+.SH AUTHOR
+This manual page was written by Petr Písař
+.RI < ppisar@redhat.com >
+and is licensed under the terms of the GNU General Public License,
+version 2 or higher.