/* * nsd.c -- nsd(8) * * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. * * See LICENSE for the license. * */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_GRP_H #include #endif /* HAVE_GRP_H */ #ifdef HAVE_SETUSERCONTEXT #include #endif /* HAVE_SETUSERCONTEXT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nsd.h" #include "namedb.h" #include "options.h" #include "tsig.h" /* 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[@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 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" " -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, "Copyright (C) 2001-2011 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 */ } void get_ip_port_frm_str(const char* arg, const char** hostname, const char** port) { /* parse src[@port] option */ char* delim = NULL; if (arg) { delim = strchr(arg, '@'); } if (delim) { *delim = '\0'; *port = delim+1; } *hostname = arg; } /* * 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) { 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) { int fd = -1; if (file) { /* truncate pidfile */ fd = open(file, O_WRONLY | O_TRUNC, 0644); if (fd != -1) close(fd); /* unlink pidfile */ if (unlink(file) == -1) log_msg(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 = 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 #ifdef USE_ZONE_STATS static void fprintf_zone_stats(FILE* fd, zone_type* zone, time_t now) { int i; /* NSTATS */ fprintf(fd, "NSTATS %s %lu", dname_to_string(domain_dname(zone->apex),0), (unsigned long) now); for (i = 0; i <= 255; i++) { if (zone->st.qtype[i] != 0) { fprintf(fd, " %s=%lu", rrtype_to_string(i), zone->st.qtype[i]); } } fprintf(fd, "\n"); /* XSTATS */ fprintf(fd, "XSTATS %s %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\n", dname_to_string(domain_dname(zone->apex),0), (unsigned long) now, zone->st.dropped, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, (unsigned long)0, zone->st.raxfr, (unsigned long)0, (unsigned long)0, (unsigned long)0, zone->st.qudp + zone->st.qudp6 - zone->st.dropped, (unsigned long)0, (unsigned long)0, zone->st.txerr, zone->st.opcode[OPCODE_QUERY], zone->st.opcode[OPCODE_IQUERY], zone->st.wrongzone, (unsigned long)0, zone->st.ctcp + zone->st.ctcp6, (unsigned long)0, zone->st.rcode[RCODE_SERVFAIL], zone->st.rcode[RCODE_FORMAT], zone->st.nona, zone->st.rcode[RCODE_NXDOMAIN], (unsigned long)0, (unsigned long)0, (unsigned long)0, zone->st.opcode[OPCODE_UPDATE]); } #endif void bind8_stats (struct nsd *nsd) { #ifdef USE_ZONE_STATS FILE* fd; zone_type* zone; #endif 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]); } #ifdef USE_ZONE_STATS /* ZSTATS */ log_msg(LOG_INFO, "ZSTATS %s", nsd->zonestatsfile); if ((fd = fopen(nsd->zonestatsfile, "a")) == NULL ) { log_msg(LOG_ERR, "cannot open zone statsfile %s: %s", nsd->zonestatsfile, strerror(errno)); return; } /* Write stats per zone */ zone = nsd->db->zones; while (zone) { fprintf_zone_stats(fd, zone, now); zone = zone->next; } fclose(fd); #endif } #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 */ /* static so it can get very big without overflowing the stack */ static struct addrinfo hints[MAX_INTERFACES]; static 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; #ifdef USE_ZONE_STATS nsd.zonestatsfile = 0; #endif 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 /* 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': 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); } 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 */ #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; } #ifdef USE_ZONE_STATS if (nsd.zonestatsfile == 0) { nsd.zonestatsfile = nsd.options->zonestatsfile; } #endif /* USE_ZONE_STATS */ #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) */ 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) */ /* 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; const char* node = NULL; const char* service = NULL; /* We don't perform name-lookups */ if (nodes[i] != NULL) hints[i].ai_flags |= AI_NUMERICHOST; get_ip_port_frm_str(nodes[i], &node, &service); hints[i].ai_socktype = SOCK_DGRAM; if ((r=getaddrinfo(node, (service?service: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(node, (service?service: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 */ if(!tsig_init(nsd.region)) error("init tsig failed"); #if defined(HAVE_SSL) key_options_tsig_add(nsd.options); #endif /* 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)); strlcat(chroot_slash, "/", sizeof(char)*(l+2)); nsd.chrootdir = chroot_slash; ++l; } if (strncmp(nsd.chrootdir, nsd.pidfile, l) != 0) { error("%s is not relative to %s: chroot not possible", nsd.pidfile, nsd.chrootdir); } else if (strncmp(nsd.chrootdir, nsd.dbfile, l) != 0) { error("%s is not relative to %s: chroot not possible", nsd.dbfile, nsd.chrootdir); } else if (strncmp(nsd.chrootdir, nsd.options->xfrdfile, l) != 0) { error("%s is not relative to %s: chroot not possible", nsd.options->xfrdfile, nsd.chrootdir); } else if (strncmp(nsd.chrootdir, nsd.options->difffile, l) != 0) { error("%s is not relative to %s: chroot not possible", nsd.options->difffile, nsd.chrootdir); } } /* 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) { if(chown(nsd.log_filename, nsd.uid, nsd.gid) != 0) VERBOSITY(2, (LOG_WARNING, "chown %s failed: %s", nsd.log_filename, strerror(errno))); } /* 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); } /* 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 && 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; #ifdef HAVE_TZSET /* set timezone whilst not yet in chroot */ tzset(); #endif if (chroot(nsd.chrootdir)) { log_msg(LOG_ERR, "unable to chroot: %s", strerror(errno)); exit(1); } if (chdir("/")) { log_msg(LOG_ERR, "unable to chdir 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_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 */ 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); }