A lot of the cleverness in whois is no longer needed!

The IANA whois server has the right referral information for domain
names, IP addresses, and AS numbers, so whois does not need to be
able to choose servers itself (except for a few cases where referrals
do not work). We can delete a chunk of code, which is always fun.

This change improves the referral handling to be less sensitive to
all the various formats, and to allow multi-hop referral chains,
such as IANA -> registry -> registrar.

ARIN queries have the "+" flag added if no flags are present, so we
get full details if the query matches multiple objects. The Verisign
anti-spam logic is also now suppressed if the user provided a non-
trivial query string.

Uninformative rubric is now trimmed by default. The -S option
turns off trimming, and disables query fettling.

The -i option is back to its traditional pre-1999 hostname, since
whois.internic.net is more useful than whois.networksolutions.com.
Note that the old fallback/default server whois.crsnic.net is an
alias for whois.internic.net.

The manual is more informative about query syntax.
This commit is contained in:
Tony Finch 2016-01-23 00:28:18 +00:00
parent 097f289f25
commit 6f4d88df9f
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=294611
2 changed files with 140 additions and 190 deletions

View File

@ -28,7 +28,7 @@
.\" From: @(#)whois.1 8.1 (Berkeley) 6/6/93
.\" $FreeBSD$
.\"
.Dd January 22, 2016
.Dd January 23, 2016
.Dt WHOIS 1
.Os
.Sh NAME
@ -49,31 +49,22 @@ Network Information Centers
.Pp
By default
.Nm
automatically discovers the name of a whois server to use
from the top-level domain
.Pq Tn TLD
of the supplied (single) argument.
It tries
.Qq Va TLD Ns Li .whois-servers.net
and
.Qq Li whois.nic. Ns Va TLD
and if neither host exists it falls back to its default server.
starts by querying the Internet Assigned Numbers Authority (IANA) whois server,
and follows referrals to whois servers
that have more specific details about the query
.Ar name .
The IANA whois server knows about
IP address and AS numbers
as well as domain names.
.Pp
If an IP address or AS number is specified,
the whois server will default to
the American Registry for Internet Numbers
.Pq Tn ARIN .
.Pp
If
There are a few special cases where referrals do not work, so
.Nm
cannot automatically discover a server,
it will fall back to
the host specified in the
.Ev WHOIS_SERVER
or
.Ev RA_SERVER
environment variables, or if those are not set, it will use
.Pa whois.crsnic.net .
goes directly to the appropriate server.
These include point-of-contact handles for ARIN,
.Pa nic.at ,
NORID, and RIPE,
and domain names under
.Pa ac.uk .
.Pp
The options are as follows:
.Bl -tag -width indent
@ -85,17 +76,16 @@ It contains network numbers used in those parts of the world covered neither by
.Tn APNIC , AfriNIC , LACNIC ,
nor by
.Tn RIPE .
.Pp
(Hint: All point of contact handles in the
.Tn ARIN
whois database end with
.Qq Li -ARIN . )
The query syntax is documented at
.Pa https://www.arin.net/resources/whoisrws/whois_api.html#nicname
.It Fl A
Use the Asia/Pacific Network Information Center
.Pq Tn APNIC
database.
It contains network numbers used in East Asia, Australia,
New Zealand, and the Pacific islands.
Get query syntax documentation using
.Ic whois -A help
.It Fl b
Use the Network Abuse Clearinghouse database.
It contains addresses to which network abuse should be reported,
@ -111,6 +101,8 @@ Use the African Network Information Centre
database.
It contains network numbers used in Africa and the islands of the
western Indian Ocean.
Get query syntax documentation using
.Ic whois -f help
.It Fl g
Use the US non-military federal government database, which contains points of
contact for subdomains of
@ -119,14 +111,28 @@ contact for subdomains of
Use the specified host instead of the default.
Either a host name or an IP address may be specified.
.It Fl i
Use the obsolete Network Solutions Registry for Internet Numbers
.Pq Pa whois.networksolutions.com
Use the traditional Network Information Center (InterNIC)
.Pq Pa whois.internic.net
database.
This now contains only registrations for domain names under
.Pa .COM ,
.Pa .NET ,
.Pa .EDU .
You can specify the type of object to search for like
.Ic whois -i ' Ns Ar type Ar name Ns Ic '
where
.Ar type
can be
.Nm domain , nameserver , registrar .
The
.Ar name
can contain
.Li *
wildcards.
.It Fl I
Use the Internet Assigned Numbers Authority
.Pq Tn IANA
database.
It contains network information for top-level domains.
.It Fl k
Use the National Internet Development Agency of Korea's
.Pq Tn KRNIC
@ -160,7 +166,7 @@ Do a quick lookup;
.Nm
will not attempt to follow referrals to other whois servers.
This is the default if a server is explicitly specified
using one of the other options.
using one of the other options or in an environment variable.
See also the
.Fl R
option.
@ -170,6 +176,8 @@ Use the R\(aaeseaux IP Europ\(aaeens
database.
It contains network numbers and domain contact information
for Europe.
Get query syntax documentation using
.Ic whois -r help
.It Fl R
Do a recursive lookup;
.Nm
@ -179,19 +187,16 @@ See also the
.Fl Q
option.
.It Fl S
By default, if the whois server is
.Pa whois.verisign-grs.com
(or a CNAME alias pointing at that name)
then
By default
.Nm
will query for
.Dl domain Ar name
The
adjusts simple queries (without spaces) to produce more useful output
from certain whois servers,
and it suppresses some uninformative output.
With the
.Fl S
option suppresses this behaviour,
allowing you to make a loose-matching query,
or query for host objects using the syntax
.Dl nameserver Ar name
option,
.Nm
sends the query and prints the output verbatim.
.El
.Pp
The operands specified to
@ -212,22 +217,11 @@ The secondary default whois server.
If this is unset,
.Nm
will use
.Pa whois.crsnic.net .
.Pa whois.iana.org .
.El
.Sh EXIT STATUS
.Ex -std
.Sh EXAMPLES
Most types of data, such as domain names and
.Tn IP
addresses, can be used as arguments to
.Nm
without any options, and
.Nm
will choose the correct whois server to query.
Some exceptions, where
.Nm
will not be able to handle data correctly, are detailed below.
.Pp
To obtain contact information about an
administrator located in the Russian
.Tn TLD

View File

@ -61,31 +61,35 @@ __FBSDID("$FreeBSD$");
#define ABUSEHOST "whois.abuse.net"
#define ANICHOST "whois.arin.net"
#define BNICHOST "whois.registro.br"
#define DENICHOST "de" QNICHOST_TAIL
#define FNICHOST "whois.afrinic.net"
#define GERMNICHOST "de" QNICHOST_TAIL
#define GNICHOST "whois.nic.gov"
#define IANAHOST "whois.iana.org"
#define INICHOST "whois.networksolutions.com"
#define INICHOST "whois.internic.net"
#define KNICHOST "whois.krnic.net"
#define LNICHOST "whois.lacnic.net"
#define MNICHOST "whois.ra.net"
#define NICHOST "whois.crsnic.net"
#define PDBHOST "whois.peeringdb.com"
#define PNICHOST "whois.apnic.net"
#define QNICHOST_HEAD "whois.nic."
#define QNICHOST_TAIL ".whois-servers.net"
#define RNICHOST "whois.ripe.net"
#define VNICHOST "whois.verisign-grs.com"
#define DEFAULT_PORT "whois"
#define WHOIS_RECURSE 0x01
#define WHOIS_QUICK 0x02
#define WHOIS_SPAM_ME 0x04
#define WHOIS_RECURSE 0x01
#define WHOIS_QUICK 0x02
#define WHOIS_SPAM_ME 0x04
#define CHOPSPAM ">>> Last update of WHOIS database:"
#define ishost(h) (isalnum((unsigned char)h) || h == '.' || h == '-')
#define SCAN(p, end, check) \
while ((p) < (end)) \
if (check) ++(p); \
else break
static struct {
const char *suffix, *server;
} whoiswhere[] = {
@ -96,7 +100,8 @@ static struct {
{ "-RIPE", RNICHOST },
/* Nominet's whois server doesn't return referrals to JANET */
{ ".ac.uk", "ac.uk" QNICHOST_TAIL },
{ NULL, NULL }
{ "", IANAHOST }, /* default */
{ NULL, NULL } /* safety belt */
};
#define WHOIS_REFERRAL(s) { s, sizeof(s) - 1 }
@ -104,18 +109,16 @@ static struct {
const char *prefix;
size_t len;
} whois_referral[] = {
WHOIS_REFERRAL("Whois Server: "),
WHOIS_REFERRAL("WHOIS Server: "),
WHOIS_REFERRAL(" Whois Server: "),
WHOIS_REFERRAL("refer: "),
WHOIS_REFERRAL("Registrant Street1:Whois Server:"),
WHOIS_REFERRAL("ReferralServer: whois://"),
WHOIS_REFERRAL("whois:"), /* IANA */
WHOIS_REFERRAL("Whois Server:"),
WHOIS_REFERRAL("Registrar WHOIS Server:"), /* corporatedomains.com */
WHOIS_REFERRAL("ReferralServer: whois://"), /* ARIN */
{ NULL, 0 }
};
static const char *port = DEFAULT_PORT;
static char *choose_server(char *);
static const char *choose_server(char *);
static struct addrinfo *gethostinfo(char const *host, int exitnoname);
static void s_asprintf(char **ret, const char *format, ...) __printflike(2, 3);
static void usage(void);
@ -125,15 +128,14 @@ int
main(int argc, char *argv[])
{
const char *country, *host;
char *qnichost;
int ch, flags, use_qnichost;
int ch, flags;
#ifdef SOCKS
SOCKSinit(argv[0]);
#endif
country = host = qnichost = NULL;
flags = use_qnichost = 0;
country = host = NULL;
flags = 0;
while ((ch = getopt(argc, argv, "aAbc:fgh:iIklmp:PQrRS")) != -1) {
switch (ch) {
case 'a':
@ -203,103 +205,43 @@ main(int argc, char *argv[])
usage();
/*
* If no host or country is specified, try to determine the top
* level domain from the query, or fall back to NICHOST.
* If no host or country is specified, rely on referrals from IANA.
*/
if (host == NULL && country == NULL) {
if ((host = getenv("WHOIS_SERVER")) == NULL &&
(host = getenv("RA_SERVER")) == NULL) {
use_qnichost = 1;
host = NICHOST;
if (!(flags & WHOIS_QUICK))
flags |= WHOIS_RECURSE;
}
}
while (argc-- > 0) {
if (country != NULL) {
char *qnichost;
s_asprintf(&qnichost, "%s%s", country, QNICHOST_TAIL);
whois(*argv, qnichost, flags);
} else if (use_qnichost)
if ((qnichost = choose_server(*argv)) != NULL)
whois(*argv, qnichost, flags);
if (qnichost == NULL)
whois(*argv, host, flags);
free(qnichost);
qnichost = NULL;
free(qnichost);
} else
whois(*argv, host != NULL ? host :
choose_server(*argv), flags);
argv++;
}
exit(0);
}
/*
* This function will remove any trailing periods from domain, after which it
* returns a pointer to newly allocated memory containing the whois server to
* be queried, or a NULL if the correct server couldn't be determined. The
* caller must remember to free(3) the allocated memory.
*
* If the domain is an IPv6 address or has a known suffix, that determines
* the server, else if the TLD is a number, query ARIN, else try a couple of
* formulaic server names. Fail if the domain does not contain '.'.
*/
static char *
static const char *
choose_server(char *domain)
{
char *pos, *retval;
size_t len = strlen(domain);
int i;
struct addrinfo *res;
if (strchr(domain, ':')) {
s_asprintf(&retval, "%s", ANICHOST);
return (retval);
}
if (strncasecmp(domain, "AS", 2) == 0) {
size_t len = strspn(domain + 2, "0123456789");
if (domain[len + 2] == '\0') {
s_asprintf(&retval, "%s", ANICHOST);
return (retval);
}
}
for (pos = strchr(domain, '\0'); pos > domain && pos[-1] == '.';)
*--pos = '\0';
if (*domain == '\0')
errx(EX_USAGE, "can't search for a null string");
for (i = 0; whoiswhere[i].suffix != NULL; i++) {
size_t suffix_len = strlen(whoiswhere[i].suffix);
if (domain + suffix_len < pos &&
strcasecmp(pos - suffix_len, whoiswhere[i].suffix) == 0) {
s_asprintf(&retval, "%s", whoiswhere[i].server);
return (retval);
}
}
while (pos > domain && *pos != '.')
--pos;
if (pos <= domain)
return (NULL);
if (isdigit((unsigned char)*++pos)) {
s_asprintf(&retval, "%s", ANICHOST);
return (retval);
}
/* Try possible alternative whois server name formulae. */
for (i = 0; ; ++i) {
switch (i) {
case 0:
s_asprintf(&retval, "%s%s", pos, QNICHOST_TAIL);
break;
case 1:
s_asprintf(&retval, "%s%s", QNICHOST_HEAD, pos);
break;
default:
return (NULL);
}
res = gethostinfo(retval, 0);
if (res) {
freeaddrinfo(res);
return (retval);
} else {
free(retval);
continue;
}
if (len > suffix_len &&
strcasecmp(domain + len - suffix_len,
whoiswhere[i].suffix) == 0)
return (whoiswhere[i].server);
}
errx(EX_SOFTWARE, "no default whois server");
}
static struct addrinfo *
@ -341,7 +283,7 @@ whois(const char *query, const char *hostname, int flags)
FILE *fp;
struct addrinfo *hostres, *res;
char *buf, *host, *nhost, *p;
int s = -1, f, antispam;
int s = -1, f;
nfds_t i, j;
size_t len, count;
struct pollfd *fds;
@ -350,10 +292,6 @@ whois(const char *query, const char *hostname, int flags)
hostres = gethostinfo(hostname, 1);
for (res = hostres, count = 0; res; res = res->ai_next)
count++;
antispam = (flags & WHOIS_SPAM_ME) == 0 &&
strcmp(hostres->ai_canonname, VNICHOST) == 0;
fds = calloc(count, sizeof(*fds));
if (fds == NULL)
err(EX_OSERR, "calloc()");
@ -420,8 +358,8 @@ whois(const char *query, const char *hostname, int flags)
break;
} else if (n < 0) {
/*
* errno here can only be EINTR which we would want
* to clean up and bail out.
* errno here can only be EINTR which we would
* want to clean up and bail out.
*/
s = -1;
goto done;
@ -455,66 +393,84 @@ whois(const char *query, const char *hostname, int flags)
s = -1;
if (count == 0)
errno = ETIMEDOUT;
done:
if (s == -1)
err(EX_OSERR, "connect()");
/* Close all watched fds except the succeeded one */
for (j = 0; j < i; j++)
if (fds[j].fd != s && fds[j].fd != -1)
close(fds[j].fd);
if (s != -1) {
/* Restore default blocking behavior. */
if ((f = fcntl(s, F_GETFL)) != -1) {
f &= ~O_NONBLOCK;
if (fcntl(s, F_SETFL, f) == -1)
err(EX_OSERR, "fcntl()");
} else
err(EX_OSERR, "fcntl()");
}
free(fds);
freeaddrinfo(hostres);
if (s == -1)
err(EX_OSERR, "connect()");
/* Restore default blocking behavior. */
if ((f = fcntl(s, F_GETFL)) == -1)
err(EX_OSERR, "fcntl()");
f &= ~O_NONBLOCK;
if (fcntl(s, F_SETFL, f) == -1)
err(EX_OSERR, "fcntl()");
fp = fdopen(s, "r+");
if (fp == NULL)
err(EX_OSERR, "fdopen()");
if (strcmp(hostname, GERMNICHOST) == 0) {
if (!(flags & WHOIS_SPAM_ME) &&
strcmp(hostname, DENICHOST) == 0)
fprintf(fp, "-T dn,ace -C ISO-8859-1 %s\r\n", query);
} else if (strcmp(hostname, "dk" QNICHOST_TAIL) == 0) {
else if (!(flags & WHOIS_SPAM_ME) &&
strcmp(hostname, "dk" QNICHOST_TAIL) == 0)
fprintf(fp, "--show-handles %s\r\n", query);
} else if (antispam) {
fprintf(fp, "domain %s\r\n", query);
} else {
else if ((flags & WHOIS_SPAM_ME) ||
strchr(query, ' ') != NULL)
fprintf(fp, "%s\r\n", query);
else if (strcmp(hostname, ANICHOST) == 0)
fprintf(fp, "+ %s\r\n", query);
else if (strcmp(hostres->ai_canonname, VNICHOST) == 0)
fprintf(fp, "domain %s\r\n", query);
else
fprintf(fp, "%s\r\n", query);
}
fflush(fp);
nhost = NULL;
while ((buf = fgetln(fp, &len)) != NULL) {
while (len > 0 && isspace((unsigned char)buf[len - 1]))
buf[--len] = '\0';
printf("%.*s\n", (int)len, buf);
/* Nominet */
if (!(flags & WHOIS_SPAM_ME) &&
len == 5 && strncmp(buf, "-- \r\n", 5) == 0)
break;
printf("%.*s", (int)len, buf);
if ((flags & WHOIS_RECURSE) && nhost == NULL) {
for (i = 0; whois_referral[i].prefix != NULL; i++) {
if (strncmp(buf,
whois_referral[i].prefix,
whois_referral[i].len) != 0)
p = buf;
SCAN(p, buf+len, *p == ' ');
if (strncasecmp(p, whois_referral[i].prefix,
whois_referral[i].len) != 0)
continue;
host = buf + whois_referral[i].len;
for (p = host; p < buf + len; p++)
if (!ishost(*p))
break;
s_asprintf(&nhost, "%.*s",
(int)(p - host), host);
p += whois_referral[i].len;
SCAN(p, buf+len, *p == ' ');
host = p;
SCAN(p, buf+len, ishost(*p));
/* avoid loops */
if (strncmp(hostname, host, p - host) != 0)
s_asprintf(&nhost, "%.*s",
(int)(p - host), host);
break;
}
}
/* Verisign etc. */
if (!(flags & WHOIS_SPAM_ME) &&
len >= sizeof(CHOPSPAM)-1 &&
(strncasecmp(buf, CHOPSPAM, sizeof(CHOPSPAM)-1) == 0 ||
strncasecmp(buf, CHOPSPAM+4, sizeof(CHOPSPAM)-5) == 0)) {
printf("\n");
break;
}
}
fclose(fp);
freeaddrinfo(hostres);
if (nhost != NULL) {
whois(query, nhost, 0);
whois(query, nhost, flags);
free(nhost);
}
}