/*
* nsd.c -- nsd(8)
*
* Copyright (c) 2001-2006, NLnet Labs. All rights reserved.
*
* See LICENSE for the license.
*
*/

#include "config.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef HAVE_GRP_H
#include <grp.h>
#endif /* HAVE_GRP_H */
#ifdef HAVE_SETUSERCONTEXT
#ifdef HAVE_LOGIN_CAP_H
#include <login_cap.h>
#endif /* HAVE_LOGIN_CAP_H */
#endif /* HAVE_SETUSERCONTEXT */
#ifdef HAVE_OPENSSL_RAND_H
#include <openssl/rand.h>
#endif

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
#endif

#include "nsd.h"
#include "options.h"
#include "tsig.h"
#include "remote.h"
#include "xfrd-disk.h"
#include "ipc.h"
#ifdef USE_DNSTAP
#include "dnstap/dnstap_collector.h"
#endif
#include "util/proxy_protocol.h"

/* The server handler... */
struct nsd nsd;
static char hostname[MAXHOSTNAMELEN];
extern config_parser_state_type* cfg_parser;
static void version(void) ATTR_NORETURN;

/*
* Print the help text.
*
*/
static void
usage (void)
{
       fprintf(stderr, "Usage: nsd [OPTION]...\n");
       fprintf(stderr, "Name Server Daemon.\n\n");
       fprintf(stderr,
               "Supported options:\n"
               "  -4                   Only listen to IPv4 connections.\n"
               "  -6                   Only listen to IPv6 connections.\n"
               "  -a ip-address[@port] Listen to the specified incoming IP address (and port)\n"
               "                       May be specified multiple times).\n"
               "  -c configfile        Read specified configfile instead of %s.\n"
               "  -d                   do not fork as a daemon process.\n"
#ifndef NDEBUG
               "  -F facilities        Specify the debug facilities.\n"
#endif /* NDEBUG */
               "  -h                   Print this help information.\n"
               , CONFIGFILE);
       fprintf(stderr,
               "  -i identity          Specify the identity when queried for id.server CHAOS TXT.\n"
               "  -I nsid              Specify the NSID. This must be a hex string.\n"
#ifndef NDEBUG
               "  -L level             Specify the debug level.\n"
#endif /* NDEBUG */
               "  -l filename          Specify the log file.\n"
               "  -N server-count      The number of servers to start.\n"
               "  -n tcp-count         The maximum number of TCP connections per server.\n"
               "  -P pidfile           Specify the PID file to write.\n"
               "  -p port              Specify the port to listen to.\n"
               "  -s seconds           Dump statistics every SECONDS seconds.\n"
               "  -t chrootdir         Change root to specified directory on startup.\n"
               );
       fprintf(stderr,
               "  -u user              Change effective uid to the specified user.\n"
               "  -V level             Specify verbosity level.\n"
               "  -v                   Print version information.\n"
               );
       fprintf(stderr, "Version %s. Report bugs to <%s>.\n",
               PACKAGE_VERSION, PACKAGE_BUGREPORT);
}

