summaryrefslogtreecommitdiffstats
path: root/usr.sbin/nsd/nsd.c
diff options
context:
space:
mode:
authorjakob <jakob@openbsd.org>2010-01-15 19:24:49 +0000
committerjakob <jakob@openbsd.org>2010-01-15 19:24:49 +0000
commit62ac0c33cd8a350e5e6563e0c87195af8a07028d (patch)
tree0fecfab1cce45651e8931998fbd441bd212b8284 /usr.sbin/nsd/nsd.c
parentReplace pool_get() + bzero() with pool_get(..., PR_ZERO). (diff)
downloadwireguard-openbsd-62ac0c33cd8a350e5e6563e0c87195af8a07028d.tar.xz
wireguard-openbsd-62ac0c33cd8a350e5e6563e0c87195af8a07028d.zip
NSD v3.2.4
Diffstat (limited to 'usr.sbin/nsd/nsd.c')
-rw-r--r--usr.sbin/nsd/nsd.c1043
1 files changed, 1043 insertions, 0 deletions
diff --git a/usr.sbin/nsd/nsd.c b/usr.sbin/nsd/nsd.c
new file mode 100644
index 00000000000..708d897fcfd
--- /dev/null
+++ b/usr.sbin/nsd/nsd.c
@@ -0,0 +1,1043 @@
+/*
+ * 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
+#include <login_cap.h>
+#endif /* HAVE_SETUSERCONTEXT */
+
+#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>
+
+#include "nsd.h"
+#include "options.h"
+#ifdef TSIG
+#include "tsig.h"
+#endif /* TSIG */
+
+/* The server handler... */
+static struct nsd nsd;
+static char hostname[MAXHOSTNAMELEN];
+
+static void error(const char *format, ...) ATTR_FORMAT(printf, 1, 2);
+
+/*
+ * 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 Listen to the specified incoming IP address (may be\n"
+ " specified multiple times).\n"
+ " -c configfile Read specified configfile instead of %s.\n"
+ " -d Enable debug mode (do not fork as a daemon process).\n"
+#ifndef NDEBUG
+ " -F facilities Specify the debug facilities.\n"
+#endif /* NDEBUG */
+ " -f database Specify the database to load.\n"
+ " -h Print this help information.\n"
+ , CONFIGFILE);
+ fprintf(stderr,
+ " -i identity Specify the identity when queried for id.server CHAOS TXT.\n"
+#ifdef NSID
+ " -I nsid Specify the NSID. This must be a hex string.\n"
+#endif /* NSID */
+#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,
+ "Copyright (C) 2001-2006 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);
+}
+
+/*
+ * Something went wrong, give error messages and exit.
+ *
+ */
+static void
+error(const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ log_vmsg(LOG_ERR, format, args);
+ va_end(args);
+ exit(1);
+}
+
+static int
+file_inside_chroot(const char* fname, const char* chr)
+{
+#ifdef NDEBUG
+ assert(chr);
+#endif /* NDEBUG */
+ /* filename and chroot the same? */
+ if (fname && fname[0] && chr[0] && !strncmp(fname, chr, strlen(chr)))
+ return 2; /* strip chroot, file rotation ok */
+ else if (fname && fname[0] != '/')
+ return 1; /* don't strip, file rotation ok */
+ return 0; /* don't strip, don't try file rotation */
+}
+
+
+/*
+ * 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 = 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)
+{
+ FILE * fd;
+ char pidbuf[32];
+
+ snprintf(pidbuf, sizeof(pidbuf), "%lu\n", (unsigned long) nsd->pid);
+
+ if ((fd = fopen(nsd->pidfile, "w")) == NULL ) {
+ log_msg(LOG_ERR, "cannot open pidfile %s: %s",
+ nsd->pidfile, strerror(errno));
+ return -1;
+ }
+
+ if (!write_data(fd, pidbuf, strlen(pidbuf))) {
+ log_msg(LOG_ERR, "cannot write pidfile %s: %s",
+ nsd->pidfile, strerror(errno));
+ fclose(fd);
+ return -1;
+ }
+ fclose(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)
+{
+ if (file && unlink(file) == -1)
+ log_msg(LOG_ERR, "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 = 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:
+ /* Silent shutdown... */
+ nsd.signal_hint_quit = 1;
+ break;
+ 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;
+
+ /* Current time... */
+ time_t now;
+ time(&now);
+
+ /* NSTATS */
+ t = msg = buf + snprintf(buf, MAXSYSLOGMSGLEN, "NSTATS %lu %lu",
+ (unsigned long) now, (unsigned long) nsd->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 (nsd->st.qtype[i] != 0) {
+ t += snprintf(t, len, " %s=%lu", rrtype_to_string(i), nsd->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
+ || nsd->st.dropped || nsd->st.raxfr || (nsd->st.qudp + nsd->st.qudp6 - nsd->st.dropped)
+ || nsd->st.txerr || nsd->st.opcode[OPCODE_QUERY] || nsd->st.opcode[OPCODE_IQUERY]
+ || nsd->st.wrongzone || nsd->st.ctcp + nsd->st.ctcp6 || nsd->st.rcode[RCODE_SERVFAIL]
+ || nsd->st.rcode[RCODE_FORMAT] || nsd->st.nona || nsd->st.rcode[RCODE_NXDOMAIN]
+ || nsd->st.opcode[OPCODE_UPDATE]) {
+
+ log_msg(LOG_INFO, "XSTATS %lu %lu"
+ " RR=%lu RNXD=%lu RFwdR=%lu RDupR=%lu RFail=%lu RFErr=%lu RErr=%lu RAXFR=%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",
+ (unsigned long) now, (unsigned long) nsd->st.boot,
+ nsd->st.dropped, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0,
+ (unsigned long)0, (unsigned long)0, nsd->st.raxfr, (unsigned long)0, (unsigned long)0,
+ (unsigned long)0, nsd->st.qudp + nsd->st.qudp6 - nsd->st.dropped, (unsigned long)0,
+ (unsigned long)0, nsd->st.txerr,
+ nsd->st.opcode[OPCODE_QUERY], nsd->st.opcode[OPCODE_IQUERY], nsd->st.wrongzone,
+ (unsigned long)0, nsd->st.ctcp + nsd->st.ctcp6,
+ (unsigned long)0, nsd->st.rcode[RCODE_SERVFAIL], nsd->st.rcode[RCODE_FORMAT],
+ nsd->st.nona, nsd->st.rcode[RCODE_NXDOMAIN],
+ (unsigned long)0, (unsigned long)0, (unsigned long)0, nsd->st.opcode[OPCODE_UPDATE]);
+ }
+
+}
+#endif /* BIND8_STATS */
+
+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;
+ FILE* dbfd;
+#ifdef HAVE_GETPWNAM
+ struct passwd *pwd;
+#endif /* HAVE_GETPWNAM */
+
+ /* For initialising the address info structures */
+ struct addrinfo hints[MAX_INTERFACES];
+ const char *nodes[MAX_INTERFACES];
+ const char *udp_port = 0;
+ const char *tcp_port = 0;
+
+ const char *configfile = CONFIGFILE;
+
+ char* argv0 = (argv0 = strrchr(argv[0], '/')) ? argv0 + 1 : argv[0];
+
+ log_init("nsd");
+
+ /* Initialize the server handler... */
+ memset(&nsd, 0, sizeof(struct nsd));
+ nsd.region = region_create(xalloc, free);
+ nsd.dbfile = 0;
+ nsd.pidfile = 0;
+ nsd.server_kind = NSD_SERVER_MAIN;
+
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ memset(&hints[i], 0, sizeof(hints[i]));
+ hints[i].ai_family = DEFAULT_AI_FAMILY;
+ hints[i].ai_flags = AI_PASSIVE;
+ nodes[i] = NULL;
+ }
+
+ nsd.identity = 0;
+ nsd.version = VERSION;
+ nsd.username = 0;
+ nsd.chrootdir = 0;
+ nsd.nsid = NULL;
+ nsd.nsid_len = 0;
+
+ nsd.child_count = 0;
+ nsd.maximum_tcp_count = 0;
+ nsd.current_tcp_count = 0;
+ nsd.grab_ip6_optional = 0;
+ nsd.file_rotation_ok = 0;
+
+ /* 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;
+ }
+
+
+ /* 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':
+ for (i = 0; i < MAX_INTERFACES; ++i) {
+ hints[i].ai_family = AF_INET;
+ }
+ break;
+ case '6':
+#ifdef INET6
+ for (i = 0; i < MAX_INTERFACES; ++i) {
+ hints[i].ai_family = AF_INET6;
+ }
+#else /* !INET6 */
+ error("IPv6 support not enabled.");
+#endif /* INET6 */
+ break;
+ case 'a':
+ if (nsd.ifs < MAX_INTERFACES) {
+ nodes[nsd.ifs] = optarg;
+ ++nsd.ifs;
+ } else {
+ error("too many interfaces ('-a') specified.");
+ }
+ break;
+ case 'c':
+ configfile = optarg;
+ break;
+ case 'd':
+ nsd.debug = 1;
+ break;
+ case 'f':
+ nsd.dbfile = optarg;
+ break;
+ case 'h':
+ usage();
+ exit(0);
+ case 'i':
+ nsd.identity = optarg;
+ break;
+ case 'I':
+#ifdef NSID
+ if (nsd.nsid_len != 0) {
+ /* can only be given once */
+ break;
+ }
+ 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);
+ }
+#endif /* NSID */
+ break;
+ case 'l':
+ nsd.log_filename = optarg;
+ break;
+ case 'N':
+ i = atoi(optarg);
+ if (i <= 0) {
+ error("number of child servers must be greather 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 */
+#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));
+ }
+
+ /* Read options */
+ nsd.options = nsd_options_create(region_create(xalloc, free));
+ if(!parse_options_file(nsd.options, configfile)) {
+ error("could not read config: %s\n", configfile);
+ }
+ if(nsd.options->ip4_only) {
+ for (i = 0; i < MAX_INTERFACES; ++i) {
+ hints[i].ai_family = AF_INET;
+ }
+ }
+#ifdef INET6
+ if(nsd.options->ip6_only) {
+ for (i = 0; i < MAX_INTERFACES; ++i) {
+ hints[i].ai_family = AF_INET6;
+ }
+ }
+#endif /* INET6 */
+ if(nsd.options->ip_addresses)
+ {
+ ip_address_option_t* ip = nsd.options->ip_addresses;
+ while(ip) {
+ if (nsd.ifs < MAX_INTERFACES) {
+ nodes[nsd.ifs] = ip->address;
+ ++nsd.ifs;
+ } else {
+ error("too many interfaces ('-a' + "
+ "'ip-address:') specified.");
+ break;
+ }
+ ip = ip->next;
+ }
+ }
+ 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.dbfile)
+ {
+ if(nsd.options->database)
+ nsd.dbfile = nsd.options->database;
+ else
+ nsd.dbfile = DBFILE;
+ }
+ 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->logfile && !nsd.log_filename) {
+ nsd.log_filename = nsd.options->logfile;
+ }
+ if(nsd.child_count == 0) {
+ nsd.child_count = nsd.options->server_count;
+ }
+ 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.ipv4_edns_size = nsd.options->ipv4_edns_size;
+ nsd.ipv6_edns_size = nsd.options->ipv6_edns_size;
+
+ 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;
+ }
+ }
+#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;
+#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) */
+
+
+
+#ifdef 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) */
+#endif /* NSID */
+ /* Number of child servers to fork. */
+ nsd.children = (struct nsd_child *) region_alloc(
+ 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;
+ nsd.children[i].dirty_zones = stack_create(nsd.region,
+ nsd_options_num_zones(nsd.options));
+ }
+
+ nsd.this_child = NULL;
+
+ /* We need at least one active interface */
+ if (nsd.ifs == 0) {
+ nsd.ifs = 1;
+
+ /*
+ * 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 INET6
+ if (hints[0].ai_family == AF_UNSPEC) {
+#ifdef IPV6_V6ONLY
+ hints[0].ai_family = AF_INET6;
+ hints[1].ai_family = AF_INET;
+ nsd.ifs = 2;
+ nsd.grab_ip6_optional = 1;
+#else /* !IPV6_V6ONLY */
+ hints[0].ai_family = AF_INET6;
+#endif /* IPV6_V6ONLY */
+ }
+#endif /* INET6 */
+ }
+
+ /* Set up the address info structures with real interface/port data */
+ for (i = 0; i < nsd.ifs; ++i) {
+ int r;
+
+ /* We don't perform name-lookups */
+ if (nodes[i] != NULL)
+ hints[i].ai_flags |= AI_NUMERICHOST;
+
+ hints[i].ai_socktype = SOCK_DGRAM;
+ if ((r=getaddrinfo(nodes[i], udp_port, &hints[i], &nsd.udp[i].addr)) != 0) {
+#ifdef INET6
+ if(nsd.grab_ip6_optional && hints[0].ai_family == AF_INET6) {
+ log_msg(LOG_WARNING, "No IPv6, fallback to IPv4. getaddrinfo: %s",
+ r==EAI_SYSTEM?strerror(errno):gai_strerror(r));
+ continue;
+ }
+#endif
+ error("cannot parse address '%s': getaddrinfo: %s %s",
+ nodes[i]?nodes[i]:"(null)",
+ gai_strerror(r),
+ r==EAI_SYSTEM?strerror(errno):"");
+ }
+
+ hints[i].ai_socktype = SOCK_STREAM;
+ if ((r=getaddrinfo(nodes[i], tcp_port, &hints[i], &nsd.tcp[i].addr)) != 0) {
+ error("cannot parse address '%s': getaddrinfo: %s %s",
+ nodes[i]?nodes[i]:"(null)",
+ gai_strerror(r),
+ r==EAI_SYSTEM?strerror(errno):"");
+ }
+ }
+
+ /* 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((int)*nsd.username)) {
+ char *t;
+ nsd.uid = strtol(nsd.username, &t, 10);
+ if (*t != 0) {
+ if (*t != '.' || !isdigit((int)*++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 */
+
+#ifdef TSIG
+ if(!tsig_init(nsd.region))
+ error("init tsig failed");
+ key_options_tsig_add(nsd.options);
+#endif /* TSIG */
+
+ /* Set up the logging */
+ log_open(LOG_PID, FACILITY, nsd.log_filename);
+ if (!nsd.log_filename)
+ log_set_log_function(log_syslog);
+ else if (nsd.uid && nsd.gid)
+ (void) chown(nsd.log_filename, nsd.uid, nsd.gid);
+
+ /* Relativize the pathnames for chroot... */
+ if (nsd.chrootdir) {
+ int l = strlen(nsd.chrootdir);
+
+ /* existing chrootdir: append trailing slash for strncmp checking */
+ if (l>0 && strncmp(nsd.chrootdir + (l-1), "/", 1) != 0) {
+ char *chroot_slash = region_alloc(nsd.region, sizeof(char)*(l+2));
+ memcpy(chroot_slash, nsd.chrootdir, sizeof(char)*(l+1));
+ strncat(chroot_slash, "/", 1);
+ nsd.chrootdir = chroot_slash;
+ ++l;
+ }
+
+ if (strncmp(nsd.chrootdir, nsd.pidfile, l) != 0) {
+ log_msg(LOG_ERR, "%s is not relative to %s: will not chroot",
+ nsd.pidfile, nsd.chrootdir);
+ nsd.chrootdir = NULL;
+ } else if (strncmp(nsd.chrootdir, nsd.dbfile, l) != 0) {
+ log_msg(LOG_ERR, "%s is not relative to %s: will not chroot",
+ nsd.dbfile, nsd.chrootdir);
+ nsd.chrootdir = NULL;
+ } else if (strncmp(nsd.chrootdir, nsd.options->xfrdfile, l) != 0) {
+ log_msg(LOG_ERR, "%s is not relative to %s: will not chroot",
+ nsd.options->xfrdfile, nsd.chrootdir);
+ nsd.chrootdir = NULL;
+ } else if (strncmp(nsd.chrootdir, nsd.options->difffile, l) != 0) {
+ log_msg(LOG_ERR, "%s is not relative to %s: will not chroot",
+ nsd.options->difffile, nsd.chrootdir);
+ nsd.chrootdir = NULL;
+ }
+ }
+
+ /* Do we have a running nsd? */
+ 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);
+ }
+ }
+
+ /* Unless we're debugging, fork... */
+ if (!nsd.debug) {
+ int fd;
+
+ /* Take off... */
+ switch ((nsd.pid = fork())) {
+ case 0:
+ /* Child */
+ break;
+ case -1:
+ log_msg(LOG_ERR, "fork() failed: %s", strerror(errno));
+ exit(1);
+ default:
+ /* Parent is done */
+ exit(0);
+ }
+
+ /* Detach ourselves... */
+ if (setsid() == -1) {
+ log_msg(LOG_ERR, "setsid() failed: %s", strerror(errno));
+ exit(1);
+ }
+
+ 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);
+ }
+ }
+
+ /* 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);
+
+ /* Get our process id */
+ nsd.pid = getpid();
+
+ /* Initialize... */
+ nsd.mode = NSD_RUN;
+ nsd.signal_hint_child = 0;
+ nsd.signal_hint_reload = 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) {
+ log_msg(LOG_ERR, "server initialization failed, %s could "
+ "not be started", argv0);
+ exit(1);
+ }
+
+#ifdef HAVE_CHROOT
+ /* Chroot */
+ if (nsd.chrootdir && strlen(nsd.chrootdir)) {
+ int l = strlen(nsd.chrootdir);
+ int ret = 0;
+
+ while (l>0 && nsd.chrootdir[l-1] == '/')
+ --l;
+
+ /* filename after chroot */
+ ret = file_inside_chroot(nsd.log_filename, nsd.chrootdir);
+ if (ret) {
+ nsd.file_rotation_ok = 1;
+ if (ret == 2) /* also strip chroot */
+ nsd.log_filename += l;
+ }
+ nsd.dbfile += l;
+ nsd.pidfile += l;
+ nsd.options->xfrdfile += l;
+ nsd.options->difffile += l;
+
+ if (chroot(nsd.chrootdir)) {
+ log_msg(LOG_ERR, "unable to chroot: %s", strerror(errno));
+ exit(1);
+ }
+ DEBUG(DEBUG_IPC,1, (LOG_INFO, "changed root directory to %s",
+ nsd.chrootdir));
+ }
+ 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"));
+
+ /* Check if nsd.db exists */
+ if ((dbfd = fopen(nsd.dbfile, "r")) == NULL) {
+ log_msg(LOG_ERR, "unable to open %s for reading: %s", nsd.dbfile, strerror(errno));
+ exit(1);
+ }
+ fclose(dbfd);
+
+ /* 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_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) != 0)
+ log_msg(LOG_WARNING, "unable to setusercontext %s: %s",
+ nsd.username, strerror(errno));
+#else /* !HAVE_SETUSERCONTEXT */
+ #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 */
+#endif /* HAVE_SETUSERCONTEXT */
+ 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 */
+
+ if (server_prepare(&nsd) != 0) {
+ log_msg(LOG_ERR, "server preparation failed, %s could "
+ "not be started", argv0);
+ unlinkpid(nsd.pidfile);
+ exit(1);
+ }
+
+ /* 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);
+}