/* rbldnsd: main program */ #define _LARGEFILE64_SOURCE /* to define O_LARGEFILE if supported */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* some systems can't include time.h and sys/time.h */ #include #include #include "rbldnsd.h" #ifndef NO_SELECT_H # include #endif #ifndef NO_POLL # include #endif #ifndef NO_MEMINFO # include #endif #ifndef NO_TIMES # include #endif #ifndef NO_STDINT_H /* if system have stdint.h, assume it have inttypes.h too */ # include #endif #ifndef NO_STATS # ifndef NO_IOVEC # include # define STATS_IPC_IOVEC 1 # endif #endif #ifndef NO_DSO # include #endif #ifndef NI_MAXHOST # define NI_MAXHOST 1025 #endif #ifndef NI_MAXSERV # define NI_MAXSERV 32 #endif #ifndef O_LARGEFILE # define O_LARGEFILE 0 #endif const char *version = VERSION; const char *show_version = "rbldnsd " VERSION; /* version to show in version.bind CH TXT reply */ char *progname; /* limited to 32 chars */ int logto; void error(int errnum, const char *fmt, ...) { char buf[256]; int l, pl; va_list ap; l = pl = ssprintf(buf, sizeof(buf), "%.30s: ", progname); va_start(ap, fmt); l += vssprintf(buf + l, sizeof(buf) - l, fmt, ap); if (errnum) l += ssprintf(buf + l, sizeof(buf) - l, ": %.50s", strerror(errnum)); if (logto & LOGTO_SYSLOG) { fmt = buf + pl; syslog(LOG_ERR, strchr(fmt, '%') ? "%s" : fmt, fmt); } buf[l++] = '\n'; write(2, buf, l); _exit(1); } static unsigned recheck = 60; /* interval between checks for reload */ static int initialized; /* 1 when initialized */ static char *logfile; /* log file name */ #ifndef NO_STATS static char *statsfile; /* statistics file */ static int stats_relative; /* dump relative, not absolute, stats */ #endif int accept_in_cidr; /* accept 127.0.0.1/8-"style" CIDRs */ int nouncompress; /* disable on-the-fly decompression */ unsigned def_ttl = 35*60; /* default record TTL 35m */ unsigned min_ttl, max_ttl; /* TTL constraints */ const char def_rr[5] = "\177\0\0\2\0"; /* default A RR */ #define MAXSOCK 20 /* maximum # of supported sockets */ static int sock[MAXSOCK]; /* array of active sockets */ static int numsock; /* number of active sockets in sock[] */ static FILE *flog; /* log file */ static int flushlog; /* flush log after each line */ static struct zone *zonelist; /* list of zones we're authoritative for */ static int numzones; /* number of zones in zonelist */ int lazy; /* don't return AUTH section by default */ static int fork_on_reload; /* >0 - perform fork on reloads, <0 - this is a child of reloading parent */ #if STATS_IPC_IOVEC static struct iovec *stats_iov; #endif #ifndef NO_DSO int (*hook_reload_check)(), (*hook_reload)(); int (*hook_query_access)(), (*hook_query_result)(); #endif /* a list of zonetypes. */ const struct dstype *ds_types[] = { dstype(ip4set), dstype(ip4tset), dstype(ip4trie), dstype(ip6tset), dstype(ip6trie), dstype(dnset), #ifdef DNHASH dstype(dnhash), #endif dstype(combined), dstype(generic), dstype(acl), NULL }; static int do_reload(int do_fork); static int satoi(const char *s) { int n = 0; if (*s < '0' || *s > '9') return -1; do n = n * 10 + (*s++ - '0'); while (*s >= '0' && *s <= '9'); return *s ? -1 : n; } static void NORETURN usage(int exitcode) { const struct dstype **dstp; printf( "%s: rbl dns daemon version %s\n" "Usage is: %s options zonespec...\n" "where options are:\n" " -u user[:group] - run as this user:group (rbldns)\n" " -r rootdir - chroot to this directory\n" " -w workdir - working directory with zone files\n" " -b address[/port] - bind to (listen on) this address (required)\n" #ifndef NO_IPv6 " -4 - use IPv4 socket type\n" " -6 - use IPv6 socket type\n" #endif " -t ttl - default TTL value to set in answers (35m)\n" " -v - hide version information in replies to version.bind CH TXT\n" " (second -v makes rbldnsd to refuse such requests completely)\n" " -e - enable CIDR ranges where prefix is not on the range boundary\n" " (by default ranges such 127.0.0.1/8 will be rejected)\n" " -c check - time interval to check for data file updates (1m)\n" " -p pidfile - write pid to specified file\n" " -n - do not become a daemon\n" " -f - fork a child process while reloading zones, to process requests\n" " during reload (may double memory requiriments)\n" " -q - quickstart, load zones after backgrounding\n" " -l [+]logfile - log queries and answers to this file (+ for unbuffered)\n" #ifndef NO_STATS " -s [+]statsfile - write a line with short statistics summary into this\n" " file every `check' (-c) secounds, for rrdtool-like applications\n" " (+ to log relative, not absolute, statistics counters)\n" #endif " -a - omit AUTH section from regular replies, do not return list of\n" " nameservers, but only return NS info when explicitly asked.\n" " This is an equivalent of bind9 \"minimal-answers\" setting.\n" " In future versions this mode will be the default.\n" " -A - put AUTH section in every reply.\n" #ifndef NO_ZLIB " -C - disable on-the-fly decompression of dataset files\n" #endif #ifndef NO_DZO " -x extension - load given extension module (.so file)\n" " -X extarg - pass extarg to extension init routine\n" #endif " -d - dump all zones in BIND format to standard output and exit\n" "each zone specified using `name:type:file,file...'\n" "syntax, repeated names constitute the same zone.\n" "Available dataset types:\n" , progname, version, progname); for(dstp = ds_types; *dstp; ++dstp) printf(" %s - %s\n", (*dstp)->dst_name, (*dstp)->dst_descr); exit(exitcode); } static volatile int signalled; #define SIGNALLED_RELOAD 0x01 #define SIGNALLED_RELOG 0x02 #define SIGNALLED_LSTATS 0x04 #define SIGNALLED_SSTATS 0x08 #define SIGNALLED_ZSTATS 0x10 #define SIGNALLED_TERM 0x20 #ifdef NO_IPv6 static void newsocket(struct sockaddr_in *sin) { int fd; const char *host = ip4atos(ntohl(sin->sin_addr.s_addr)); if (numsock >= MAXSOCK) error(0, "too many listening sockets (%d max)", MAXSOCK); fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) error(errno, "unable to create socket"); if (bind(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0) error(errno, "unable to bind to %s/%d", host, ntohs(sin->sin_port)); dslog(LOG_INFO, 0, "listening on %s/%d", host, ntohs(sin->sin_port)); sock[numsock++] = fd; } #else static int newsocket(struct addrinfo *ai) { int fd; char host[NI_MAXHOST], serv[NI_MAXSERV]; if (numsock >= MAXSOCK) error(0, "too many listening sockets (%d max)", MAXSOCK); fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) { if (errno == EAFNOSUPPORT) return 0; error(errno, "unable to create socket"); } getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0) error(errno, "unable to bind to %s/%s", host, serv); dslog(LOG_INFO, 0, "listening on %s/%s", host, serv); sock[numsock++] = fd; return 1; } #endif static void initsockets(const char *bindaddr[MAXSOCK], int nba, int UNUSED family) { int i, x; char *host, *serv; const char *ba; #ifdef NO_IPv6 struct sockaddr_in sin; ip4addr_t sinaddr; int port; struct servent *se; struct hostent *he; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; if (!(se = getservbyname("domain", "udp"))) port = htons(DNS_PORT); else port = se->s_port; #else struct addrinfo hints, *aires, *ai; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_PASSIVE; #endif for (i = 0; i < nba; ++i) { ba = bindaddr[i]; host = estrdup(ba); serv = strchr(host, '/'); if (serv) { *serv++ = '\0'; if (!*host) error(0, "missing host part in bind address `%.60s'", ba); } #ifdef NO_IPv6 if (!serv || !*serv) sin.sin_port = port; else if ((x = satoi(serv)) > 0 && x <= 0xffff) sin.sin_port = htons(x); else if (!(se = getservbyname(serv, "udp"))) error(0, "unknown service in `%.60s'", ba); else sin.sin_port = se->s_port; if (ip4addr(host, &sinaddr, NULL) > 0) { sin.sin_addr.s_addr = htonl(sinaddr); newsocket(&sin); } else if (!(he = gethostbyname(host)) || he->h_addrtype != AF_INET || he->h_length != 4 || !he->h_addr_list[0]) error(0, "unknown host in `%.60s'", ba); else { for(x = 0; he->h_addr_list[x]; ++x) { memcpy(&sin.sin_addr, he->h_addr_list[x], 4); newsocket(&sin); } } #else if (!serv || !*serv) serv = "domain"; x = getaddrinfo(host, serv, &hints, &aires); if (x != 0) error(0, "%.60s: %s", ba, gai_strerror(x)); for(ai = aires, x = 0; ai; ai = ai->ai_next) if (newsocket(ai)) ++x; if (!x) error(0, "%.60s: no available protocols", ba); freeaddrinfo(aires); #endif free(host); } endservent(); endhostent(); for (i = 0; i < numsock; ++i) { x = 65536; do if (setsockopt(sock[i], SOL_SOCKET, SO_RCVBUF, (void*)&x, sizeof x) == 0) break; while ((x -= (x >> 5)) >= 1024); } } static void init(int argc, char **argv) { int c; char *p; const char *user = NULL; const char *rootdir = NULL, *workdir = NULL, *pidfile = NULL; const char *bindaddr[MAXSOCK]; int nba = 0; uid_t uid = 0; gid_t gid = 0; int nodaemon = 0, quickstart = 0, dump = 0, nover = 0, forkon = 0; int family = AF_UNSPEC; int cfd = -1; const struct zone *z; #ifndef NO_DSO char *ext = NULL, *extarg = NULL; int (*extinit)(const char *arg, struct zone *zonelist) = NULL; #endif if ((progname = strrchr(argv[0], '/')) != NULL) argv[0] = ++progname; else progname = argv[0]; if (argc <= 1) usage(1); while((c = getopt(argc, argv, "u:r:b:w:t:c:p:nel:qs:h46dvaAfCx:X:")) != EOF) switch(c) { case 'u': user = optarg; break; case 'r': rootdir = optarg; break; case 'b': if (nba >= MAXSOCK) error(0, "too many addresses to listen on (%d max)", MAXSOCK); bindaddr[nba++] = optarg; break; #ifndef NO_IPv6 case '4': family = AF_INET; break; case '6': family = AF_INET6; break; #else case '4': break; case '6': error(0, "IPv6 support isn't compiled in"); #endif case 'w': workdir = optarg; break; case 'p': pidfile = optarg; break; case 't': p = optarg; if (*p == ':') ++p; else { if (!(p = parse_time(p, &def_ttl)) || !def_ttl || (*p && *p++ != ':')) error(0, "invalid ttl (-t) value `%.50s'", optarg); } if (*p == ':') ++p; else if (*p) { if (!(p = parse_time(p, &min_ttl)) || (*p && *p++ != ':')) error(0, "invalid minttl (-t) value `%.50s'", optarg); } if (*p == ':') ++p; else if (*p) { if (!(p = parse_time(p, &max_ttl)) || (*p && *p++ != ':')) error(0, "invalid maxttl (-t) value `%.50s'", optarg); } if (*p) error(0, "invalid value for -t (ttl) option: `%.50s'", optarg); if ((min_ttl && max_ttl && min_ttl > max_ttl) || (min_ttl && def_ttl < min_ttl) || (max_ttl && def_ttl > max_ttl)) error(0, "inconsistent def:min:max ttl: %u:%u:%u", def_ttl, min_ttl, max_ttl); break; case 'c': if (!(p = parse_time(optarg, &recheck)) || *p) error(0, "invalid check interval (-c) value `%.50s'", optarg); break; case 'n': nodaemon = 1; break; case 'e': accept_in_cidr = 1; break; case 'l': logfile = optarg; if (*logfile != '+') flushlog = 0; else ++logfile, flushlog = 1; if (!*logfile) logfile = NULL, flushlog = 0; else if (logfile[0] == '-' && logfile[1] == '\0') logfile = NULL, flog = stdout; break; break; case 's': #ifdef NO_STATS fprintf(stderr, "%s: warning: no statistics counters support is compiled in\n", progname); #else statsfile = optarg; if (*statsfile != '+') stats_relative = 0; else ++statsfile, stats_relative = 1; if (!*statsfile) statsfile = NULL; #endif break; case 'q': quickstart = 1; break; case 'd': #ifdef NO_MASTER_DUMP error(0, "master-format dump option (-d) isn't compiled in"); #endif dump = 1; break; case 'v': show_version = nover++ ? NULL : "rbldnsd"; break; case 'a': lazy = 1; break; case 'A': lazy = 0; break; case 'f': forkon = 1; break; case 'C': nouncompress = 1; break; #ifndef NO_DSO case 'x': ext = optarg; break; case 'X': extarg = optarg; break; #else case 'x': case 'X': error(0, "extension support is not compiled in"); #endif case 'h': usage(0); default: error(0, "type `%.50s -h' for help", progname); } if (!(argc -= optind)) error(0, "no zone(s) to service specified (-h for help)"); argv += optind; #ifndef NO_MASTER_DUMP if (dump) { time_t now; logto = LOGTO_STDERR; for(c = 0; c < argc; ++c) zonelist = addzone(zonelist, argv[c]); init_zones_caches(zonelist); if (rootdir && (chdir(rootdir) < 0 || chroot(rootdir) < 0)) error(errno, "unable to chroot to %.50s", rootdir); if (workdir && chdir(workdir) < 0) error(errno, "unable to chdir to %.50s", workdir); if (!do_reload(0)) error(0, "zone loading errors, aborting"); now = time(NULL); printf("; zone dump made %s", ctime(&now)); printf("; rbldnsd version %s\n", version); for (z = zonelist; z; z = z->z_next) dumpzone(z, stdout); fflush(stdout); exit(ferror(stdout) ? 1 : 0); } #endif if (!nba) error(0, "no address to listen on (-b option) specified"); tzset(); if (nodaemon) logto = LOGTO_STDOUT|LOGTO_STDERR; else { /* fork early so that logging will be from right pid */ int pfd[2]; if (pipe(pfd) < 0) error(errno, "pipe() failed"); c = fork(); if (c < 0) error(errno, "fork() failed"); if (c > 0) { close(pfd[1]); if (read(pfd[0], &c, 1) < 1) exit(1); else exit(0); } cfd = pfd[1]; close(pfd[0]); openlog(progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); logto = LOGTO_STDERR|LOGTO_SYSLOG; if (!quickstart && !flog) logto |= LOGTO_STDOUT; } initsockets(bindaddr, nba, family); #ifndef NO_DSO if (ext) { void *handle = dlopen(ext, RTLD_NOW); if (!handle) error(0, "unable to load extension `%s': %s", ext, dlerror()); extinit = dlsym(handle, "rbldnsd_extension_init"); if (!extinit) error(0, "unable to find extension init routine in `%s'", ext); } #endif if (!user && !(uid = getuid())) user = "rbldns"; if (!user) p = NULL; else { if ((p = strchr(user, ':')) != NULL) *p++ = '\0'; if ((c = satoi(user)) >= 0) uid = c, gid = c; else { struct passwd *pw = getpwnam(user); if (!pw) error(0, "unknown user `%s'", user); uid = pw->pw_uid; gid = pw->pw_gid; endpwent(); } } if (!uid) error(0, "daemon should not run as root, specify -u option"); if (p) { if ((c = satoi(p)) >= 0) gid = c; else { struct group *gr = getgrnam(p); if (!gr) error(0, "unknown group `%s'", p); gid = gr->gr_gid; endgrent(); } p[-1] = ':'; } if (pidfile) { int fdpid; char buf[40]; c = sprintf(buf, "%ld\n", (long)getpid()); fdpid = open(pidfile, O_CREAT|O_WRONLY|O_TRUNC, 0644); if (fdpid < 0 || write(fdpid, buf, c) < c) error(errno, "unable to write pidfile"); close(fdpid); } if (rootdir && (chdir(rootdir) < 0 || chroot(rootdir) < 0)) error(errno, "unable to chroot to %.50s", rootdir); if (workdir && chdir(workdir) < 0) error(errno, "unable to chdir to %.50s", workdir); if (user) if (setgroups(1, &gid) < 0 || setgid(gid) < 0 || setuid(uid) < 0) error(errno, "unable to setuid(%d:%d)", (int)uid, (int)gid); for(c = 0; c < argc; ++c) zonelist = addzone(zonelist, argv[c]); init_zones_caches(zonelist); #ifndef NO_DSO if (extinit && extinit(extarg, zonelist) != 0) error(0, "unable to iniitialize extension `%s'", ext); #endif if (!quickstart && !do_reload(0)) error(0, "zone loading errors, aborting"); /* count number of zones */ for(c = 0, z = zonelist; z; z = z->z_next) ++c; numzones = c; #if STATS_IPC_IOVEC stats_iov = (struct iovec *)emalloc(numzones * sizeof(struct iovec)); for(c = 0, z = zonelist; z; z = z->z_next, ++c) { stats_iov[c].iov_base = (char*)&z->z_stats; stats_iov[c].iov_len = sizeof(z->z_stats); } #endif dslog(LOG_INFO, 0, "rbldnsd version %s started (%d socket(s), %d zone(s))", version, numsock, numzones); initialized = 1; if (cfd >= 0) { write(cfd, "", 1); close(cfd); close(0); close(2); if (!flog) close(1); setsid(); logto = LOGTO_SYSLOG; } if (quickstart) do_reload(0); /* only set "main" fork_on_reload after first reload */ fork_on_reload = forkon; } static void sighandler(int sig) { switch(sig) { case SIGHUP: signalled |= SIGNALLED_RELOG|SIGNALLED_RELOAD; break; case SIGALRM: #ifndef HAVE_SETITIMER alarm(recheck); #endif signalled |= SIGNALLED_RELOAD|SIGNALLED_SSTATS; break; #ifndef NO_STATS case SIGUSR1: signalled |= SIGNALLED_LSTATS|SIGNALLED_SSTATS; break; case SIGUSR2: signalled |= SIGNALLED_LSTATS|SIGNALLED_SSTATS|SIGNALLED_ZSTATS; break; #endif case SIGTERM: case SIGINT: signalled |= SIGNALLED_TERM; break; } } static sigset_t ssblock; /* signals to block during zone reload */ static sigset_t ssempty; /* empty set */ static void setup_signals(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sighandler; sigemptyset(&ssblock); sigemptyset(&ssempty); sigaction(SIGHUP, &sa, NULL); sigaddset(&ssblock, SIGHUP); sigaction(SIGALRM, &sa, NULL); sigaddset(&ssblock, SIGALRM); #ifndef NO_STATS sigaction(SIGUSR1, &sa, NULL); sigaddset(&ssblock, SIGUSR1); sigaction(SIGUSR2, &sa, NULL); sigaddset(&ssblock, SIGUSR2); #endif sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); signal(SIGPIPE, SIG_IGN); /* in case logfile is FIFO */ } #ifndef NO_STATS struct dnsstats gstats; static struct dnsstats gptot; static time_t stats_time; static void dumpstats(void) { struct dnsstats tot; char name[DNS_MAXDOMAIN+1]; FILE *f; struct zone *z; f = fopen(statsfile, "a"); if (f) fprintf(f, "%ld", (long)time(NULL)); #define C ":%" PRI_DNSCNT tot = gstats; for(z = zonelist; z; z = z->z_next) { #define add(x) tot.x += z->z_stats.x add(b_in); add(b_out); add(q_ok); add(q_nxd); add(q_err); #undef add if (f) { dns_dntop(z->z_dn, name, sizeof(name)); #define delta(x) z->z_stats.x - z->z_pstats.x fprintf(f, " %s" C C C C C, name, delta(q_ok) + delta(q_nxd) + delta(q_err), delta(q_ok), delta(q_nxd), delta(b_in), delta(b_out)); #undef delta } if (stats_relative) z->z_pstats = z->z_stats; } if (f) { #define delta(x) tot.x - gptot.x fprintf(f, " *" C C C C C "\n", delta(q_ok) + delta(q_nxd) + delta(q_err), delta(q_ok), delta(q_nxd), delta(b_in), delta(b_out)); #undef delta fclose(f); } if (stats_relative) gptot = tot; #undef C } static void dumpstats_z(void) { FILE *f = fopen(statsfile, "a"); if (f) { fprintf(f, "%ld\n", (long)time(NULL)); fclose(f); } } static void logstats(int reset) { time_t t = time(NULL); time_t d = t - stats_time; struct dnsstats tot = gstats; char name[DNS_MAXDOMAIN+1]; struct zone *z; #define C(x) " " #x "=%" PRI_DNSCNT for(z = zonelist; z; z = z->z_next) { #define add(x) tot.x += z->z_stats.x add(b_in); add(b_out); add(q_ok); add(q_nxd); add(q_err); #undef add dns_dntop(z->z_dn, name, sizeof(name)); dslog(LOG_INFO, 0, "stats for %ldsecs zone %.60s:" C(tot) C(ok) C(nxd) C(err) C(in) C(out), (long)d, name, z->z_stats.q_ok + z->z_stats.q_nxd + z->z_stats.q_err, z->z_stats.q_ok, z->z_stats.q_nxd, z->z_stats.q_err, z->z_stats.b_in, z->z_stats.b_out); } dslog(LOG_INFO, 0, "stats for %ldsec:" C(tot) C(ok) C(nxd) C(err) C(in) C(out), (long)d, tot.q_ok + tot.q_nxd + tot.q_err, tot.q_ok, tot.q_nxd, tot.q_err, tot.b_in, tot.b_out); #undef C if (reset) { for(z = zonelist; z; z = z->z_next) { memset(&z->z_stats, 0, sizeof(z->z_stats)); memset(&z->z_pstats, 0, sizeof(z->z_pstats)); } memset(&gstats, 0, sizeof(gstats)); memset(&gptot, 0, sizeof(gptot)); stats_time = t; } } #if STATS_IPC_IOVEC # define ipc_read_stats(fd) readv(fd, stats_iov, numzones) # define ipc_write_stats(fd) writev(fd, stats_iov, numzones) #else static void ipc_read_stats(int fd) { struct zone *z; for(z = zonelist; z; z = z->z_next) if (read(fd, &z->z_stats, sizeof(z->z_stats)) <= 0) break; } static void ipc_write_stats(int fd) { const struct zone *z; for(z = zonelist; z; z = z->z_next) if (write(fd, &z->z_stats, sizeof(z->z_stats)) <= 0) break; } #endif #else # define ipc_read_stats(fd) # define ipc_write_stats(fd) #endif static void reopenlog(void) { if (logfile) { int fd; if (flog) fclose(flog); fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK|O_LARGEFILE, 0644); if (fd < 0 || (flog = fdopen(fd, "a")) == NULL) { dslog(LOG_WARNING, 0, "error (re)opening logfile `%.50s': %s", logfile, strerror(errno)); if (fd >= 0) close(fd); flog = NULL; } } else if (flog && !flushlog) { /* log to stdout */ clearerr(flog); fflush(flog); } } static void check_expires(void) { struct zone *zone; time_t now = time(NULL); for (zone = zonelist; zone; zone = zone->z_next) { if (!zone->z_stamp) continue; if (zone->z_expires && zone->z_expires < now) { zlog(LOG_WARNING, zone, "zone data expired, zone will not be serviced"); zone->z_stamp = 0; } } } static int do_reload(int do_fork) { int r; char ibuf[150]; int ip; struct dataset *ds; struct zone *zone; pid_t cpid = 0; /* child pid; =0 to make gcc happy */ int cfd = 0; /* child stats fd; =0 to make gcc happy */ #ifndef NO_TIMES struct tms tms; clock_t utm, etm; #ifndef HZ static clock_t HZ; #endif #endif /* NO_TIMES */ ds = nextdataset2reload(NULL); if (!ds && call_hook(reload_check, (zonelist)) == 0) { check_expires(); return 1; /* nothing to reload */ } if (do_fork) { int pfd[2]; if (flog && !flushlog) fflush(flog); /* forking reload. if anything fails, just do a non-forking one */ if (pipe(pfd) < 0) do_fork = 0; else if ((cpid = fork()) < 0) { /* fork failed, close the pipe */ close(pfd[0]); close(pfd[1]); do_fork = 0; } else if (!cpid) { /* child, continue answering queries */ signal(SIGALRM, SIG_IGN); signal(SIGHUP, SIG_IGN); #ifndef NO_STATS signal(SIGUSR1, SIG_IGN); signal(SIGUSR2, SIG_IGN); #endif close(pfd[0]); /* set up the fd#1 to write stats later on SIGTERM */ if (pfd[1] != 1) { dup2(pfd[1], 1); close(pfd[1]); } fork_on_reload = -1; return 1; } else { close(pfd[1]); cfd = pfd[0]; } } #ifndef NO_TIMES #ifndef HZ if (!HZ) HZ = sysconf(_SC_CLK_TCK); #endif etm = times(&tms); utm = tms.tms_utime; #endif /* NO_TIMES */ r = 1; while(ds) { if (!loaddataset(ds)) r = 0; ds = nextdataset2reload(ds); } for (zone = zonelist; zone; zone = zone->z_next) { time_t stamp = 0; time_t expires = 0; const struct dssoa *dssoa = NULL; const struct dsns *dsns = NULL; unsigned nsttl = 0; struct dslist *dsl; for(dsl = zone->z_dsl; dsl; dsl = dsl->dsl_next) { const struct dataset *ds = dsl->dsl_ds; if (!ds->ds_stamp) { stamp = 0; break; } if (stamp < ds->ds_stamp) stamp = ds->ds_stamp; if (ds->ds_expires && (!expires || expires > ds->ds_expires)) expires = ds->ds_expires; if (!dssoa) dssoa = ds->ds_dssoa; if (!dsns) dsns = ds->ds_dsns, nsttl = ds->ds_nsttl; } zone->z_expires = expires; zone->z_stamp = stamp; if (!stamp) { zlog(LOG_WARNING, zone, "not all datasets are loaded, zone will not be serviced"); r = 0; } else if (!update_zone_soa(zone, dssoa) || !update_zone_ns(zone, dsns, nsttl, zonelist)) zlog(LOG_WARNING, zone, "NS or SOA RRs are too long, will be ignored"); } if (call_hook(reload, (zonelist)) != 0) r = 0; ip = ssprintf(ibuf, sizeof(ibuf), "zones reloaded"); #ifndef NO_TIMES etm = times(&tms) - etm; utm = tms.tms_utime - utm; # define sec(tm) (unsigned long)(tm/HZ), (unsigned long)((tm*100/HZ)%100) ip += ssprintf(ibuf + ip, sizeof(ibuf) - ip, ", time %lu.%lue/%lu.%luu sec", sec(etm), sec(utm)); # undef sec #endif /* NO_TIMES */ #ifndef NO_MEMINFO { struct mallinfo mi = mallinfo(); # define kb(x) ((mi.x + 512)>>10) ip += ssprintf(ibuf + ip, sizeof(ibuf) - ip, ", mem arena=%d free=%d mmap=%d Kb", kb(arena), kb(fordblks), kb(hblkhd)); # undef kb } #endif /* NO_MEMINFO */ dslog(LOG_INFO, 0, ibuf); check_expires(); /* ok, (something) loaded. */ if (do_fork) { /* here we should notify query-answering child (send SIGTERM to it), * and wait for it to complete. * Unfortunately at least on linux, the SIGTERM sometimes gets ignored * by the child process, so we're trying several times here, in a loop. */ int s, n; fd_set fds; struct timeval tv; for(n = 1; ++n;) { if (kill(cpid, SIGTERM) != 0) dslog(LOG_WARNING, 0, "kill(qchild): %s", strerror(errno)); FD_ZERO(&fds); FD_SET(cfd, &fds); tv.tv_sec = 0; tv.tv_usec = 500000; s = select(cfd+1, &fds, NULL, NULL, &tv); if (s > 0) break; dslog(LOG_WARNING, 0, "waiting for qchild process: %s, retrying", s ? strerror(errno) : "timeout"); } ipc_read_stats(cfd); close(cfd); wait(&s); } return r; } static void do_signalled(void) { sigprocmask(SIG_SETMASK, &ssblock, NULL); if (signalled & SIGNALLED_TERM) { if (fork_on_reload < 0) { /* this is a temp child; dump stats and exit */ ipc_write_stats(1); if (flog && !flushlog) fflush(flog); _exit(0); } dslog(LOG_INFO, 0, "terminating"); #ifndef NO_STATS if (statsfile) dumpstats(); logstats(0); if (statsfile) dumpstats_z(); #endif exit(0); } #ifndef NO_STATS if (signalled & SIGNALLED_SSTATS && statsfile) dumpstats(); if (signalled & SIGNALLED_LSTATS) { logstats(signalled & SIGNALLED_ZSTATS); if (signalled & SIGNALLED_ZSTATS && statsfile) dumpstats_z(); } #endif if (signalled & SIGNALLED_RELOG) reopenlog(); if (signalled & SIGNALLED_RELOAD) do_reload(fork_on_reload); signalled = 0; sigprocmask(SIG_SETMASK, &ssempty, NULL); } #ifndef NO_IPv6 static struct sockaddr_storage peer_sa; #else static struct sockaddr_in peer_sa; #endif static struct dnspacket pkt; static void request(int fd) { int q, r; socklen_t salen = sizeof(peer_sa); q = recvfrom(fd, (void*)pkt.p_buf, sizeof(pkt.p_buf), 0, (struct sockaddr *)&peer_sa, &salen); if (q <= 0) /* interrupted? */ return; pkt.p_peerlen = salen; r = replypacket(&pkt, q, zonelist); if (!r) return; if (flog) logreply(&pkt, flog, flushlog); /* finally, send a reply */ while(sendto(fd, (void*)pkt.p_buf, r, 0, (struct sockaddr *)&peer_sa, salen) < 0) if (errno != EINTR) break; } int main(int argc, char **argv) { init(argc, argv); setup_signals(); reopenlog(); #ifdef HAVE_SETITIMER if (recheck) { struct itimerval itv; itv.it_interval.tv_sec = itv.it_value.tv_sec = recheck; itv.it_interval.tv_usec = itv.it_value.tv_usec = 0; if (setitimer(ITIMER_REAL, &itv, NULL) < 0) error(errno, "unable to setitimer()"); } #else alarm(recheck); #endif #ifndef NO_STATS stats_time = time(NULL); if (statsfile) dumpstats_z(); #endif pkt.p_peer = (struct sockaddr *)&peer_sa; if (numsock == 1) { /* optimized case for only one socket */ int fd = sock[0]; for(;;) { if (signalled) do_signalled(); request(fd); } } else { /* several sockets, do select/poll loop */ #ifdef NO_POLL fd_set rfds; int maxfd = 0; int *fdi, *fde = sock + numsock; FD_ZERO(&rfds); for (fdi = sock; fdi < fde; ++fdi) { FD_SET(*fdi, &rfds); if (*fdi > maxfd) maxfd = *fdi; } ++maxfd; for(;;) { fd_set rfd = rfds; if (signalled) do_signalled(); if (select(maxfd, &rfd, NULL, NULL, NULL) <= 0) continue; for(fdi = sock; fdi < fde; ++fdi) { if (FD_ISSET(*fdi, &rfd)) request(*fdi); } } #else /* !NO_POLL */ struct pollfd pfda[MAXSOCK]; struct pollfd *pfdi, *pfde = pfda + numsock; int r; for(r = 0; r < numsock; ++r) { pfda[r].fd = sock[r]; pfda[r].events = POLLIN; } for(;;) { if (signalled) do_signalled(); r = poll(pfda, numsock, -1); if (r <= 0) continue; for(pfdi = pfda; pfdi < pfde; ++pfdi) { if (!(pfdi->revents & POLLIN)) continue; request(pfdi->fd); if (!--r) break; } } #endif /* NO_POLL */ } } void oom(void) { if (initialized) dslog(LOG_ERR, 0, "out of memory loading dataset"); else error(0, "out of memory"); }