/*
* 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"
#ifdef USE_DNSTAP
#include "dnstap/dnstap_collector.h"
#endif
/* 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 */
" -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, "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;
/* Current time... */
time_t now;
if(!nsd->st.period)
return;
time(&now);
/* NSTATS */
t = msg = buf + snprintf(buf, MAXSYSLOGMSGLEN, "NSTATS %lld %lu",
(long 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.rixfr || (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 %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) 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, nsd->st.rixfr, (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 */
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.dbfile = 0;
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':
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 (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");
/* 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.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->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.dbfile, nsd.chrootdir)) {
error("database %s is not relative to %s: chroot not possible",
nsd.dbfile, 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();
}
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(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.dbfile[0] == '/')
nsd.dbfile += 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 */
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);
}