"Fossies" - the Fresh Open Source Software Archive

Member "darkstat-3.0.721/dns.c" (12 Jan 2022, 11015 Bytes) of package /linux/privat/darkstat-3.0.721.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dns.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.0.719_vs_3.0.721.

    1 /* darkstat 3
    2  * copyright (c) 2001-2014 Emil Mikulic.
    3  *
    4  * dns.c: synchronous DNS in a child process.
    5  *
    6  * You may use, modify and redistribute this file under the terms of the
    7  * GNU General Public License version 2. (see COPYING.GPL)
    8  */
    9 
   10 #include "cdefs.h"
   11 #include "cap.h"
   12 #include "conv.h"
   13 #include "decode.h"
   14 #include "dns.h"
   15 #include "err.h"
   16 #include "hosts_db.h"
   17 #include "queue.h"
   18 #include "str.h"
   19 #include "tree.h"
   20 #include "bsd.h" /* for setproctitle, strlcpy */
   21 
   22 #include <sys/param.h>
   23 #include <sys/socket.h>
   24 #include <sys/wait.h>
   25 #include <assert.h>
   26 #include <errno.h>
   27 #include <netdb.h>
   28 #include <signal.h>
   29 #include <stdlib.h>
   30 #include <string.h>
   31 #include <unistd.h>
   32 
   33 #ifdef __NetBSD__
   34 # define gethostbyaddr(addr, len, type) \
   35          gethostbyaddr((const char *)(addr), len, type)
   36 #endif
   37 
   38 static void dns_main(void) _noreturn_; /* the child process runs this */
   39 
   40 #define CHILD 0 /* child process uses this socket */
   41 #define PARENT 1
   42 static int dns_sock[2];
   43 static pid_t pid = -1;
   44 
   45 struct dns_reply {
   46    struct addr addr;
   47    int error; /* for gai_strerror(), or 0 if no error */
   48    char name[256]; /* http://tools.ietf.org/html/rfc1034#section-3.1 */
   49 };
   50 
   51 void
   52 dns_init(const char *privdrop_user)
   53 {
   54    if (socketpair(AF_UNIX, SOCK_STREAM, 0, dns_sock) == -1)
   55       err(1, "socketpair");
   56 
   57    pid = fork();
   58    if (pid == -1)
   59       err(1, "fork");
   60 
   61    if (pid == 0) {
   62       /* We are the child. */
   63       privdrop(NULL /* don't chroot */, privdrop_user);
   64       close(dns_sock[PARENT]);
   65       dns_sock[PARENT] = -1;
   66       daemonize_finish(); /* drop our copy of the lifeline! */
   67       if (signal(SIGUSR1, SIG_IGN) == SIG_ERR)
   68          errx(1, "signal(SIGUSR1, ignore) failed");
   69       cap_free_args();
   70       dns_main();
   71    } else {
   72       /* We are the parent. */
   73       close(dns_sock[CHILD]);
   74       dns_sock[CHILD] = -1;
   75       fd_set_nonblock(dns_sock[PARENT]);
   76       verbosef("DNS child has PID %d", pid);
   77    }
   78 }
   79 
   80 void
   81 dns_stop(void)
   82 {
   83    if (pid == -1)
   84       return; /* no child was started */
   85    close(dns_sock[PARENT]);
   86    if (kill(pid, SIGINT) == -1)
   87       err(1, "kill");
   88    verbosef("dns_stop() waiting for child");
   89    if (waitpid(pid, NULL, 0) == -1)
   90       err(1, "waitpid");
   91    verbosef("dns_stop() done waiting for child");
   92 }
   93 
   94 struct tree_rec {
   95    RB_ENTRY(tree_rec) ptree;
   96    struct addr ip;
   97 };
   98 
   99 static int
  100 tree_cmp(struct tree_rec *a, struct tree_rec *b)
  101 {
  102    if (a->ip.family != b->ip.family)
  103       /* Sort IPv4 to the left of IPv6.  */
  104       return ((a->ip.family == IPv4) ? -1 : +1);
  105 
  106    if (a->ip.family == IPv4)
  107       return (memcmp(&a->ip.ip.v4, &b->ip.ip.v4, sizeof(a->ip.ip.v4)));
  108    else {
  109       assert(a->ip.family == IPv6);
  110       return (memcmp(&a->ip.ip.v6, &b->ip.ip.v6, sizeof(a->ip.ip.v6)));
  111    }
  112 }
  113 
  114 static RB_HEAD(tree_t, tree_rec) ip_tree = RB_INITIALIZER(&tree_rec);
  115 RB_GENERATE_STATIC(tree_t, tree_rec, ptree, tree_cmp)
  116 
  117 void
  118 dns_queue(const struct addr *const ipaddr)
  119 {
  120    struct tree_rec *rec;
  121    ssize_t num_w;
  122 
  123    if (pid == -1)
  124       return; /* no child was started - we're not doing any DNS */
  125 
  126    if ((ipaddr->family != IPv4) && (ipaddr->family != IPv6)) {
  127       verbosef("dns_queue() for unknown family %d", ipaddr->family);
  128       return;
  129    }
  130 
  131    rec = xmalloc(sizeof(*rec));
  132    memcpy(&rec->ip, ipaddr, sizeof(rec->ip));
  133 
  134    if (RB_INSERT(tree_t, &ip_tree, rec) != NULL) {
  135       /* Already queued - this happens seldom enough that we don't care about
  136        * the performance hit of needlessly malloc()ing. */
  137       verbosef("already queued %s", addr_to_str(ipaddr));
  138       free(rec);
  139       return;
  140    }
  141 
  142    num_w = write(dns_sock[PARENT], ipaddr, sizeof(*ipaddr)); /* won't block */
  143    if (num_w == 0)
  144       warnx("dns_queue: write: ignoring end of file");
  145    else if (num_w == -1)
  146       warn("dns_queue: ignoring write error");
  147    else if (num_w != sizeof(*ipaddr))
  148       err(1, "dns_queue: wrote %zu instead of %zu", num_w, sizeof(*ipaddr));
  149 }
  150 
  151 static void
  152 dns_unqueue(const struct addr *const ipaddr)
  153 {
  154    struct tree_rec tmp, *rec;
  155 
  156    memcpy(&tmp.ip, ipaddr, sizeof(tmp.ip));
  157    if ((rec = RB_FIND(tree_t, &ip_tree, &tmp)) != NULL) {
  158       RB_REMOVE(tree_t, &ip_tree, rec);
  159       free(rec);
  160    }
  161    else
  162       verbosef("couldn't unqueue %s - not in queue!", addr_to_str(ipaddr));
  163 }
  164 
  165 /*
  166  * Returns non-zero if result waiting, stores IP and name into given pointers
  167  * (name buffer is allocated by dns_poll)
  168  */
  169 static int
  170 dns_get_result(struct addr *ipaddr, char **name)
  171 {
  172    struct dns_reply reply;
  173    ssize_t numread;
  174 
  175    numread = read(dns_sock[PARENT], &reply, sizeof(reply));
  176    if (numread == -1) {
  177       if (errno == EAGAIN)
  178          return (0); /* no input waiting */
  179       else
  180          goto error;
  181    }
  182    if (numread == 0)
  183       goto error; /* EOF */
  184    if (numread != sizeof(reply))
  185       errx(1, "dns_get_result read got %zu, expected %zu",
  186          numread, sizeof(reply));
  187 
  188    /* Return successful reply. */
  189    memcpy(ipaddr, &reply.addr, sizeof(*ipaddr));
  190    if (reply.error != 0) {
  191       /* Identify common special cases.  */
  192       const char *type = "none";
  193 
  194       if (reply.addr.family == IPv6) {
  195          if (IN6_IS_ADDR_LINKLOCAL(&reply.addr.ip.v6))
  196             type = "link-local";
  197          else if (IN6_IS_ADDR_SITELOCAL(&reply.addr.ip.v6))
  198             type = "site-local";
  199          else if (IN6_IS_ADDR_MULTICAST(&reply.addr.ip.v6))
  200             type = "multicast";
  201       } else {
  202          assert(reply.addr.family == IPv4);
  203          if (IN_MULTICAST(htonl(reply.addr.ip.v4)))
  204             type = "multicast";
  205       }
  206       xasprintf(name, "(%s)", type);
  207    }
  208    else  /* Correctly resolved name.  */
  209       *name = xstrdup(reply.name);
  210 
  211    dns_unqueue(&reply.addr);
  212    return (1);
  213 
  214 error:
  215    warn("dns_get_result: ignoring read error");
  216    /* FIXME: re-align to stream?  restart dns child? */
  217    return (0);
  218 }
  219 
  220 void
  221 dns_poll(void)
  222 {
  223    struct addr ip;
  224    char *name;
  225 
  226    if (pid == -1)
  227       return; /* no child was started - we're not doing any DNS */
  228 
  229    while (dns_get_result(&ip, &name)) {
  230       /* push into hosts_db */
  231       struct bucket *b = host_find(&ip);
  232 
  233       if (b == NULL) {
  234          verbosef("resolved %s to %s but it's not in the DB!",
  235             addr_to_str(&ip), name);
  236          return;
  237       }
  238       if (b->u.host.dns != NULL) {
  239          verbosef("resolved %s to %s but it's already in the DB!",
  240             addr_to_str(&ip), name);
  241          return;
  242       }
  243       b->u.host.dns = name;
  244    }
  245 }
  246 
  247 /* ------------------------------------------------------------------------ */
  248 
  249 struct qitem {
  250    STAILQ_ENTRY(qitem) entries;
  251    struct addr ip;
  252 };
  253 
  254 static STAILQ_HEAD(qhead, qitem) queue = STAILQ_HEAD_INITIALIZER(queue);
  255 
  256 static void
  257 enqueue(const struct addr *const ip)
  258 {
  259    struct qitem *i;
  260 
  261    i = xmalloc(sizeof(*i));
  262    memcpy(&i->ip, ip, sizeof(i->ip));
  263    STAILQ_INSERT_TAIL(&queue, i, entries);
  264    verbosef("DNS: enqueued %s", addr_to_str(ip));
  265 }
  266 
  267 /* Return non-zero and populate <ip> pointer if queue isn't empty. */
  268 static int
  269 dequeue(struct addr *ip)
  270 {
  271    struct qitem *i;
  272 
  273    i = STAILQ_FIRST(&queue);
  274    if (i == NULL)
  275       return (0);
  276    STAILQ_REMOVE_HEAD(&queue, entries);
  277    memcpy(ip, &i->ip, sizeof(*ip));
  278    free(i);
  279    verbosef("DNS: dequeued %s", addr_to_str(ip));
  280    return 1;
  281 }
  282 
  283 static void
  284 xwrite(const int d, const void *buf, const size_t nbytes)
  285 {
  286    ssize_t ret = write(d, buf, nbytes);
  287 
  288    if (ret == -1)
  289       err(1, "write");
  290    if (ret != (ssize_t)nbytes)
  291       err(1, "wrote %d bytes instead of all %d bytes", (int)ret, (int)nbytes);
  292 }
  293 
  294 static void
  295 dns_main(void)
  296 {
  297    struct addr ip;
  298 
  299    setproctitle("DNS child");
  300    fd_set_nonblock(dns_sock[CHILD]);
  301    verbosef("DNS child entering main DNS loop");
  302    for (;;) {
  303       int blocking;
  304 
  305       if (STAILQ_EMPTY(&queue)) {
  306          blocking = 1;
  307          fd_set_block(dns_sock[CHILD]);
  308          verbosef("entering blocking read loop");
  309       } else {
  310          blocking = 0;
  311          fd_set_nonblock(dns_sock[CHILD]);
  312          verbosef("non-blocking poll");
  313       }
  314       for (;;) {
  315          /* While we have input to process... */
  316          ssize_t numread = read(dns_sock[CHILD], &ip, sizeof(ip));
  317          if (numread == 0)
  318             exit(0); /* end of file, nothing more to do here. */
  319          if (numread == -1) {
  320             if (!blocking && (errno == EAGAIN))
  321                break; /* ran out of input */
  322             /* else error */
  323             err(1, "DNS: read failed");
  324          }
  325          if (numread != sizeof(ip))
  326             err(1, "DNS: read got %zu bytes, expecting %zu",
  327                numread, sizeof(ip));
  328          enqueue(&ip);
  329          if (blocking) {
  330             /* After one blocking read, become non-blocking so that when we
  331              * run out of input we fall through to queue processing.
  332              */
  333             blocking = 0;
  334             fd_set_nonblock(dns_sock[CHILD]);
  335          }
  336       }
  337 
  338       /* Process queue. */
  339       if (dequeue(&ip)) {
  340          struct dns_reply reply;
  341          struct sockaddr_in sin;
  342          struct sockaddr_in6 sin6;
  343          struct hostent *he;
  344          char host[NI_MAXHOST];
  345          int ret, flags;
  346 
  347          reply.addr = ip;
  348          flags = NI_NAMEREQD;
  349 #  ifdef NI_IDN
  350          flags |= NI_IDN;
  351 #  endif
  352          switch (ip.family) {
  353             case IPv4:
  354                sin.sin_family = AF_INET;
  355                sin.sin_addr.s_addr = ip.ip.v4;
  356                ret = getnameinfo((struct sockaddr *) &sin, sizeof(sin),
  357                                  host, sizeof(host), NULL, 0, flags);
  358                if (ret == EAI_FAMILY) {
  359                   verbosef("getnameinfo error %s, trying gethostbyname",
  360                      gai_strerror(ret));
  361                   he = gethostbyaddr(&sin.sin_addr.s_addr,
  362                      sizeof(sin.sin_addr.s_addr), sin.sin_family);
  363                   if (he == NULL) {
  364                      ret = EAI_FAIL;
  365                      verbosef("gethostbyname error %s", hstrerror(h_errno));
  366                   } else {
  367                      ret = 0;
  368                      strlcpy(host, he->h_name, sizeof(host));
  369                   }
  370                }
  371                break;
  372             case IPv6:
  373                sin6.sin6_family = AF_INET6;
  374                memcpy(&sin6.sin6_addr, &ip.ip.v6, sizeof(sin6.sin6_addr));
  375                ret = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6),
  376                                  host, sizeof(host), NULL, 0, flags);
  377                break;
  378             default:
  379                errx(1, "unexpected ip.family = %d", ip.family);
  380          }
  381 
  382          if (ret != 0) {
  383             reply.name[0] = '\0';
  384             reply.error = ret;
  385          } else {
  386             assert(sizeof(reply.name) > sizeof(char *)); /* not just a ptr */
  387             strlcpy(reply.name, host, sizeof(reply.name));
  388             reply.error = 0;
  389          }
  390          fd_set_block(dns_sock[CHILD]);
  391          xwrite(dns_sock[CHILD], &reply, sizeof(reply));
  392          verbosef("DNS: %s is \"%s\".", addr_to_str(&reply.addr),
  393             (ret == 0) ? reply.name : gai_strerror(ret));
  394       }
  395    }
  396 }
  397 
  398 /* vim:set ts=3 sw=3 tw=78 expandtab: */