gai.c: Add program for name resolution using getaddrinfo(3).
authorMark Wooding <mdw@distorted.org.uk>
Wed, 13 Sep 2017 11:22:16 +0000 (12:22 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 13 Sep 2017 11:25:55 +0000 (12:25 +0100)
I seem to use this a lot, so package it.

Makefile.am
debian/control
debian/inst
gai.1 [new file with mode: 0644]
gai.c [new file with mode: 0644]

index c908472..4d8f307 100644 (file)
@@ -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
index d4a0354..70cd17a 100644 (file)
@@ -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}
index 7f2997e..7f0bbf5 100644 (file)
@@ -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 (file)
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, <mdw@distorted.org.uk>
diff --git a/gai.c b/gai.c
new file mode 100644 (file)
index 0000000..5891c55
--- /dev/null
+++ b/gai.c
@@ -0,0 +1,181 @@
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <getopt.h>
+
+#include <netdb.h>
+
+struct nameval { const char *name; int val; };
+const char *prog = "<unset>";
+
+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);
+}