/*
* Print the version exit.
*
*/
static void
version(void)
{
       fprintf(stderr, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
       fprintf(stderr, "Written by NLnet Labs.\n\n");
       fprintf(stderr, "Configure line: %s\n", CONFCMDLINE);
#ifdef USE_MINI_EVENT
       fprintf(stderr, "Event loop: internal (uses select)\n");
#else
#  if defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP)
       fprintf(stderr, "Event loop: %s %s (uses %s)\n",
               "libev",
               nsd_event_vs(),
               nsd_event_method());
#  else
       fprintf(stderr, "Event loop: %s %s (uses %s)\n",
               "libevent",
               nsd_event_vs(),
               nsd_event_method());
#  endif
#endif
#ifdef HAVE_SSL
       fprintf(stderr, "Linked with %s\n\n",
#  ifdef SSLEAY_VERSION
               SSLeay_version(SSLEAY_VERSION)
#  else
               OpenSSL_version(OPENSSL_VERSION)
#  endif
               );
#endif
       fprintf(stderr,
               "Copyright (C) 2001-2020 NLnet Labs.  This is free software.\n"
               "There is NO warranty; not even for MERCHANTABILITY or FITNESS\n"
               "FOR A PARTICULAR PURPOSE.\n");
       exit(0);
}

static void
setup_verifier_environment(void)
{
       size_t i;
       int ret, ip4, ip6;
       char *buf, host[NI_MAXHOST], serv[NI_MAXSERV];
       size_t size, cnt = 0;

       /* allocate large enough buffer to hold a list of all ip addresses.
          ((" " + INET6_ADDRSTRLEN + "@" + "65535") * n) + "\0" */
       size = ((INET6_ADDRSTRLEN + 1 + 5 + 1) * nsd.verify_ifs) + 1;
       buf = xalloc(size);

       ip4 = ip6 = 0;
       for(i = 0; i < nsd.verify_ifs; i++) {
               ret = getnameinfo(
                       (struct sockaddr *)&nsd.verify_udp[i].addr.ai_addr,
                       nsd.verify_udp[i].addr.ai_addrlen,
                       host, sizeof(host), serv, sizeof(serv),
                       NI_NUMERICHOST | NI_NUMERICSERV);
               if(ret != 0) {
                       log_msg(LOG_ERR, "error in getnameinfo: %s",
                               gai_strerror(ret));
                       continue;
               }
               buf[cnt++] = ' ';
               cnt += strlcpy(&buf[cnt], host, size - cnt);
               assert(cnt < size);
               buf[cnt++] = '@';
               cnt += strlcpy(&buf[cnt], serv, size - cnt);
               assert(cnt < size);
#ifdef INET6
               if (nsd.verify_udp[i].addr.ai_family == AF_INET6 && !ip6) {
                       setenv("VERIFY_IPV6_ADDRESS", host, 1);
                       setenv("VERIFY_IPV6_PORT", serv, 1);
                       setenv("VERIFY_IP_ADDRESS", host, 1);
                       setenv("VERIFY_PORT", serv, 1);
                       ip6 = 1;
               } else
#endif
               if (!ip4) {
                       assert(nsd.verify_udp[i].addr.ai_family == AF_INET);
                       setenv("VERIFY_IPV4_ADDRESS", host, 1);
                       setenv("VERIFY_IPV4_PORT", serv, 1);
                       if (!ip6) {
                               setenv("VERIFY_IP_ADDRESS", host, 1);
                               setenv("VERIFY_PORT", serv, 1);
                       }
                       ip4 = 1;
               }
       }

       setenv("VERIFY_IP_ADDRESSES", &buf[1], 1);
       free(buf);
}

static void
copyaddrinfo(struct nsd_addrinfo *dest, struct addrinfo *src)
{
       dest->ai_flags = src->ai_flags;
       dest->ai_family = src->ai_family;
       dest->ai_socktype = src->ai_socktype;
       dest->ai_addrlen = src->ai_addrlen;
       memcpy(&dest->ai_addr, src->ai_addr, src->ai_addrlen);
}

static void
setup_socket(
       struct nsd_socket *sock, const char *node, const char *port,
       struct addrinfo *hints)
{
       int ret;
       char *host;
       char host_buf[sizeof("65535") + INET6_ADDRSTRLEN + 1 /* '\0' */];
       const char *service;
       struct addrinfo *addr = NULL;

       sock->fib = -1;
       if(node) {
               char *sep;

               if (strlcpy(host_buf, node, sizeof(host_buf)) >= sizeof(host_buf)) {
                       error("cannot parse address '%s': %s", node,
                           strerror(ENAMETOOLONG));
               }

               host = host_buf;
               sep = strchr(host_buf, '@');
               if(sep != NULL) {
                       *sep = '\0';
                       service = sep + 1;
               } else {
                       service = port;
               }
       } else {
               host = NULL;
               service = port;
       }

       if((ret = getaddrinfo(host, service, hints, &addr)) == 0) {
               copyaddrinfo(&sock->addr, addr);
               freeaddrinfo(addr);
       } else {
               error("cannot parse address '%s': getaddrinfo: %s %s",
                     host ? host : "(null)",
                     gai_strerror(ret),
                     ret==EAI_SYSTEM ? strerror(errno) : "");
       }
}

static void
figure_socket_servers(
       struct nsd_socket *sock, struct ip_address_option *ip)
{
       int i;
       struct range_option *server;

       sock->servers = xalloc_zero(nsd_bitset_size(nsd.child_count));
       region_add_cleanup(nsd.region, free, sock->servers);
       nsd_bitset_init(sock->servers, nsd.child_count);

       if(!ip || !ip->servers) {
               /* every server must listen on this socket */
               for(i = 0; i < (int)nsd.child_count; i++) {
                       nsd_bitset_set(sock->servers, i);
               }
               return;
       }

       /* only specific servers must listen on this socket */
       for(server = ip->servers; server; server = server->next) {
               if(server->first == server->last) {
                       if(server->first <= 0) {
                               error("server %d specified for ip-address %s "
                                     "is invalid; server ranges are 1-based",
                                     server->first, ip->address);
                       } else if(server->last > (int)nsd.child_count) {
                               error("server %d specified for ip-address %s "
                                     "exceeds number of servers configured "
                                     "in server-count",
                                     server->first, ip->address);
                       }
               } else {
                       /* parse_range must ensure range itself is valid */
                       assert(server->first < server->last);
                       if(server->first <= 0) {
                               error("server range %d-%d specified for "
                                     "ip-address %s is invalid; server "
                                     "ranges are 1-based",
                                     server->first, server->last, ip->address);
                       } else if(server->last > (int)nsd.child_count) {
                               error("server range %d-%d specified for "
                                     "ip-address %s exceeds number of servers "
                                     "configured in server-count",
                                     server->first, server->last, ip->address);
                       }
               }
               for(i = server->first - 1; i < server->last; i++) {
                       nsd_bitset_set(sock->servers, i);
               }
       }
}

static void
figure_default_sockets(
       struct nsd_socket **udp, struct nsd_socket **tcp, size_t *ifs,
       const char *node, const char *udp_port, const char *tcp_port,
       const struct addrinfo *hints)
{
       size_t i = 0, n = 1;
       struct addrinfo ai[2] = { *hints, *hints };

       assert(udp != NULL);
       assert(tcp != NULL);
       assert(ifs != NULL);

       ai[0].ai_socktype = SOCK_DGRAM;
       ai[1].ai_socktype = SOCK_STREAM;

#ifdef INET6
#ifdef IPV6_V6ONLY
       if (hints->ai_family == AF_UNSPEC) {
               ai[0].ai_family = AF_INET6;
               ai[1].ai_family = AF_INET6;
               n++;
       }
#endif /* IPV6_V6ONLY */
#endif /* INET6 */

       *udp = xalloc_zero((n + 1) * sizeof(struct nsd_socket));
       *tcp = xalloc_zero((n + 1) * sizeof(struct nsd_socket));
       region_add_cleanup(nsd.region, free, *udp);
       region_add_cleanup(nsd.region, free, *tcp);

#ifdef INET6
       if(hints->ai_family == AF_UNSPEC) {
               /*
                * With IPv6 we'd like to open two separate sockets, one for
                * IPv4 and one for IPv6, both listening to the wildcard
                * address (unless the -4 or -6 flags are specified).
                *
                * However, this is only supported on platforms where we can
                * turn the socket option IPV6_V6ONLY _on_. Otherwise we just
                * listen to a single IPv6 socket and any incoming IPv4
                * connections will be automatically mapped to our IPv6
                * socket.
                */
#ifdef IPV6_V6ONLY
               int r;
               struct addrinfo *addrs[2] = { NULL, NULL };

               if((r = getaddrinfo(node, udp_port, &ai[0], &addrs[0])) == 0 &&
                  (r = getaddrinfo(node, tcp_port, &ai[1], &addrs[1])) == 0)
               {
                       (*udp)[i].flags |= NSD_SOCKET_IS_OPTIONAL;
                       (*udp)[i].fib = -1;
                       copyaddrinfo(&(*udp)[i].addr, addrs[0]);
                       figure_socket_servers(&(*udp)[i], NULL);
                       (*tcp)[i].flags |= NSD_SOCKET_IS_OPTIONAL;
                       (*tcp)[i].fib = -1;
                       copyaddrinfo(&(*tcp)[i].addr, addrs[1]);
                       figure_socket_servers(&(*tcp)[i], NULL);
                       i++;
               } else {
                       log_msg(LOG_WARNING, "No IPv6, fallback to IPv4. getaddrinfo: %s",
                         r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
               }

               if(addrs[0])
                       freeaddrinfo(addrs[0]);
               if(addrs[1])
                       freeaddrinfo(addrs[1]);

               ai[0].ai_family = AF_INET;
               ai[1].ai_family = AF_INET;
#endif /* IPV6_V6ONLY */
       }
#endif /* INET6 */

       *ifs = i + 1;
       setup_socket(&(*udp)[i], node, udp_port, &ai[0]);
       figure_socket_servers(&(*udp)[i], NULL);
       setup_socket(&(*tcp)[i], node, tcp_port, &ai[1]);
       figure_socket_servers(&(*tcp)[i], NULL);
}

#ifdef HAVE_GETIFADDRS
static int
find_device(
       struct nsd_socket *sock,
       const struct ifaddrs *ifa)
{
       for(; ifa != NULL; ifa = ifa->ifa_next) {
               if((ifa->ifa_addr == NULL) ||
                  (ifa->ifa_addr->sa_family != sock->addr.ai_family) ||
                  ((ifa->ifa_flags & IFF_UP) == 0 ||
                   (ifa->ifa_flags & IFF_LOOPBACK) != 0 ||
                   (ifa->ifa_flags & IFF_RUNNING) == 0))
               {
                       continue;
               }

#ifdef INET6
               if(ifa->ifa_addr->sa_family == AF_INET6) {
                       struct sockaddr_in6 *sa1, *sa2;
                       size_t sz = sizeof(struct in6_addr);
                       sa1 = (struct sockaddr_in6 *)ifa->ifa_addr;
                       sa2 = (struct sockaddr_in6 *)&sock->addr.ai_addr;
                       if(memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sz) == 0) {
                               break;
                       }
               } else
#endif
               if(ifa->ifa_addr->sa_family == AF_INET) {
                       struct sockaddr_in *sa1, *sa2;
                       sa1 = (struct sockaddr_in *)ifa->ifa_addr;
                       sa2 = (struct sockaddr_in *)&sock->addr.ai_addr;
                       if(sa1->sin_addr.s_addr == sa2->sin_addr.s_addr) {
                               break;
                       }
               }
       }

       if(ifa != NULL) {
               size_t len = strlcpy(sock->device, ifa->ifa_name, sizeof(sock->device));
               if(len < sizeof(sock->device)) {
                       char *colon = strchr(sock->device, ':');
                       if(colon != NULL)
                               *colon = '\0';
                       return 1;
               }
       }

       return 0;
}
#endif /* HAVE_GETIFADDRS */

static void
figure_sockets(
       struct nsd_socket **udp, struct nsd_socket **tcp, size_t *ifs,
       struct ip_address_option *ips,
       const char *node, const char *udp_port, const char *tcp_port,
       const struct addrinfo *hints)
{
       size_t i = 0;
       struct addrinfo ai = *hints;
       struct ip_address_option *ip;
#ifdef HAVE_GETIFADDRS
       struct ifaddrs *ifa = NULL;
#endif
       int bind_device = 0;

       if(!ips) {
               figure_default_sockets(
                       udp, tcp, ifs, node, udp_port, tcp_port, hints);
               return;
       }

       *ifs = 0;
       for(ip = ips; ip; ip = ip->next) {
               (*ifs)++;
               bind_device |= (ip->dev != 0);
       }

#ifdef HAVE_GETIFADDRS
       if(bind_device && getifaddrs(&ifa) == -1) {
               error("getifaddrs failed: %s", strerror(errno));
       }
#endif

       *udp = xalloc_zero((*ifs + 1) * sizeof(struct nsd_socket));
       *tcp = xalloc_zero((*ifs + 1) * sizeof(struct nsd_socket));
       region_add_cleanup(nsd.region, free, *udp);
       region_add_cleanup(nsd.region, free, *tcp);

       ai.ai_flags |= AI_NUMERICHOST;
       for(ip = ips, i = 0; ip; ip = ip->next, i++) {
               ai.ai_socktype = SOCK_DGRAM;
               setup_socket(&(*udp)[i], ip->address, udp_port, &ai);
               figure_socket_servers(&(*udp)[i], ip);
               ai.ai_socktype = SOCK_STREAM;
               setup_socket(&(*tcp)[i], ip->address, tcp_port, &ai);
               figure_socket_servers(&(*tcp)[i], ip);
               if(ip->fib != -1) {
                       (*udp)[i].fib = ip->fib;
                       (*tcp)[i].fib = ip->fib;
               }
#ifdef HAVE_GETIFADDRS
               if(ip->dev != 0) {
                       (*udp)[i].flags |= NSD_BIND_DEVICE;
                       (*tcp)[i].flags |= NSD_BIND_DEVICE;
                       if(ifa != NULL && (find_device(&(*udp)[i], ifa) == 0 ||
                                          find_device(&(*tcp)[i], ifa) == 0))
                       {
                               error("cannot find device for ip-address %s",
                                     ip->address);
                       }
               }
#endif
       }

       assert(i == *ifs);

#ifdef HAVE_GETIFADDRS
       if(ifa != NULL) {
               freeifaddrs(ifa);
       }
#endif
}

/* print server affinity for given socket. "*" if socket has no affinity with
  any specific server, "x-y" if socket has affinity with more than two
  consecutively numbered servers, "x" if socket has affinity with a specific
  server number, which is not necessarily just one server. e.g. "1 3" is
  printed if socket has affinity with servers number one and three, but not
  server number two. */
static ssize_t
print_socket_servers(struct nsd_socket *sock, char *buf, size_t bufsz)
{
       int i, x, y, z, n = (int)(sock->servers->size);
       char *sep = "";
       size_t off, tot;
       ssize_t cnt = 0;

       assert(bufsz != 0);

       off = tot = 0;
       x = y = z = -1;
       for (i = 0; i <= n; i++) {
               if (i == n || !nsd_bitset_isset(sock->servers, i)) {
                       cnt = 0;
                       if (i == n && x == -1) {
                               assert(y == -1);
                               assert(z == -1);
                               cnt = snprintf(buf, bufsz, "-");
                       } else if (y > z) {
                               assert(x > z);
                               if (x == 0 && y == (n - 1)) {
                                       assert(z == -1);
                                       cnt = snprintf(buf+off, bufsz-off,
                                                      "*");
                               } else if (x == y) {
                                       cnt = snprintf(buf+off, bufsz-off,
                                                      "%s%d", sep, x+1);
                               } else if (x == (y - 1)) {
                                       cnt = snprintf(buf+off, bufsz-off,
                                                      "%s%d %d", sep, x+1, y+1);
                               } else {
                                       assert(y > (x + 1));
                                       cnt = snprintf(buf+off, bufsz-off,
                                                      "%s%d-%d", sep, x+1, y+1);
                               }
                       }
                       z = i;
                       if (cnt > 0) {
                               tot += (size_t)cnt;
                               off = (tot < bufsz) ? tot : bufsz - 1;
                               sep = " ";
                       } else if (cnt < 0) {
                               return -1;
                       }
               } else if (x <= z) {
                       x = y = i;
               } else {
                       assert(x > z);
                       y = i;
               }
       }

       return tot;
}

static void
print_sockets(
       struct nsd_socket *udp, struct nsd_socket *tcp, size_t ifs)
{
       char sockbuf[INET6_ADDRSTRLEN + 6 + 1];
       char *serverbuf;
       size_t i, serverbufsz, servercnt;
       const char *fmt = "listen on ip-address %s (%s) with server(s): %s";
       struct nsd_bitset *servers;

       if(ifs == 0) {
               return;
       }

       assert(udp != NULL);
       assert(tcp != NULL);

       servercnt = udp[0].servers->size;
       serverbufsz = (((servercnt / 10) * servercnt) + servercnt) + 1;
       serverbuf = xalloc(serverbufsz);

       /* warn user of unused servers */
       servers = xalloc(nsd_bitset_size(servercnt));
       nsd_bitset_init(servers, (size_t)servercnt);

       for(i = 0; i < ifs; i++) {
               assert(udp[i].servers->size == servercnt);
               addrport2str((void*)&udp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
               print_socket_servers(&udp[i], serverbuf, serverbufsz);
               nsd_bitset_or(servers, servers, udp[i].servers);
               VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "udp", serverbuf));
               assert(tcp[i].servers->size == servercnt);
               addrport2str((void*)&tcp[i].addr.ai_addr, sockbuf, sizeof(sockbuf));
               print_socket_servers(&tcp[i], serverbuf, serverbufsz);
               nsd_bitset_or(servers, servers, tcp[i].servers);
               VERBOSITY(3, (LOG_NOTICE, fmt, sockbuf, "tcp", serverbuf));
       }


       /* warn user of unused servers */
       for(i = 0; i < servercnt; i++) {
               if(!nsd_bitset_isset(servers, i)) {
                       log_msg(LOG_WARNING, "server %zu will not listen on "
                                            "any specified ip-address", i+1);
               }
       }
       free(serverbuf);
       free(servers);
}

