/* 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);
}
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);
}
}
}
#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 };
/* 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;
}
}
/*
* 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;
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;
}
}
/* 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; */
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) */
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);
}
}
}
/* 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);