From: Mark Wooding Date: Wed, 13 Sep 2017 11:22:16 +0000 (+0100) Subject: gai.c: Add program for name resolution using getaddrinfo(3). X-Git-Tag: 1.4.3~1 X-Git-Url: https://git.distorted.org.uk/~mdw/misc/commitdiff_plain/d3fafed3ea3c8937802eaae33ae3f5b8f2b45a62 gai.c: Add program for name resolution using getaddrinfo(3). I seem to use this a lot, so package it. --- diff --git a/Makefile.am b/Makefile.am index c908472..4d8f307 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,6 +55,11 @@ bin_PROGRAMS += getpass getpass_SOURCES = getpass.c dist_man_MANS += getpass.1 +## gai +bin_PROGRAMS += gai +gai_SOURCES = gai.c +dist_man_MANS += gai.1 + ## gorp if HAVE_CATACOMB bin_PROGRAMS += gorp diff --git a/debian/control b/debian/control index d4a0354..70cd17a 100644 --- a/debian/control +++ b/debian/control @@ -18,6 +18,7 @@ Depends: create, distorted-cdb, distorted-mail, + gai, getpass, gorp, hush, @@ -64,6 +65,12 @@ Section: mail Depends: ${shlibs:Depends}, qmail, distorted-cdb, perl, libmime-perl Description: Some simple scripts for mail handling. +Package: gai +Architecture: any +Section: utils +Depends: ${shlibs:Depends} +Description: A tool for name resolution, exercising getaddrinfo(3). + Package: getpass Architecture: any Depends: ${shlibs:Depends} diff --git a/debian/inst b/debian/inst index 7f2997e..7f0bbf5 100644 --- a/debian/inst +++ b/debian/inst @@ -14,6 +14,8 @@ check-sender distorted-mail /usr/bin check-sender.1 distorted-mail /usr/share/man/man1 create create /usr/bin create.1 create /usr/share/man/man1 +gai gai /usr/bin +gai.1 gai /usr/share/man/man1 getpass getpass /usr/bin getpass.1 getpass /usr/share/man/man1 gorp gorp /usr/bin diff --git a/gai.1 b/gai.1 new file mode 100644 index 0000000..6f0ddf1 --- /dev/null +++ b/gai.1 @@ -0,0 +1,221 @@ +.\" -*-nroff-*- +.ie t .ds o \(bu +.el .ds o * +.de hP +.IP +\fB\h'-\w'\\$1'u-1m'\\$1\h'1m'\fP\c +.. +.TH space 1 "13 September 2017" "Straylight/Edgeware" +.SH NAME +gai \- look up names using getaddrinfo +.SH SYNOPSIS +.B gai +.RB [ \-CNacgimnsuv ] +.RB [ \-f +.IR family ] +.RB [ \-p +.IR protocol ] +.RB [ \-t +.IR type ] +.I name +.RI [ service ] +.SH DESCRIPTION +The +.B gai +program looks up host and/or service names +using the +.BR getaddrinfo (3) +C-library function, +and reports the results in a machine-readable manner. +.PP +The following command-line options are recognized. +.TP +.B \-h +Give a short help message describing the options available. +.TP +.B \-a +If +.B \-m +is set, +report plain IPv4 address +.I in addition to +the corresponding v6-mapped addresses. +Sets the +.B AI_ALL +flag. +See also +.BR \-m . +.TP +.B \-c +Report the canonical name of the host. +Sets the +.B AI_CANONNAME +flag. +See also +.BR \-C . +.TP +.B \-C +If the canonical name is an IDN, +then convert it back into the local character set before display. +Sets the +.B AI_CANONIDN +flag. +See also +.BR \-c . +.TP +.BI "\-f " family +Return only addresses for the given address +.IR family , +which may be one of +.B unspec +(any address family is acceptable; the default), +.B inet +(IPv4), or +.B inet6 +(IPv6). +Sets the +.B ai_family +hint. +.TP +.B \-g +Return only addresses of families +for which the host has a configured address. +This is less useful than it sounds +because of IPv6 link-local addresses. +Sets the +.B AI_ADDRCONFIG +flag. +.TP +.B \-i +Convert hostnames containing unusual characters to IDNs. +Sets the +.B AI_IDN +flag. +.TP +.B \-m +Return IPv4 addresses as their v6-mapped equivalents. +Sets the +.B AI_V4MAPPED +flag. +See also +.BR \-a . +.TP +.B \-n +Accept only numeric host addresses; +do not attempt to resolve hostnames. +(Scope ids on link-local IPv6 addresses are still accepted.) +Sets the +.B AI_NUMERICHOST +flag. +See also +.BR \-N . +.TP +.B \-n +Accept only numeric port numbers; +do not attempt to resolve service names. +Sets the +.B AI_NUMERICSERV +flag. +See also +.BR \-n . +.TP +.BI "\-p " protocol +Return only addresses suitable for use with the given +.I protocol , +which may be a protocol number +or a name to be looked up in +.BR /etc/protocols . +Sets the +.B ai_protocol +hint. +.TP +.B \-s +Allow unassigned Unicode code points in IDNs. +Sets the +.B AI_IDN_ALLOW_UNASSIGNED +flag. +See also +.B \-C +and +.BR \-i . +.TP +.BI "\-t " type +Return only addresses for the given socket +.I type , +which may be one of +.B any +(no restriction; the default), +.BR stream , +.BR dgram , +or +.BR raw . +Sets the +.B ai_socktype +hint. +.TP +.B \-u +Check that the result of IDN conversion is a valid STD3 host name. +Sets the +.B AI_IDN_USE_STD3_ASCII_RULES +flag. +See also +.B \-C +and +.BR \-i . +.TP +.B \-v +Default the host address suitably for use for receiving connections; +if +.I name +is +.RB ` \- ' +then return appropriate wildcard addresses rather than loopback +addresses. +Sets the +.B AI_PASSIVE +flag. +.PP +The +.I name +may be +.RB ` \- ' +to indicate that a service-only lookup should be performed. +The +.I service +may be omitted to indicate that a hostname-only lookup should be +performed. +.PP +The output is as follows. +Firstly, if +.B \-c +is set, then a line +.IP +.BI [ canon ] +.PP +is written showing the canonical host name. +Following this (if it's present) are one or more lines of the form +.IP +.IR family " " type " " address " " port +.PP +The +.I family +shows the address family of the given address, as either +.B inet +or +.BR inet6 . +The +.I type +shows the socket type, as +.BR stream , +.BR dgram , +or +.BR raw . +The +.I address +and +.I port +are the numeric address and service, +as produced by +.BR getnameinfo (3). +.SH AUTHOR +Mark Wooding, diff --git a/gai.c b/gai.c new file mode 100644 index 0000000..5891c55 --- /dev/null +++ b/gai.c @@ -0,0 +1,181 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct nameval { const char *name; int val; }; +const char *prog = ""; + +static int parseint(const char *s, const char *what) +{ + char *q; + unsigned long i; + int e = errno; + + if (!isdigit((unsigned char)*s)) goto fail; + errno = 0; + i = strtoul(s, &q, 0); + if (errno || *q || i > INT_MAX) goto fail; + errno = e; + return ((int)i); + +fail: + fprintf(stderr, "%s: bad numeric %s `%s'\n", prog, what, s); + exit(1); +} + +static int lookup(const char *name, const char *what, + const struct nameval *nv) +{ + if (isdigit((unsigned char)*name)) + return (parseint(name, what)); + for (; nv->name; nv++) + if (strcmp(nv->name, name) == 0) return nv->val; + fprintf(stderr, "%s: unknown %s `%s'\n", prog, what, name); + exit(1); +} + +static const char *rlookup(int val, const char *what, + const struct nameval *nv) +{ + static char buf[32]; + for (; nv->name; nv++) + if (nv->val == val) return (nv->name); + sprintf(buf, "%s#%d", what, val); + return (buf); +} + +static const struct nameval familymap[] = { + { "unspec", AF_UNSPEC }, + { "inet", AF_INET }, + { "inet6", AF_INET6 }, + { 0 } +}; + +static const struct nameval typemap[] = { + { "any", 0 }, + { "stream", SOCK_STREAM }, + { "dgram", SOCK_DGRAM }, + { "raw", SOCK_RAW }, + { 0 } +}; + +static void usage(FILE *fp) +{ + fprintf(fp, + "Usage: %s " + "[-CNacgimnsuv] [-f FAMILY] [-p PROTOCOL] [-t TYPE] " + "NAME [SERVICE]\n", + prog); +} + +static void help(FILE *fp) +{ + usage(fp); fputs("\n\ +Options:\n\ +\n\ + -a plain v4 also (with -m) -n numeric host\n\ + -c canonify name -N numeric service\n\ + -C canonify to IDN (with -c) -s allow unassigned codepoints\n\ + -g families with configured addrs -u check result is STD3 hostname\n\ + -i convert to IDN -v passive\n\ + -m v6-mapped v4 addresses\n\ +\n\ + -f unspec|inet|inet6 NAME -- hostname or `-' for none\n\ + -p PROTOCOL -- /etc/protocols SERVICE -- number or /etc/services\n\ + -t any|stream|dgram|raw\n\ +", fp); +} + +int main(int argc, char *argv[]) +{ + struct addrinfo *ai, hint = { .ai_family = AF_UNSPEC }; + const char *name; + const char *serv = 0; + char namebuf[NI_MAXHOST], servbuf[NI_MAXSERV]; + int err; + + prog = strrchr(argv[0], '/'); + if (prog) prog++; + else prog = argv[0]; + + for (;;) { + int opt = getopt(argc, argv, "hf:t:p:nNcvgamiCus"); + if (opt < 0) break; + switch (opt) { + case 'h': help(stdout); exit(0); + case 'f': + hint.ai_family = lookup(optarg, "family", familymap); + break; + case 't': + hint.ai_socktype = lookup(optarg, "type", typemap); + break; + case 'p': + if (isdigit((unsigned char)*optarg)) + hint.ai_protocol = parseint(optarg, "protocol"); + else { + struct protoent *p = getprotobyname(optarg); + if (!p) { + fprintf(stderr, "%s: unknown protocol `%s'\n", prog, optarg); + exit(1); + } + hint.ai_protocol = p->p_proto; + } + break; + case 'C': hint.ai_flags |= AI_CANONIDN; break; + case 'N': hint.ai_flags |= AI_NUMERICSERV; break; + case 'a': hint.ai_flags |= AI_ALL; break; + case 'c': hint.ai_flags |= AI_CANONNAME; break; + case 'g': hint.ai_flags |= AI_ADDRCONFIG; break; + case 'i': hint.ai_flags |= AI_IDN; break; + case 'm': hint.ai_flags |= AI_V4MAPPED; break; + case 'n': hint.ai_flags |= AI_NUMERICHOST; break; + case 's': hint.ai_flags |= AI_IDN_USE_STD3_ASCII_RULES; break; + case 'u': hint.ai_flags |= AI_IDN_ALLOW_UNASSIGNED; break; + case 'v': hint.ai_flags |= AI_PASSIVE; break; + default: usage(stderr); exit(1); + } + } + + argv += optind; + if (!*argv) { usage(stderr); exit(1); } name = *argv++; + if (*argv) { serv = *argv++; } + if (*argv) { usage(stderr); exit(1); } + + if (strcmp(name, "-") == 0) name = 0; + + if ((err = getaddrinfo(name, serv, &hint, &ai)) != 0) { + fprintf(stderr, "%s: %s\n", prog, gai_strerror(err)); + exit(1); + } + + for (; ai; ai = ai->ai_next) { + if (ai->ai_canonname) printf("[%s]\n", ai->ai_canonname); + fputs(rlookup(ai->ai_family, "family", familymap), stdout); + fputc(' ', stdout); + fputs(rlookup(ai->ai_socktype, "type", typemap), stdout); + fputc(' ', stdout); + if ((err = getnameinfo(ai->ai_addr, ai->ai_addrlen, + namebuf, sizeof(namebuf), + servbuf, sizeof(servbuf), + (NI_NUMERICHOST | NI_NUMERICSERV | + (ai->ai_socktype == SOCK_DGRAM ? + NI_DGRAM : 0)))) != 0) + printf("(error: %s)", gai_strerror(err)); + else + printf("%s %s", namebuf, servbuf); + fputc('\n', stdout); + } + + freeaddrinfo(ai); + + return (0); +}