#ifdef HAVE_CPUSET_T
static void free_cpuset(void *ptr)
{
       cpuset_t *set = (cpuset_t *)ptr;
       cpuset_destroy(set);
}
#endif

/*
* Fetch the nsd parent process id from the nsd pidfile
*
*/
pid_t
readpid(const char *file)
{
       int fd;
       pid_t pid;
       char pidbuf[16];
       char *t;
       int l;

       if ((fd = open(file, O_RDONLY)) == -1) {
               return -1;
       }

       if (((l = read(fd, pidbuf, sizeof(pidbuf)))) == -1) {
               close(fd);
               return -1;
       }

       close(fd);

       /* Empty pidfile means no pidfile... */
       if (l == 0) {
               errno = ENOENT;
               return -1;
       }

       pid = (pid_t) strtol(pidbuf, &t, 10);

       if (*t && *t != '\n') {
               return -1;
       }
       return pid;
}

/*
* Store the nsd parent process id in the nsd pidfile
*
*/
int
writepid(struct nsd *nsd)
{
       int fd;
       char pidbuf[32];
       size_t count = 0;
       if(!nsd->pidfile || !nsd->pidfile[0])
               return 0;

       snprintf(pidbuf, sizeof(pidbuf), "%lu\n", (unsigned long) nsd->pid);

       if((fd = open(nsd->pidfile, O_WRONLY | O_CREAT | O_TRUNC
#ifdef O_NOFOLLOW
               | O_NOFOLLOW
#endif
               , 0644)) == -1) {
               log_msg(LOG_ERR, "cannot open pidfile %s: %s",
                       nsd->pidfile, strerror(errno));
               return -1;
       }

       while(count < strlen(pidbuf)) {
               ssize_t r = write(fd, pidbuf+count, strlen(pidbuf)-count);
               if(r == -1) {
                       if(errno == EAGAIN || errno == EINTR)
                               continue;
                       log_msg(LOG_ERR, "cannot write pidfile %s: %s",
                               nsd->pidfile, strerror(errno));
                       close(fd);
                       return -1;
               } else if(r == 0) {
                       log_msg(LOG_ERR, "cannot write any bytes to "
                               "pidfile %s: write returns 0 bytes written",
                               nsd->pidfile);
                       close(fd);
                       return -1;
               }
               count += r;
       }
       close(fd);

       if (chown(nsd->pidfile, nsd->uid, nsd->gid) == -1) {
               log_msg(LOG_ERR, "cannot chown %u.%u %s: %s",
                       (unsigned) nsd->uid, (unsigned) nsd->gid,
                       nsd->pidfile, strerror(errno));
               return -1;
       }

       return 0;
}

