master xplshn/aruu / cmd / net / host.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include "arg.h"
  4#include "util.h"
  5
  6#include <arpa/inet.h>
  7#include <ctype.h>
  8#include <errno.h>
  9#include <netdb.h>
 10#include <netinet/in.h>
 11#include <resolv.h>
 12#include <stdio.h>
 13#include <stdlib.h>
 14#include <string.h>
 15#include <sys/socket.h>
 16#include <sys/time.h>
 17#include <sys/types.h>
 18#include <unistd.h>
 19
 20struct RRType {
 21  const char *name;
 22  const char *msg;
 23  int         type;
 24};
 25
 26static const struct RRType rrtypes[] = {
 27    {"A", "has address", 1},
 28    {"NS", "name server", 2},
 29    {"CNAME", "is a nickname for", 5},
 30    {"SOA", "start of authority", 6},
 31    {"PTR", "domain name pointer", 12},
 32    {"MX", "mail is handled by", 15},
 33    {"TXT", "descriptive text", 16},
 34    {"AAAA", "has IPv6 address", 28},
 35    {"SRV", "has SRV record", 33},
 36    {"ANY", "has ANY record", 255}
 37};
 38
 39static void
 40usage(void)
 41{
 42  eprintf("usage: %s [-t type] name [server]\n", argv0);
 43}
 44
 45static int
 46load_nameservers(char **ns, int max_ns)
 47{
 48  FILE *fp = fopen("/etc/resolv.conf", "r");
 49  char  line[256];
 50  char *p, *end;
 51  int   count = 0;
 52
 53  if (!fp)
 54    return 0;
 55
 56  while (fgets(line, sizeof(line), fp) && count < max_ns) {
 57    if (strncmp(line, "nameserver", 10) == 0 && isspace(line[10])) {
 58      p = line + 11;
 59      while (isspace(*p))
 60        p++;
 61      end = p;
 62      while (*end && !isspace(*end) && *end != '#')
 63        end++;
 64      *end = '\0';
 65      if (*p) {
 66        ns[count++] = estrdup(p);
 67      }
 68    }
 69  }
 70  fclose(fp);
 71  return count;
 72}
 73
 74static char *
 75reverse_ip(const char *ip_str, int *type)
 76{
 77  struct in_addr  addr4;
 78  struct in6_addr addr6;
 79  unsigned char  *p;
 80  char           *buf = emalloc(256);
 81  int             i, j;
 82
 83  if (inet_pton(AF_INET, ip_str, &addr4) > 0) {
 84    p = (unsigned char *)&addr4.s_addr;
 85    sprintf(buf, "%d.%d.%d.%d.in-addr.arpa", p[3], p[2], p[1], p[0]);
 86    *type = 12;
 87    return buf;
 88  } else if (inet_pton(AF_INET6, ip_str, &addr6) > 0) {
 89    p = (unsigned char *)&addr6.s6_addr;
 90    j = 0;
 91    for (i = 15; i >= 0; i--) {
 92      j += sprintf(buf + j, "%x.%x.", p[i] & 15, p[i] >> 4);
 93    }
 94    strcpy(buf + j, "ip6.arpa");
 95    *type = 12;
 96    return buf;
 97  }
 98
 99  free(buf);
100  return NULL;
101}
102
103static int
104send_query(const char *ns, const char *name, int type, unsigned char *resp, int resp_len)
105{
106  struct addrinfo hints, *res;
107  unsigned char   query[512];
108  struct timeval  tv;
109  int             qlen;
110  int             sock = -1;
111  int             r;
112
113  qlen = res_mkquery(0, name, 1, type, NULL, 0, NULL, query, sizeof(query));
114  if (qlen < 0) {
115    weprintf("res_mkquery failed for %s\n", name);
116    return -1;
117  }
118
119  memset(&hints, 0, sizeof(hints));
120  hints.ai_family   = AF_UNSPEC;
121  hints.ai_socktype = SOCK_DGRAM;
122
123  r = getaddrinfo(ns, "53", &hints, &res);
124  if (r != 0) {
125    weprintf("getaddrinfo %s: %s\n", ns, gai_strerror(r));
126    return -1;
127  }
128
129  sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
130  if (sock < 0) {
131    freeaddrinfo(res);
132    return -1;
133  }
134
135  tv.tv_sec  = 5;
136  tv.tv_usec = 0;
137  setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
138
139  if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
140    close(sock);
141    freeaddrinfo(res);
142    return -1;
143  }
144
145  if (send(sock, query, qlen, 0) < 0) {
146    close(sock);
147    freeaddrinfo(res);
148    return -1;
149  }
150
151  r = recv(sock, resp, resp_len, 0);
152  close(sock);
153  freeaddrinfo(res);
154
155  return r;
156}
157
158static unsigned short
159peek_be(const unsigned char *p)
160{
161  return (p[0] << 8) | p[1];
162}
163
164static int
165parse_response(const unsigned char *resp, int resp_len, const char *query_name)
166{
167  const unsigned char *p;
168  char                 name[256];
169  char                 target[256];
170  char                 ipv6[64];
171  char                 mname[256], rname[256];
172  const char          *err;
173  const char          *msg;
174  struct in_addr       a;
175  int                  rcode, qdcount, ancount;
176  int                  i, n, type, rdlen, pref, n1, n2;
177  unsigned int         ttl;
178
179  if (resp_len < 12) {
180    weprintf("response too short: %d\n", resp_len);
181    return -1;
182  }
183
184  rcode = resp[3] & 0x0f;
185  if (rcode != 0) {
186    err = "Unknown error";
187    switch (rcode) {
188      case 1:
189        err = "Format error";
190        break;
191      case 2:
192        err = "Server failure";
193        break;
194      case 3:
195        err = "Non-existent domain";
196        break;
197      case 4:
198        err = "Not implemented";
199        break;
200      case 5:
201        err = "Refused";
202        break;
203    }
204    eprintf("Host not found: %s\n", err);
205  }
206
207  qdcount = peek_be(resp + 4);
208  ancount = peek_be(resp + 6);
209
210  p = resp + 12;
211  for (i = 0; i < qdcount; i++) {
212    n = dn_expand(resp, resp + resp_len, p, name, sizeof(name));
213    if (n < 0)
214      return -1;
215    p += n + 4;
216  }
217
218  if (ancount == 0) {
219    printf("%s has no records\n", query_name);
220    return 0;
221  }
222
223  for (i = 0; i < ancount; i++) {
224    n = dn_expand(resp, resp + resp_len, p, name, sizeof(name));
225    if (n < 0)
226      return -1;
227    p += n;
228    type  = peek_be(p);
229    ttl   = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
230    rdlen = peek_be(p + 8);
231    p += 10;
232
233    (void)ttl;
234
235    if (type == 1) {
236      memcpy(&a, p, 4);
237      printf("%s has address %s\n", name, inet_ntoa(a));
238    } else if (type == 28) {
239      inet_ntop(AF_INET6, p, ipv6, sizeof(ipv6));
240      printf("%s has IPv6 address %s\n", name, ipv6);
241    } else if (type == 2 || type == 5 || type == 12) {
242      dn_expand(resp, resp + resp_len, p, target, sizeof(target));
243      msg = "has record";
244      if (type == 2)
245        msg = "name server";
246      else if (type == 5)
247        msg = "is a nickname for";
248      else if (type == 12)
249        msg = "domain name pointer";
250      printf("%s %s %s\n", name, msg, target);
251    } else if (type == 15) {
252      pref = peek_be(p);
253      dn_expand(resp, resp + resp_len, p + 2, target, sizeof(target));
254      printf("%s mail is handled by %d %s\n", name, pref, target);
255    } else if (type == 16) {
256      printf("%s descriptive text \"%.*s\"\n", name, (int)p[0], (char *)(p + 1));
257    } else if (type == 6) {
258      n1 = dn_expand(resp, resp + resp_len, p, mname, sizeof(mname));
259      n2 = dn_expand(resp, resp + resp_len, p + n1, rname, sizeof(rname));
260      (void)n2;
261      printf("%s start of authority %s %s\n", name, mname, rname);
262    } else {
263      printf("%s has unsupported record type %d\n", name, type);
264    }
265
266    p += rdlen;
267  }
268
269  return 0;
270}
271
272// ?man host: dns lookup utility
273// ?man arguments: name [server]
274// ?man look up hostnames and IP addresses using dns
275int
276main(int argc, char *argv[])
277{
278  unsigned char resp[65536];
279  char         *ns[8];
280  char         *tflag = NULL;
281  char         *name;
282  char         *query_name;
283  char         *rev;
284  int           ns_count = 0;
285  int           type     = 1;
286  int           i, r;
287
288  ARGBEGIN
289  {
290    // ?man -t:str: sort or specify timestamp
291    case 't':
292      tflag = EARGF(usage());
293      break;
294    default:
295      usage();
296  }
297  ARGEND
298
299  if (argc < 1)
300    usage();
301
302  query_name = argv[0];
303
304  if (argc > 1) {
305    ns[0]    = argv[1];
306    ns_count = 1;
307  } else {
308    ns_count = load_nameservers(ns, 8);
309    if (ns_count == 0)
310      eprintf("no nameservers found in /etc/resolv.conf\n");
311  }
312
313  rev = reverse_ip(query_name, &type);
314  if (rev) {
315    name = rev;
316  } else {
317    name = query_name;
318    if (tflag) {
319      type = -1;
320      for (i = 0; i < (int)LEN(rrtypes); i++) {
321        if (strcasecmp(tflag, rrtypes[i].name) == 0) {
322          type = rrtypes[i].type;
323          break;
324        }
325      }
326      if (type == -1)
327        eprintf("invalid record type: %s\n", tflag);
328    }
329  }
330
331  for (i = 0; i < ns_count; i++) {
332    r = send_query(ns[i], name, type, resp, sizeof(resp));
333    if (r > 0) {
334      parse_response(resp, r, query_name);
335      break;
336    }
337  }
338
339  if (rev)
340    free(rev);
341
342  if (argc == 1) {
343    for (i = 0; i < ns_count; i++) {
344      free(ns[i]);
345    }
346  }
347
348  return r > 0 ? 0 : 1;
349}