diff options
Diffstat (limited to 'whois.c')
-rw-r--r-- | whois.c | 1561 |
1 files changed, 1561 insertions, 0 deletions
@@ -0,0 +1,1561 @@ +/* + * Copyright (C) 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. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* 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); + free(server); + 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 + +/* + * Check if the argument is an autonomous system number in the form "ASnnnn" + * or "AS nnnn". + */ +int is_asn(const char *s, int allow_space, const char *suffix_separators) +{ + const char *p; + int max_numbers = 6; + int seen_number = 0; + + if (!strncaseeq(s, "as", 2)) + return 0; /* does not start with "as" (case insensitive) */ + + /* skip a single optional space */ + p = s + 2; + if (allow_space && *p == ' ') + p++; + + /* all numbers, but not too many */ + while (*p) { + if (*p < '0' || *p > '9') { /* not a number */ + if (!seen_number) + return 0; /* just "as" with no numbers */ + if (suffix_separators && strchr(suffix_separators, *p)) + return 1; /* has a trailing separator */ + return 0; /* has trailing garbage */ + } + + seen_number = 1; + if (max_numbers-- == 0) + return 0; /* too many numbers */ + p++; + } + + if (seen_number) + return 1; /* a real ASN */ + else + return 0; /* trailing garbage */ +} + +/* 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; + unsigned int i; + const char *colon, *tld; + + /* IPv6 address */ + if ((colon = strchr(s, ':'))) { + unsigned long v6prefix, v6net; + + /* RPSL hierarchical objects */ + if (is_asn(s, 0, ":")) + return strdup(whereas(atol(s + 2))); + + 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 (is_asn(s, 1, NULL)) + return strdup(whereas(atol(s + 2))); /* it's an AS */ + if (*s == '!') /* NSI NIC handle */ + return strdup("whois.networksolutions.com"); + else + return strdup("\x05"); /* probably a unknown kind of nic handle */ + } + + /* 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 *whereas(const unsigned long asn) +{ + int i; + + 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.punktum.dk") || + 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") && + is_asn(query, 1, NULL)) { + strcat(buf, "AS "); + strcat(buf, query + 2); + } + else if (!isripe && streq(server, "whois.arin.net") && + !strrchr(query, ' ')) { + if (is_asn(query, 0, NULL)) { + 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()) { + /* but not if the user already added the /e flag */ + char *flag = buf + strlen(buf) - 2; + if (!streq(flag, "/e")) + 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(UNUSED 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; +} + +int isasciidigit(const char c) +{ + return (c >= '0' && c <= '9') ? 1 : 0; +} + +/* https://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); +} + |