void
unlinkpid(const char* file)
{
       int fd = -1;

       if (file && file[0]) {
               /* truncate pidfile */
               fd = open(file, O_WRONLY | O_TRUNC, 0644);
               if (fd == -1) {
                       /* Truncate the pid file.  */
                       log_msg(LOG_ERR, "can not truncate the pid file %s: %s", file, strerror(errno));
               } else {
                       close(fd);
               }

               /* unlink pidfile */
               if (unlink(file) == -1) {
                       /* this unlink may not work if the pidfile is located
                        * outside of the chroot/workdir or we no longer
                        * have permissions */
                       VERBOSITY(3, (LOG_WARNING,
                               "failed to unlink pidfile %s: %s",
                               file, strerror(errno)));
               }
       }
}

/*
* Incoming signals, set appropriate actions.
*
*/
void
sig_handler(int sig)
{
       /* To avoid race cond. We really don't want to use log_msg() in this handler */

       /* Are we a child server? */
       if (nsd.server_kind != NSD_SERVER_MAIN) {
               switch (sig) {
               case SIGCHLD:
                       nsd.signal_hint_child = 1;
                       break;
               case SIGALRM:
                       break;
               case SIGINT:
               case SIGTERM:
                       nsd.signal_hint_quit = 1;
                       break;
               case SIGILL:
               case SIGUSR1:   /* Dump stats on SIGUSR1.  */
                       nsd.signal_hint_statsusr = 1;
                       break;
               default:
                       break;
               }
               return;
       }

       /* We are the main process */
       switch (sig) {
       case SIGCHLD:
               nsd.signal_hint_child = 1;
               return;
       case SIGHUP:
               nsd.signal_hint_reload_hup = 1;
               return;
       case SIGALRM:
               nsd.signal_hint_stats = 1;
               break;
       case SIGILL:
               /*
                * For backwards compatibility with BIND 8 and older
                * versions of NSD.
                */
               nsd.signal_hint_statsusr = 1;
               break;
       case SIGUSR1:
               /* Dump statistics.  */
               nsd.signal_hint_statsusr = 1;
               break;
       case SIGINT:
       case SIGTERM:
       default:
               nsd.signal_hint_shutdown = 1;
               break;
       }
}

/*
* Statistic output...
*
*/
#ifdef BIND8_STATS
void
bind8_stats (struct nsd *nsd)
{
       char buf[MAXSYSLOGMSGLEN];
       char *msg, *t;
       int i, len;
       struct nsdst st;

       /* Current time... */
       time_t now;
       if(!nsd->st_period)
               return;
       time(&now);

       memcpy(&st, nsd->st, sizeof(st));
       stats_subtract(&st, &nsd->stat_proc);

       /* NSTATS */
       t = msg = buf + snprintf(buf, MAXSYSLOGMSGLEN, "NSTATS %lld %lu",
                                (long long) now, (unsigned long) st.boot);
       for (i = 0; i <= 255; i++) {
               /* How much space left? */
               if ((len = buf + MAXSYSLOGMSGLEN - t) < 32) {
                       log_msg(LOG_INFO, "%s", buf);
                       t = msg;
                       len = buf + MAXSYSLOGMSGLEN - t;
               }

               if (st.qtype[i] != 0) {
                       t += snprintf(t, len, " %s=%lu", rrtype_to_string(i), st.qtype[i]);
               }
       }
       if (t > msg)
               log_msg(LOG_INFO, "%s", buf);

       /* XSTATS */
       /* Only print it if we're in the main daemon or have anything to report... */
       if (nsd->server_kind == NSD_SERVER_MAIN
           || st.dropped || st.raxfr || st.rixfr || (st.qudp + st.qudp6 - st.dropped)
           || st.txerr || st.opcode[OPCODE_QUERY] || st.opcode[OPCODE_IQUERY]
           || st.wrongzone || st.ctcp + st.ctcp6 || st.rcode[RCODE_SERVFAIL]
           || st.rcode[RCODE_FORMAT] || st.nona || st.rcode[RCODE_NXDOMAIN]
           || st.opcode[OPCODE_UPDATE]) {

               log_msg(LOG_INFO, "XSTATS %lld %lu"
                       " RR=%lu RNXD=%lu RFwdR=%lu RDupR=%lu RFail=%lu RFErr=%lu RErr=%lu RAXFR=%lu RIXFR=%lu"
                       " RLame=%lu ROpts=%lu SSysQ=%lu SAns=%lu SFwdQ=%lu SDupQ=%lu SErr=%lu RQ=%lu"
                       " RIQ=%lu RFwdQ=%lu RDupQ=%lu RTCP=%lu SFwdR=%lu SFail=%lu SFErr=%lu SNaAns=%lu"
                       " SNXD=%lu RUQ=%lu RURQ=%lu RUXFR=%lu RUUpd=%lu",
                       (long long) now, (unsigned long) st.boot,
                       st.dropped, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0,
                       (unsigned long)0, (unsigned long)0, st.raxfr, st.rixfr, (unsigned long)0, (unsigned long)0,
                       (unsigned long)0, st.qudp + st.qudp6 - st.dropped, (unsigned long)0,
                       (unsigned long)0, st.txerr,
                       st.opcode[OPCODE_QUERY], st.opcode[OPCODE_IQUERY], st.wrongzone,
                       (unsigned long)0, st.ctcp + st.ctcp6,
                       (unsigned long)0, st.rcode[RCODE_SERVFAIL], st.rcode[RCODE_FORMAT],
                       st.nona, st.rcode[RCODE_NXDOMAIN],
                       (unsigned long)0, (unsigned long)0, (unsigned long)0, st.opcode[OPCODE_UPDATE]);
       }

}
#endif /* BIND8_STATS */

static
int cookie_secret_file_read(nsd_type* nsd) {
       char secret[NSD_COOKIE_SECRET_SIZE * 2 + 2/*'\n' and '\0'*/];
       char const* file = nsd->options->cookie_secret_file;
       FILE* f;
       int corrupt = 0;
       size_t count;

       assert( nsd->options->cookie_secret_file != NULL );
       f = fopen(file, "r");
       /* a non-existing cookie file is not an error */
       if( f == NULL ) { return errno != EPERM; }
       /* cookie secret file exists and is readable */
       nsd->cookie_count = 0;
       for( count = 0; count < NSD_COOKIE_HISTORY_SIZE; count++ ) {
               size_t secret_len = 0;
               ssize_t decoded_len = 0;
               if( fgets(secret, sizeof(secret), f) == NULL ) { break; }
               secret_len = strlen(secret);
               if( secret_len == 0 ) { break; }
               assert( secret_len <= sizeof(secret) );
               secret_len = secret[secret_len - 1] == '\n' ? secret_len - 1 : secret_len;
               if( secret_len != NSD_COOKIE_SECRET_SIZE * 2 ) { corrupt++; break; }
               /* needed for `hex_pton`; stripping potential `\n` */
               secret[secret_len] = '\0';
               decoded_len = hex_pton(secret, nsd->cookie_secrets[count].cookie_secret,
                                      NSD_COOKIE_SECRET_SIZE);
               if( decoded_len != NSD_COOKIE_SECRET_SIZE ) { corrupt++; break; }
               nsd->cookie_count++;
       }
       fclose(f);
       return corrupt == 0;
}

extern char *optarg;
extern int optind;

int
main(int argc, char *argv[])
{
       /* Scratch variables... */
       int c;
       pid_t   oldpid;
       size_t i;
       struct sigaction action;
#ifdef HAVE_GETPWNAM
       struct passwd *pwd = NULL;
#endif /* HAVE_GETPWNAM */

       struct ip_address_option *ip;
       struct addrinfo hints;
       const char *udp_port = 0;
       const char *tcp_port = 0;
       const char *verify_port = 0;

       const char *configfile = CONFIGFILE;

       char* argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0];

       log_init(argv0);

       /* Initialize the server handler... */
       memset(&nsd, 0, sizeof(struct nsd));
       nsd.region      = region_create(xalloc, free);
       nsd.pidfile     = 0;
       nsd.server_kind = NSD_SERVER_MAIN;
       memset(&hints, 0, sizeof(hints));
       hints.ai_family = DEFAULT_AI_FAMILY;
       hints.ai_flags = AI_PASSIVE;
       nsd.identity    = 0;
       nsd.version     = VERSION;
       nsd.username    = 0;
       nsd.chrootdir   = 0;
       nsd.nsid        = NULL;
       nsd.nsid_len    = 0;
       nsd.cookie_count = 0;

       nsd.child_count = 0;
       nsd.maximum_tcp_count = 0;
       nsd.current_tcp_count = 0;
       nsd.file_rotation_ok = 0;

       nsd.do_answer_cookie = 1;

       /* Set up our default identity to gethostname(2) */
       if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
               nsd.identity = hostname;
       } else {
               log_msg(LOG_ERR,
                       "failed to get the host name: %s - using default identity",
                       strerror(errno));
               nsd.identity = IDENTITY;
       }

       /* Create region where options will be stored and set defaults */
       nsd.options = nsd_options_create(region_create_custom(xalloc, free,
               DEFAULT_CHUNK_SIZE, DEFAULT_LARGE_OBJECT_SIZE,
               DEFAULT_INITIAL_CLEANUP_SIZE, 1));

       /* Parse the command line... */
       while ((c = getopt(argc, argv, "46a:c:df:hi:I:l:N:n:P:p:s:u:t:X:V:v"
#ifndef NDEBUG /* <mattthijs> only when configured with --enable-checking */
               "F:L:"
#endif /* NDEBUG */
               )) != -1) {
               switch (c) {
               case '4':
                       hints.ai_family = AF_INET;
                       break;
               case '6':
#ifdef INET6
                       hints.ai_family = AF_INET6;
#else /* !INET6 */
                       error("IPv6 support not enabled.");
#endif /* INET6 */
                       break;
               case 'a':
                       ip = region_alloc_zero(
                               nsd.options->region, sizeof(*ip));
                       ip->address = region_strdup(
                               nsd.options->region, optarg);
                       ip->next = nsd.options->ip_addresses;
                       nsd.options->ip_addresses = ip;
                       break;
               case 'c':
                       configfile = optarg;
                       break;
               case 'd':
                       nsd.debug = 1;
                       break;
               case 'f':
                       break;
               case 'h':
                       usage();
                       exit(0);
               case 'i':
                       nsd.identity = optarg;
                       break;
               case 'I':
                       if (nsd.nsid_len != 0) {
                               /* can only be given once */
                               break;
                       }
                       if (strncasecmp(optarg, "ascii_", 6) == 0) {
                               nsd.nsid = xalloc(strlen(optarg+6));
                               nsd.nsid_len = strlen(optarg+6);
                               memmove(nsd.nsid, optarg+6, nsd.nsid_len);
                       } else {
                               if (strlen(optarg) % 2 != 0) {
                                       error("the NSID must be a hex string of an even length.");
                               }
                               nsd.nsid = xalloc(strlen(optarg) / 2);
                               nsd.nsid_len = strlen(optarg) / 2;
                               if (hex_pton(optarg, nsd.nsid, nsd.nsid_len) == -1) {
                                       error("hex string cannot be parsed '%s' in NSID.", optarg);
                               }
                       }
                       break;
               case 'l':
                       nsd.log_filename = optarg;
                       break;
               case 'N':
                       i = atoi(optarg);
                       if (i <= 0) {
                               error("number of child servers must be greater than zero.");
                       } else {
                               nsd.child_count = i;
                       }
                       break;
               case 'n':
                       i = atoi(optarg);
                       if (i <= 0) {
                               error("number of concurrent TCP connections must greater than zero.");
                       } else {
                               nsd.maximum_tcp_count = i;
                       }
                       break;
               case 'P':
                       nsd.pidfile = optarg;
                       break;
               case 'p':
                       if (atoi(optarg) == 0) {
                               error("port argument must be numeric.");
                       }
                       tcp_port = optarg;
                       udp_port = optarg;
                       break;
               case 's':
#ifdef BIND8_STATS
                       nsd.st_period = atoi(optarg);
#else /* !BIND8_STATS */
                       error("BIND 8 statistics not enabled.");
#endif /* BIND8_STATS */
                       break;
               case 't':
#ifdef HAVE_CHROOT
                       nsd.chrootdir = optarg;
#else /* !HAVE_CHROOT */
                       error("chroot not supported on this platform.");
#endif /* HAVE_CHROOT */
                       break;
               case 'u':
                       nsd.username = optarg;
                       break;
               case 'V':
                       verbosity = atoi(optarg);
                       break;
               case 'v':
                       version();
                       /* version exits */
                       break;
#ifndef NDEBUG
               case 'F':
                       sscanf(optarg, "%x", &nsd_debug_facilities);
                       break;
               case 'L':
                       sscanf(optarg, "%d", &nsd_debug_level);
                       break;
#endif /* NDEBUG */
               case '?':
               default:
                       usage();
                       exit(1);
               }
       }
       argc -= optind;
       /* argv += optind; */

       /* Commandline parse error */
       if (argc != 0) {
               usage();
               exit(1);
       }

       if (strlen(nsd.identity) > UCHAR_MAX) {
               error("server identity too long (%u characters)",
                     (unsigned) strlen(nsd.identity));
       }
       if(!tsig_init(nsd.region))
               error("init tsig failed");
       pp_init(&write_uint16, &write_uint32);

       /* Read options */
       if(!parse_options_file(nsd.options, configfile, NULL, NULL)) {
               error("could not read config: %s\n", configfile);
       }
       if(!parse_zone_list_file(nsd.options)) {
               error("could not read zonelist file %s\n",
                       nsd.options->zonelistfile);
       }
       if(nsd.options->do_ip4 && !nsd.options->do_ip6) {
               hints.ai_family = AF_INET;
       }
#ifdef INET6
       if(nsd.options->do_ip6 && !nsd.options->do_ip4) {
               hints.ai_family = AF_INET6;
       }
#endif /* INET6 */
       if (verbosity == 0)
               verbosity = nsd.options->verbosity;
#ifndef NDEBUG
       if (nsd_debug_level > 0 && verbosity == 0)
               verbosity = nsd_debug_level;
#endif /* NDEBUG */
       if(nsd.options->debug_mode) nsd.debug=1;
       if(!nsd.pidfile)
       {
               if(nsd.options->pidfile)
                       nsd.pidfile = nsd.options->pidfile;
               else
                       nsd.pidfile = PIDFILE;
       }
       if(strcmp(nsd.identity, hostname)==0 || strcmp(nsd.identity,IDENTITY)==0)
       {
               if(nsd.options->identity)
                       nsd.identity = nsd.options->identity;
       }
       if(nsd.options->version) {
               nsd.version = nsd.options->version;
       }
       if (nsd.options->logfile && !nsd.log_filename) {
               nsd.log_filename = nsd.options->logfile;
       }
       if(nsd.child_count == 0) {
               nsd.child_count = nsd.options->server_count;
       }

#ifdef SO_REUSEPORT
       if(nsd.options->reuseport && nsd.child_count > 1) {
               nsd.reuseport = nsd.child_count;
       }
#endif /* SO_REUSEPORT */
       if(nsd.maximum_tcp_count == 0) {
               nsd.maximum_tcp_count = nsd.options->tcp_count;
       }
       nsd.tcp_timeout = nsd.options->tcp_timeout;
       nsd.tcp_query_count = nsd.options->tcp_query_count;
       nsd.tcp_mss = nsd.options->tcp_mss;
       nsd.outgoing_tcp_mss = nsd.options->outgoing_tcp_mss;
       nsd.ipv4_edns_size = nsd.options->ipv4_edns_size;
       nsd.ipv6_edns_size = nsd.options->ipv6_edns_size;
#ifdef HAVE_SSL
       nsd.tls_ctx = NULL;
#endif

       if(udp_port == 0)
       {
               if(nsd.options->port != 0) {
                       udp_port = nsd.options->port;
                       tcp_port = nsd.options->port;
               } else {
                       udp_port = UDP_PORT;
                       tcp_port = TCP_PORT;
               }
       }
       if(nsd.options->verify_port != 0) {
               verify_port = nsd.options->verify_port;
       } else {
               verify_port = VERIFY_PORT;
       }
#ifdef BIND8_STATS
       if(nsd.st_period == 0) {
               nsd.st_period = nsd.options->statistics;
       }
#endif /* BIND8_STATS */
#ifdef HAVE_CHROOT
       if(nsd.chrootdir == 0) nsd.chrootdir = nsd.options->chroot;
#ifdef CHROOTDIR
       /* if still no chrootdir, fallback to default */
       if(nsd.chrootdir == 0) nsd.chrootdir = CHROOTDIR;
#endif /* CHROOTDIR */
#endif /* HAVE_CHROOT */
       if(nsd.username == 0) {
               if(nsd.options->username) nsd.username = nsd.options->username;
               else nsd.username = USER;
       }
       if(nsd.options->zonesdir && nsd.options->zonesdir[0]) {
               if(chdir(nsd.options->zonesdir)) {
                       error("cannot chdir to '%s': %s",
                               nsd.options->zonesdir, strerror(errno));
               }
               DEBUG(DEBUG_IPC,1, (LOG_INFO, "changed directory to %s",
                       nsd.options->zonesdir));
       }

       /* EDNS0 */
       edns_init_data(&nsd.edns_ipv4, nsd.options->ipv4_edns_size);
#if defined(INET6)
#if defined(IPV6_USE_MIN_MTU) || defined(IPV6_MTU)
       edns_init_data(&nsd.edns_ipv6, nsd.options->ipv6_edns_size);
#else /* no way to set IPV6 MTU, send no bigger than that. */
       if (nsd.options->ipv6_edns_size < IPV6_MIN_MTU)
               edns_init_data(&nsd.edns_ipv6, nsd.options->ipv6_edns_size);
       else
               edns_init_data(&nsd.edns_ipv6, IPV6_MIN_MTU);
#endif /* IPV6 MTU) */
#endif /* defined(INET6) */

       nsd.do_answer_cookie = nsd.options->answer_cookie;
       if (nsd.cookie_count > 0)
               ; /* pass */

       else if (nsd.options->cookie_secret) {
               ssize_t len = hex_pton(nsd.options->cookie_secret,
                       nsd.cookie_secrets[0].cookie_secret, NSD_COOKIE_SECRET_SIZE);
               if (len != NSD_COOKIE_SECRET_SIZE ) {
                       error("A cookie secret must be a "
                             "128 bit hex string");
               }
               nsd.cookie_count = 1;
       } else {
               size_t j;
               size_t const cookie_secret_len = NSD_COOKIE_SECRET_SIZE;
               /* Calculate a new random secret */
               srandom(getpid() ^ time(NULL));

               for( j = 0; j < NSD_COOKIE_HISTORY_SIZE; j++) {
#if defined(HAVE_SSL)
                       if (!RAND_status()
                           || !RAND_bytes(nsd.cookie_secrets[j].cookie_secret, cookie_secret_len))
#endif
                       for (i = 0; i < cookie_secret_len; i++)
                               nsd.cookie_secrets[j].cookie_secret[i] = random_generate(256);
               }
               // XXX: all we have is a random cookie, still pretend we have one
               nsd.cookie_count = 1;
       }

       if (nsd.nsid_len == 0 && nsd.options->nsid) {
               if (strlen(nsd.options->nsid) % 2 != 0) {
                       error("the NSID must be a hex string of an even length.");
               }
               nsd.nsid = xalloc(strlen(nsd.options->nsid) / 2);
               nsd.nsid_len = strlen(nsd.options->nsid) / 2;
               if (hex_pton(nsd.options->nsid, nsd.nsid, nsd.nsid_len) == -1) {
                       error("hex string cannot be parsed '%s' in NSID.", nsd.options->nsid);
               }
       }
       edns_init_nsid(&nsd.edns_ipv4, nsd.nsid_len);
#if defined(INET6)
       edns_init_nsid(&nsd.edns_ipv6, nsd.nsid_len);
#endif /* defined(INET6) */

#ifdef HAVE_CPUSET_T
       nsd.use_cpu_affinity = (nsd.options->cpu_affinity != NULL);
       if(nsd.use_cpu_affinity) {
               int ncpus;
               struct cpu_option* opt = nsd.options->cpu_affinity;

               if((ncpus = number_of_cpus()) == -1) {
                       error("cannot retrieve number of cpus: %s",
                             strerror(errno));
               }
               nsd.cpuset = cpuset_create();
               region_add_cleanup(nsd.region, free_cpuset, nsd.cpuset);
               for(; opt; opt = opt->next) {
                       assert(opt->cpu >= 0);
                       if(opt->cpu >= ncpus) {
                               error("invalid cpu %d specified in "
                                     "cpu-affinity", opt->cpu);
                       }
                       cpuset_set((cpuid_t)opt->cpu, nsd.cpuset);
               }
       }
       if(nsd.use_cpu_affinity) {
               int cpu;
               struct cpu_map_option *opt
                       = nsd.options->service_cpu_affinity;

               cpu = -1;
               for(; opt && cpu == -1; opt = opt->next) {
                       if(opt->service == -1) {
                               cpu = opt->cpu;
                               assert(cpu >= 0);
                       }
               }
               nsd.xfrd_cpuset = cpuset_create();
               region_add_cleanup(nsd.region, free_cpuset, nsd.xfrd_cpuset);
               if(cpu == -1) {
                       cpuset_or(nsd.xfrd_cpuset,
                                 nsd.cpuset);
               } else {
                       if(!cpuset_isset(cpu, nsd.cpuset)) {
                               error("cpu %d specified in xfrd-cpu-affinity "
                                     "is not specified in cpu-affinity", cpu);
                       }
                       cpuset_set((cpuid_t)cpu, nsd.xfrd_cpuset);
               }
       }
#endif /* HAVE_CPUSET_T */

       /* Number of child servers to fork.  */
       nsd.children = (struct nsd_child *) region_alloc_array(
               nsd.region, nsd.child_count, sizeof(struct nsd_child));
       for (i = 0; i < nsd.child_count; ++i) {
               nsd.children[i].kind = NSD_SERVER_BOTH;
               nsd.children[i].pid = -1;
               nsd.children[i].child_fd = -1;
               nsd.children[i].parent_fd = -1;
               nsd.children[i].handler = NULL;
               nsd.children[i].need_to_send_STATS = 0;
               nsd.children[i].need_to_send_QUIT = 0;
               nsd.children[i].need_to_exit = 0;
               nsd.children[i].has_exited = 0;
#ifdef BIND8_STATS
               nsd.children[i].query_count = 0;
#endif

#ifdef HAVE_CPUSET_T
               if(nsd.use_cpu_affinity) {
                       int cpu, server;
                       struct cpu_map_option *opt
                               = nsd.options->service_cpu_affinity;

                       cpu = -1;
                       server = i+1;
                       for(; opt && cpu == -1; opt = opt->next) {
                               if(opt->service == server) {
                                       cpu = opt->cpu;
                                       assert(cpu >= 0);
                               }
                       }
                       nsd.children[i].cpuset = cpuset_create();
                       region_add_cleanup(nsd.region,
                                          free_cpuset,
                                          nsd.children[i].cpuset);
                       if(cpu == -1) {
                               cpuset_or(nsd.children[i].cpuset,
                                         nsd.cpuset);
                       } else {
                               if(!cpuset_isset((cpuid_t)cpu, nsd.cpuset)) {
                                       error("cpu %d specified in "
                                             "server-%d-cpu-affinity is not "
                                             "specified in cpu-affinity",
                                             cpu, server);
                               }
                               cpuset_set(
                                       (cpuid_t)cpu, nsd.children[i].cpuset);
                       }
               }
#endif /* HAVE_CPUSET_T */
       }

       nsd.this_child = NULL;

       resolve_interface_names(nsd.options);
       figure_sockets(&nsd.udp, &nsd.tcp, &nsd.ifs,
               nsd.options->ip_addresses, NULL, udp_port, tcp_port, &hints);

       if(nsd.options->verify_enable) {
               figure_sockets(&nsd.verify_udp, &nsd.verify_tcp, &nsd.verify_ifs,
                       nsd.options->verify_ip_addresses, "localhost", verify_port, verify_port, &hints);
               setup_verifier_environment();
       }

       /* Parse the username into uid and gid */
       nsd.gid = getgid();
       nsd.uid = getuid();
#ifdef HAVE_GETPWNAM
       /* Parse the username into uid and gid */
       if (*nsd.username) {
               if (isdigit((unsigned char)*nsd.username)) {
                       char *t;
                       nsd.uid = strtol(nsd.username, &t, 10);
                       if (*t != 0) {
                               if (*t != '.' || !isdigit((unsigned char)*++t)) {
                                       error("-u user or -u uid or -u uid.gid");
                               }
                               nsd.gid = strtol(t, &t, 10);
                       } else {
                               /* Lookup the group id in /etc/passwd */
                               if ((pwd = getpwuid(nsd.uid)) == NULL) {
                                       error("user id %u does not exist.", (unsigned) nsd.uid);
                               } else {
                                       nsd.gid = pwd->pw_gid;
                               }
                       }
               } else {
                       /* Lookup the user id in /etc/passwd */
                       if ((pwd = getpwnam(nsd.username)) == NULL) {
                               error("user '%s' does not exist.", nsd.username);
                       } else {
                               nsd.uid = pwd->pw_uid;
                               nsd.gid = pwd->pw_gid;
                       }
               }
       }
       /* endpwent(); */
#endif /* HAVE_GETPWNAM */

#if defined(HAVE_SSL)
       key_options_tsig_add(nsd.options);
#endif

       append_trailing_slash(&nsd.options->xfrdir, nsd.options->region);
       /* Check relativity of pathnames to chroot */
       if (nsd.chrootdir && nsd.chrootdir[0]) {
               /* existing chrootdir: append trailing slash for strncmp checking */
               append_trailing_slash(&nsd.chrootdir, nsd.region);
               append_trailing_slash(&nsd.options->zonesdir, nsd.options->region);

               /* zonesdir must be absolute and within chroot,
                * all other pathnames may be relative to zonesdir */
               if (strncmp(nsd.options->zonesdir, nsd.chrootdir, strlen(nsd.chrootdir)) != 0) {
                       error("zonesdir %s has to be an absolute path that starts with the chroot path %s",
                               nsd.options->zonesdir, nsd.chrootdir);
               } else if (!file_inside_chroot(nsd.pidfile, nsd.chrootdir)) {
                       error("pidfile %s is not relative to %s: chroot not possible",
                               nsd.pidfile, nsd.chrootdir);
               } else if (!file_inside_chroot(nsd.options->xfrdfile, nsd.chrootdir)) {
                       error("xfrdfile %s is not relative to %s: chroot not possible",
                               nsd.options->xfrdfile, nsd.chrootdir);
               } else if (!file_inside_chroot(nsd.options->zonelistfile, nsd.chrootdir)) {
                       error("zonelistfile %s is not relative to %s: chroot not possible",
                               nsd.options->zonelistfile, nsd.chrootdir);
               } else if (!file_inside_chroot(nsd.options->xfrdir, nsd.chrootdir)) {
                       error("xfrdir %s is not relative to %s: chroot not possible",
                               nsd.options->xfrdir, nsd.chrootdir);
               }
       }

       /* Set up the logging */
       log_open(LOG_PID, FACILITY, nsd.log_filename);
       if(nsd.options->log_only_syslog)
               log_set_log_function(log_only_syslog);
       else if (!nsd.log_filename)
               log_set_log_function(log_syslog);
       else if (nsd.uid && nsd.gid) {
               if(chown(nsd.log_filename, nsd.uid, nsd.gid) != 0)
                       VERBOSITY(2, (LOG_WARNING, "chown %s failed: %s",
                               nsd.log_filename, strerror(errno)));
       }
       log_msg(LOG_NOTICE, "%s starting (%s)", argv0, PACKAGE_STRING);

       /* Do we have a running nsd? */
       if(nsd.pidfile && nsd.pidfile[0]) {
               if ((oldpid = readpid(nsd.pidfile)) == -1) {
                       if (errno != ENOENT) {
                               log_msg(LOG_ERR, "can't read pidfile %s: %s",
                                       nsd.pidfile, strerror(errno));
                       }
               } else {
                       if (kill(oldpid, 0) == 0 || errno == EPERM) {
                               log_msg(LOG_WARNING,
                                       "%s is already running as %u, continuing",
                                       argv0, (unsigned) oldpid);
                       } else {
                               log_msg(LOG_ERR,
                                       "...stale pid file from process %u",
                                       (unsigned) oldpid);
                       }
               }
       }

#ifdef HAVE_SETPROCTITLE
       setproctitle("main");
#endif
#ifdef HAVE_CPUSET_T
       if(nsd.use_cpu_affinity) {
               set_cpu_affinity(nsd.cpuset);
       }
#endif

       print_sockets(nsd.udp, nsd.tcp, nsd.ifs);

       /* Setup the signal handling... */
       action.sa_handler = sig_handler;
       sigfillset(&action.sa_mask);
       action.sa_flags = 0;
       sigaction(SIGTERM, &action, NULL);
       sigaction(SIGHUP, &action, NULL);
       sigaction(SIGINT, &action, NULL);
       sigaction(SIGILL, &action, NULL);
       sigaction(SIGUSR1, &action, NULL);
       sigaction(SIGALRM, &action, NULL);
       sigaction(SIGCHLD, &action, NULL);
       action.sa_handler = SIG_IGN;
       sigaction(SIGPIPE, &action, NULL);

       /* Initialize... */
       nsd.mode = NSD_RUN;
       nsd.signal_hint_child = 0;
       nsd.signal_hint_reload = 0;
       nsd.signal_hint_reload_hup = 0;
       nsd.signal_hint_quit = 0;
       nsd.signal_hint_shutdown = 0;
       nsd.signal_hint_stats = 0;
       nsd.signal_hint_statsusr = 0;
       nsd.quit_sync_done = 0;

       /* Initialize the server... */
       if (server_init(&nsd) != 0) {
               error("server initialization failed, %s could "
                       "not be started", argv0);
       }
#if defined(HAVE_SSL)
       if(nsd.options->control_enable || (nsd.options->tls_service_key && nsd.options->tls_service_key[0])) {
               perform_openssl_init();
       }
#endif /* HAVE_SSL */
       if(nsd.options->control_enable) {
               /* read ssl keys while superuser and outside chroot */
               if(!(nsd.rc = daemon_remote_create(nsd.options)))
                       error("could not perform remote control setup");
       }
#if defined(HAVE_SSL)
       if(nsd.options->tls_service_key && nsd.options->tls_service_key[0]
          && nsd.options->tls_service_pem && nsd.options->tls_service_pem[0]) {
               if(!(nsd.tls_ctx = server_tls_ctx_create(&nsd, NULL,
                       nsd.options->tls_service_ocsp)))
                       error("could not set up tls SSL_CTX");
       }
#endif /* HAVE_SSL */

       if(nsd.options->cookie_secret_file && nsd.options->cookie_secret_file[0]
          && !cookie_secret_file_read(&nsd) ) {
               log_msg(LOG_ERR, "cookie secret file corrupt or not readable");
       }

       /* Unless we're debugging, fork... */
       if (!nsd.debug) {
               int fd;

               /* Take off... */
               switch (fork()) {
               case 0:
                       /* Child */
                       break;
               case -1:
                       error("fork() failed: %s", strerror(errno));
                       break;
               default:
                       /* Parent is done */
                       server_close_all_sockets(nsd.udp, nsd.ifs);
                       server_close_all_sockets(nsd.tcp, nsd.ifs);
                       exit(0);
               }

               /* Detach ourselves... */
               if (setsid() == -1) {
                       error("setsid() failed: %s", strerror(errno));
               }

               if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
                       (void)dup2(fd, STDIN_FILENO);
                       (void)dup2(fd, STDOUT_FILENO);
                       (void)dup2(fd, STDERR_FILENO);
                       if (fd > 2)
                               (void)close(fd);
               }
       }

       /* Get our process id */
       nsd.pid = getpid();

       /* Set user context */
#ifdef HAVE_GETPWNAM
       if (*nsd.username) {
#ifdef HAVE_SETUSERCONTEXT
               /* setusercontext does initgroups, setuid, setgid, and
                * also resource limits from login config, but we
                * still call setresuid, setresgid to be sure to set all uid */
               if (setusercontext(NULL, pwd, nsd.uid,
                       LOGIN_SETALL & ~LOGIN_SETUSER & ~LOGIN_SETGROUP) != 0)
                       log_msg(LOG_WARNING, "unable to setusercontext %s: %s",
                               nsd.username, strerror(errno));
#endif /* HAVE_SETUSERCONTEXT */
       }
#endif /* HAVE_GETPWNAM */

       /* Chroot */
#ifdef HAVE_CHROOT
       if (nsd.chrootdir && nsd.chrootdir[0]) {
               int l = strlen(nsd.chrootdir)-1; /* ends in trailing slash */

               if (file_inside_chroot(nsd.log_filename, nsd.chrootdir))
                       nsd.file_rotation_ok = 1;

               /* strip chroot from pathnames if they're absolute */
               nsd.options->zonesdir += l;
               if (nsd.log_filename){
                       if (nsd.log_filename[0] == '/')
                               nsd.log_filename += l;
               }
               if (nsd.pidfile && nsd.pidfile[0] == '/')
                       nsd.pidfile += l;
               if (nsd.options->xfrdfile[0] == '/')
                       nsd.options->xfrdfile += l;
               if (nsd.options->zonelistfile[0] == '/')
                       nsd.options->zonelistfile += l;
               if (nsd.options->xfrdir[0] == '/')
                       nsd.options->xfrdir += l;

               /* strip chroot from pathnames of "include:" statements
                * on subsequent repattern commands */
               cfg_parser->chroot = nsd.chrootdir;

#ifdef HAVE_TZSET
               /* set timezone whilst not yet in chroot */
               tzset();
#endif
               if (chroot(nsd.chrootdir)) {
                       error("unable to chroot: %s", strerror(errno));
               }
               if (chdir("/")) {
                       error("unable to chdir to chroot: %s", strerror(errno));
               }
               DEBUG(DEBUG_IPC,1, (LOG_INFO, "changed root directory to %s",
                       nsd.chrootdir));
               /* chdir to zonesdir again after chroot */
               if(nsd.options->zonesdir && nsd.options->zonesdir[0]) {
                       if(chdir(nsd.options->zonesdir)) {
                               error("unable to chdir to '%s': %s",
                                       nsd.options->zonesdir, strerror(errno));
                       }
                       DEBUG(DEBUG_IPC,1, (LOG_INFO, "changed directory to %s",
                               nsd.options->zonesdir));
               }
       }
       else
#endif /* HAVE_CHROOT */
               nsd.file_rotation_ok = 1;

       DEBUG(DEBUG_IPC,1, (LOG_INFO, "file rotation on %s %sabled",
               nsd.log_filename, nsd.file_rotation_ok?"en":"dis"));

       /* Write pidfile */
       if (writepid(&nsd) == -1) {
               log_msg(LOG_ERR, "cannot overwrite the pidfile %s: %s",
                       nsd.pidfile, strerror(errno));
       }

       /* Drop the permissions */
#ifdef HAVE_GETPWNAM
       if (*nsd.username) {
#ifdef HAVE_INITGROUPS
               if(initgroups(nsd.username, nsd.gid) != 0)
                       log_msg(LOG_WARNING, "unable to initgroups %s: %s",
                               nsd.username, strerror(errno));
#endif /* HAVE_INITGROUPS */
               endpwent();

#ifdef HAVE_SETRESGID
               if(setresgid(nsd.gid,nsd.gid,nsd.gid) != 0)
#elif defined(HAVE_SETREGID) && !defined(DARWIN_BROKEN_SETREUID)
                       if(setregid(nsd.gid,nsd.gid) != 0)
#else /* use setgid */
                               if(setgid(nsd.gid) != 0)
#endif /* HAVE_SETRESGID */
                                       error("unable to set group id of %s: %s",
                                               nsd.username, strerror(errno));

#ifdef HAVE_SETRESUID
               if(setresuid(nsd.uid,nsd.uid,nsd.uid) != 0)
#elif defined(HAVE_SETREUID) && !defined(DARWIN_BROKEN_SETREUID)
                       if(setreuid(nsd.uid,nsd.uid) != 0)
#else /* use setuid */
                               if(setuid(nsd.uid) != 0)
#endif /* HAVE_SETRESUID */
                                       error("unable to set user id of %s: %s",
                                               nsd.username, strerror(errno));

               DEBUG(DEBUG_IPC,1, (LOG_INFO, "dropped user privileges, run as %s",
                       nsd.username));
       }
#endif /* HAVE_GETPWNAM */
       xfrd_make_tempdir(&nsd);
#ifdef USE_ZONE_STATS
       options_zonestatnames_create(nsd.options);
       server_zonestat_alloc(&nsd);
#endif /* USE_ZONE_STATS */
#ifdef BIND8_STATS
       server_stat_alloc(&nsd);
#endif /* BIND8_STATS */
       if(nsd.server_kind == NSD_SERVER_MAIN) {
               server_prepare_xfrd(&nsd);
               /* xfrd forks this before reading database, so it does not get
                * the memory size of the database */
               server_start_xfrd(&nsd, 0, 0);
               /* close zonelistfile in non-xfrd processes */
               zone_list_close(nsd.options);
#ifdef USE_DNSTAP
               if(nsd.options->dnstap_enable) {
                       nsd.dt_collector = dt_collector_create(&nsd);
                       dt_collector_start(nsd.dt_collector, &nsd);
               }
#endif /* USE_DNSTAP */
       }
       if (server_prepare(&nsd) != 0) {
               unlinkpid(nsd.pidfile);
               error("server preparation failed, %s could "
                       "not be started", argv0);
       }
       if(nsd.server_kind == NSD_SERVER_MAIN) {
               server_send_soa_xfrd(&nsd, 0);
       }

       /* Really take off */
       log_msg(LOG_NOTICE, "%s started (%s), pid %d",
               argv0, PACKAGE_STRING, (int) nsd.pid);

       if (nsd.server_kind == NSD_SERVER_MAIN) {
               server_main(&nsd);
       } else {
               server_child(&nsd);
       }

       /* NOTREACH */
       exit(0);
}