]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/dnsmasq.c
import of dnsmasq-2.56.tar.gz
[people/ms/dnsmasq.git] / src / dnsmasq.c
index da217403a0a32f162c2c48bb0a037cff1ff34ff8..689a86b606a363d3e8e99172400e4e261e462c4c 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2007 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2011 Simon Kelley
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -10,8 +10,8 @@
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
      
-  You should have received a copy of the GNU General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "dnsmasq.h"
@@ -33,10 +33,6 @@ static char *compile_opts =
 #ifdef NO_FORK
 "no-MMU "
 #endif
-#ifndef HAVE_ISC_READER
-"no-"
-#endif
-"ISC-leasefile "
 #ifndef HAVE_DBUS
 "no-"
 #endif
@@ -45,11 +41,20 @@ static char *compile_opts =
 "no-"
 #endif
 "I18N "
+#ifndef HAVE_DHCP
+"no-"
+#endif
+"DHCP "
+#if defined(HAVE_DHCP) && !defined(HAVE_SCRIPT)
+"no-scripts "
+#endif
 #ifndef HAVE_TFTP
 "no-"
 #endif
 "TFTP";
 
+
+
 static volatile pid_t pid = 0;
 static volatile int pipewrite;
 
@@ -57,18 +62,28 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp);
 static void check_dns_listeners(fd_set *set, time_t now);
 static void sig_handler(int sig);
 static void async_event(int pipe, time_t now);
-static void poll_resolv(void);
+static void fatal_event(struct event_desc *ev);
 
 int main (int argc, char **argv)
 {
   int bind_fallback = 0;
-  int bad_capabilities = 0;
-  time_t now, last = 0;
+  time_t now;
   struct sigaction sigact;
   struct iname *if_tmp;
-  int piperead, pipefd[2];
-  struct passwd *ent_pw;
+  int piperead, pipefd[2], err_pipe[2];
+  struct passwd *ent_pw = NULL;
+#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT)
+  uid_t script_uid = 0;
+  gid_t script_gid = 0;
+#endif
+  struct group *gp = NULL;
   long i, max_fd = sysconf(_SC_OPEN_MAX);
+  char *baduser = NULL;
+  int log_err;
+#if defined(HAVE_LINUX_NETWORK)
+  cap_user_header_t hdr = NULL;
+  cap_user_data_t data = NULL;
+#endif 
 
 #ifdef LOCALEDIR
   setlocale(LC_ALL, "");
@@ -99,15 +114,13 @@ int main (int argc, char **argv)
   daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? 
     daemon->edns_pktsz : DNSMASQ_PACKETSZ;
   daemon->packet = safe_malloc(daemon->packet_buff_sz);
-  
+
+#ifdef HAVE_DHCP
   if (!daemon->lease_file)
     {
       if (daemon->dhcp)
        daemon->lease_file = LEASEFILE;
     }
-#ifndef HAVE_ISC_READER
-  else if (!daemon->dhcp)
-    die(_("ISC dhcpd integration not available: set HAVE_ISC_READER in src/config.h"), NULL, EC_BADCONF);
 #endif
   
   /* Close any file descriptors we inherited apart from std{in|out|err} */
@@ -120,15 +133,15 @@ int main (int argc, char **argv)
 #elif !(defined(IP_RECVDSTADDR) && \
        defined(IP_RECVIF) && \
        defined(IP_SENDSRCADDR))
-  if (!(daemon->options & OPT_NOWILD))
+  if (!option_bool(OPT_NOWILD))
     {
       bind_fallback = 1;
-      daemon->options |= OPT_NOWILD;
+      set_option_bool(OPT_NOWILD);
     }
 #endif
 
 #ifndef HAVE_TFTP
-  if (daemon->options & OPT_TFTP)
+  if (daemon->tftp_unlimited || daemon->tftp_interfaces)
     die(_("TFTP server not available: set HAVE_TFTP in src/config.h"), NULL, EC_BADCONF);
 #endif
 
@@ -137,30 +150,25 @@ int main (int argc, char **argv)
     die(_("asychronous logging is not available under Solaris"), NULL, EC_BADCONF);
 #endif
   
+  rand_init();
+  
   now = dnsmasq_time();
   
+#ifdef HAVE_DHCP
   if (daemon->dhcp)
     {
-#if !defined(HAVE_LINUX_NETWORK) && !defined(IP_RECVIF)
-      int c;
-      struct iname *tmp;
-      for (c = 0, tmp = daemon->if_names; tmp; tmp = tmp->next)
-       if (!tmp->isloop)
-         c++;
-      if (c != 1)
-       die(_("must set exactly one interface on broken systems without IP_RECVIF"), NULL, EC_BADCONF);
-#endif
       /* Note that order matters here, we must call lease_init before
         creating any file descriptors which shouldn't be leaked
         to the lease-script init process. */
       lease_init(now);
       dhcp_init();
     }
+#endif
 
   if (!enumerate_interfaces())
     die(_("failed to find list of interfaces: %s"), NULL, EC_MISC);
     
-  if (daemon->options & OPT_NOWILD
+  if (option_bool(OPT_NOWILD)
     {
       daemon->listeners = create_bound_listeners();
 
@@ -175,14 +183,13 @@ int main (int argc, char **argv)
            die(_("no interface with address %s"), daemon->namebuff, EC_BADNET);
          }
     }
-  else if ((daemon->port != 0 || (daemon->options & OPT_TFTP)) &&
-          !(daemon->listeners = create_wildcard_listeners()))
-    die(_("failed to create listening socket: %s"), NULL, EC_BADNET);
+  else 
+    daemon->listeners = create_wildcard_listeners();
   
   if (daemon->port != 0)
     cache_init();
-
-  if (daemon->options & OPT_DBUS)
+    
+  if (option_bool(OPT_DBUS))
 #ifdef HAVE_DBUS
     {
       char *err;
@@ -197,108 +204,190 @@ int main (int argc, char **argv)
   
   if (daemon->port != 0)
     pre_allocate_sfds();
+
+#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT)
+  /* Note getpwnam returns static storage */
+  if (daemon->dhcp && daemon->lease_change_command && daemon->scriptuser)
+    {
+      if ((ent_pw = getpwnam(daemon->scriptuser)))
+       {
+         script_uid = ent_pw->pw_uid;
+         script_gid = ent_pw->pw_gid;
+        }
+      else
+       baduser = daemon->scriptuser;
+    }
+#endif
   
+  if (daemon->username && !(ent_pw = getpwnam(daemon->username)))
+    baduser = daemon->username;
+  else if (daemon->groupname && !(gp = getgrnam(daemon->groupname)))
+    baduser = daemon->groupname;
+
+  if (baduser)
+    die(_("unknown user or group: %s"), baduser, EC_BADCONF);
+   
+  /* implement group defaults, "dip" if available, or group associated with uid */
+  if (!daemon->group_set && !gp)
+    {
+      if (!(gp = getgrnam(CHGRP)) && ent_pw)
+       gp = getgrgid(ent_pw->pw_gid);
+      
+      /* for error message */
+      if (gp)
+       daemon->groupname = gp->gr_name; 
+    }
+
+#if defined(HAVE_LINUX_NETWORK)
+  /* determine capability API version here, while we can still
+     call safe_malloc */
+  if (ent_pw && ent_pw->pw_uid != 0)
+    {
+      int capsize = 1; /* for header version 1 */
+      hdr = safe_malloc(sizeof(*hdr));
+
+      /* find version supported by kernel */
+      memset(hdr, 0, sizeof(*hdr));
+      capget(hdr, NULL);
+      
+      if (hdr->version != LINUX_CAPABILITY_VERSION_1)
+       {
+         /* if unknown version, use largest supported version (3) */
+         if (hdr->version != LINUX_CAPABILITY_VERSION_2)
+           hdr->version = LINUX_CAPABILITY_VERSION_3;
+         capsize = 2;
+       }
+      
+      data = safe_malloc(sizeof(*data) * capsize);
+      memset(data, 0, sizeof(*data) * capsize);
+    }
+#endif
+
   /* Use a pipe to carry signals and other events back to the event loop 
-     in a race-free manner */
-  if (pipe(pipefd) == -1 || !fix_fd(pipefd[0]) || !fix_fd(pipefd[1])) 
-    die(_("cannot create pipe: %s"), NULL, EC_MISC);
+     in a race-free manner and another to carry errors to daemon-invoking process */
+  safe_pipe(pipefd, 1);
   
   piperead = pipefd[0];
   pipewrite = pipefd[1];
   /* prime the pipe to load stuff first time. */
   send_event(pipewrite, EVENT_RELOAD, 0); 
+
+  err_pipe[1] = -1;
   
-  if (!(daemon->options & OPT_DEBUG))   
+  if (!option_bool(OPT_DEBUG))   
     {
-      FILE *pidfile;
-      int nullfd;
-
       /* The following code "daemonizes" the process. 
         See Stevens section 12.4 */
+      
+      if (chdir("/") != 0)
+       die(_("cannot chdir to filesystem root: %s"), NULL, EC_MISC); 
 
 #ifndef NO_FORK      
-      if (!(daemon->options & OPT_NO_FORK))
+      if (!option_bool(OPT_NO_FORK))
        {
          pid_t pid;
          
-         if ((pid = fork()) == -1 )
-           die(_("cannot fork into background: %s"), NULL, EC_MISC);
+         /* pipe to carry errors back to original process.
+            When startup is complete we close this and the process terminates. */
+         safe_pipe(err_pipe, 0);
          
+         if ((pid = fork()) == -1)
+           /* fd == -1 since we've not forked, never returns. */
+           send_event(-1, EVENT_FORK_ERR, errno);
+          
          if (pid != 0)
-           _exit(EC_GOOD);
+           {
+             struct event_desc ev;
+             
+             /* close our copy of write-end */
+             close(err_pipe[1]);
+             
+             /* check for errors after the fork */
+             if (read_write(err_pipe[0], (unsigned char *)&ev, sizeof(ev), 1))
+               fatal_event(&ev);
+             
+             _exit(EC_GOOD);
+           } 
          
-         setsid();
-         pid = fork();
+         close(err_pipe[0]);
 
-         if (pid != 0 && pid != -1)
+         /* NO calls to die() from here on. */
+         
+         setsid();
+        
+         if ((pid = fork()) == -1)
+           send_event(err_pipe[1], EVENT_FORK_ERR, errno);
+        
+         if (pid != 0)
            _exit(0);
        }
 #endif
-      
-      if (chdir("/") != 0)
-       die(_("cannot chdir to filesystem root: %s"), NULL, EC_MISC);
-      
+            
       /* write pidfile _after_ forking ! */
-      if (daemon->runfile && (pidfile = fopen(daemon->runfile, "w")))
-       {
-         fprintf(pidfile, "%d\n", (int) getpid());
-         fclose(pidfile);
+      if (daemon->runfile)
+       {
+         FILE *pidfile;
+         
+         /* only complain if started as root */
+         if ((pidfile = fopen(daemon->runfile, "w")))
+           {
+             fprintf(pidfile, "%d\n", (int) getpid());
+             fclose(pidfile);
+           }
+         else if (getuid() == 0)
+           {
+             send_event(err_pipe[1], EVENT_PIDFILE, errno);
+             _exit(0);
+           }
        }
-      
-      /* open  stdout etc to /dev/null */
-      nullfd = open("/dev/null", O_RDWR);
-      dup2(nullfd, STDOUT_FILENO);
-      dup2(nullfd, STDERR_FILENO);
-      dup2(nullfd, STDIN_FILENO);
-      close(nullfd);
     }
   
-  /* if we are to run scripts, we need to fork a helper before dropping root. */
-#ifndef NO_FORK
-  daemon->helperfd = create_helper(pipewrite, max_fd);
-#endif
+   log_err = log_start(ent_pw, err_pipe[1]);
+
+   if (!option_bool(OPT_DEBUG)) 
+     {       
+       /* open  stdout etc to /dev/null */
+       int nullfd = open("/dev/null", O_RDWR);
+       dup2(nullfd, STDOUT_FILENO);
+       dup2(nullfd, STDERR_FILENO);
+       dup2(nullfd, STDIN_FILENO);
+       close(nullfd);
+     }
    
-  ent_pw = daemon->username ? getpwnam(daemon->username) : NULL;
-
-  /* before here, we should only call die(), after here, only call syslog() */
-  log_start(ent_pw); 
+   /* if we are to run scripts, we need to fork a helper before dropping root. */
+  daemon->helperfd = -1;
+#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT) 
+  if (daemon->dhcp && daemon->lease_change_command)
+    daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd);
+#endif
 
-  if (!(daemon->options & OPT_DEBUG))   
+  if (!option_bool(OPT_DEBUG) && getuid() == 0)   
     {
-      /* UID changing, etc */
-      if (daemon->groupname || ent_pw)
+      int bad_capabilities = 0;
+      gid_t dummy;
+      
+      /* remove all supplimentary groups */
+      if (gp && 
+         (setgroups(0, &dummy) == -1 ||
+          setgid(gp->gr_gid) == -1))
        {
-         gid_t dummy;
-         struct group *gp;
-         
-         /* change group for /etc/ppp/resolv.conf otherwise get the group for "nobody" */
-         if ((daemon->groupname && (gp = getgrnam(daemon->groupname))) || 
-             (ent_pw && (gp = getgrgid(ent_pw->pw_gid))))
-           {
-             /* remove all supplimentary groups */
-             setgroups(0, &dummy);
-             setgid(gp->gr_gid);
-           } 
+         send_event(err_pipe[1], EVENT_GROUP_ERR, errno);
+         _exit(0);
        }
-      
+  
       if (ent_pw && ent_pw->pw_uid != 0)
        {     
 #if defined(HAVE_LINUX_NETWORK)
          /* On linux, we keep CAP_NETADMIN (for ARP-injection) and
             CAP_NET_RAW (for icmp) if we're doing dhcp */
-         cap_user_header_t hdr = safe_malloc(sizeof(*hdr));
-         cap_user_data_t data = safe_malloc(sizeof(*data));
-         hdr->version = _LINUX_CAPABILITY_VERSION;
-         hdr->pid = 0; /* this process */
          data->effective = data->permitted = data->inheritable =
-           (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) |
-           (1 << CAP_SETGID) | (1 << CAP_SETUID);
+           (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID);
          
          /* Tell kernel to not clear capabilities when dropping root */
          if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1) == -1)
            bad_capabilities = errno;
-
-#elif defined(HAVE_SOLARIS_PRIVS)
+                         
+#elif defined(HAVE_SOLARIS_NETWORK)
          /* http://developers.sun.com/solaris/articles/program_privileges.html */
          priv_set_t *priv_set;
          
@@ -318,30 +407,39 @@ int main (int argc, char **argv)
          if (priv_set)
            priv_freeset(priv_set);
 
-#elif defined(HAVE_SOLARIS_NETWORK)
-
-         bad_capabilities = ENOTSUP;
 #endif    
 
-         if (bad_capabilities == 0) 
+         if (bad_capabilities != 0)
            {
-             /* finally drop root */
-             setuid(ent_pw->pw_uid);
-             
+             send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities);
+             _exit(0);
+           }
+         
+         /* finally drop root */
+         if (setuid(ent_pw->pw_uid) == -1)
+           {
+             send_event(err_pipe[1], EVENT_USER_ERR, errno);
+             _exit(0);
+           }     
+
 #ifdef HAVE_LINUX_NETWORK
-             data->effective = data->permitted = 
-               (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW);
-             data->inheritable = 0;
-             
-             /* lose the setuid and setgid capbilities */
-             capset(hdr, data);
-#endif
+         data->effective = data->permitted = 
+           (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW);
+         data->inheritable = 0;
+         
+         /* lose the setuid and setgid capbilities */
+         if (capset(hdr, data) == -1)
+           {
+             send_event(err_pipe[1], EVENT_CAP_ERR, errno);
+             _exit(0);
            }
+#endif
+         
        }
     }
   
 #ifdef HAVE_LINUX_NETWORK
-  if (daemon->options & OPT_DEBUG
+  if (option_bool(OPT_DEBUG)
     prctl(PR_SET_DUMPABLE, 1);
 #endif
 
@@ -355,7 +453,7 @@ int main (int argc, char **argv)
   my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts);
   
 #ifdef HAVE_DBUS
-  if (daemon->options & OPT_DBUS)
+  if (option_bool(OPT_DBUS))
     {
       if (daemon->dbus)
        my_syslog(LOG_INFO, _("DBus support enabled: connected to system bus"));
@@ -364,15 +462,19 @@ int main (int argc, char **argv)
     }
 #endif
 
+  if (log_err != 0)
+    my_syslog(LOG_WARNING, _("warning: failed to change owner of %s: %s"), 
+             daemon->log_file, strerror(log_err));
+
   if (bind_fallback)
     my_syslog(LOG_WARNING, _("setting --bind-interfaces option because of OS limitations"));
   
-  if (!(daemon->options & OPT_NOWILD)) 
+  if (!option_bool(OPT_NOWILD)) 
     for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
       if (if_tmp->name && !if_tmp->used)
        my_syslog(LOG_WARNING, _("warning: interface %s does not currently exist"), if_tmp->name);
    
-  if (daemon->port != 0 && (daemon->options & OPT_NO_RESOLV))
+  if (daemon->port != 0 && option_bool(OPT_NO_RESOLV))
     {
       if (daemon->resolv_files && !daemon->resolv_files->is_default)
        my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set"));
@@ -384,6 +486,7 @@ int main (int argc, char **argv)
   if (daemon->max_logs != 0)
     my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs);
 
+#ifdef HAVE_DHCP
   if (daemon->dhcp)
     {
       struct dhcp_context *dhcp_tmp;
@@ -392,26 +495,29 @@ int main (int argc, char **argv)
        {
          prettyprint_time(daemon->dhcp_buff2, dhcp_tmp->lease_time);
          strcpy(daemon->dhcp_buff, inet_ntoa(dhcp_tmp->start));
-         my_syslog(LOG_INFO, 
+         my_syslog(MS_DHCP | LOG_INFO, 
                    (dhcp_tmp->flags & CONTEXT_STATIC) ? 
                    _("DHCP, static leases only on %.0s%s, lease time %s") :
+                   (dhcp_tmp->flags & CONTEXT_PROXY) ?
+                   _("DHCP, proxy on subnet %.0s%s%.0s") :
                    _("DHCP, IP range %s -- %s, lease time %s"),
                    daemon->dhcp_buff, inet_ntoa(dhcp_tmp->end), daemon->dhcp_buff2);
        }
     }
+#endif
 
 #ifdef HAVE_TFTP
-  if (daemon->options & OPT_TFTP)
+  if (daemon->tftp_unlimited || daemon->tftp_interfaces)
     {
 #ifdef FD_SETSIZE
       if (FD_SETSIZE < (unsigned)max_fd)
        max_fd = FD_SETSIZE;
 #endif
 
-      my_syslog(LOG_INFO, "TFTP %s%s %s", 
+      my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s", 
                daemon->tftp_prefix ? _("root is ") : _("enabled"),
                daemon->tftp_prefix ? daemon->tftp_prefix: "",
-               daemon->options & OPT_TFTP_SECURE ? _("secure mode") : "");
+               option_bool(OPT_TFTP_SECURE) ? _("secure mode") : "");
       
       /* This is a guess, it assumes that for small limits, 
         disjoint files might be served, but for large limits, 
@@ -436,20 +542,16 @@ int main (int argc, char **argv)
       if (daemon->tftp_max > max_fd)
        {
          daemon->tftp_max = max_fd;
-         my_syslog(LOG_WARNING, 
+         my_syslog(MS_TFTP | LOG_WARNING, 
                    _("restricting maximum simultaneous TFTP transfers to %d"), 
                    daemon->tftp_max);
        }
     }
 #endif
 
-  if (!(daemon->options & OPT_DEBUG) && (getuid() == 0 || geteuid() == 0))
-    {
-      if (bad_capabilities)
-       my_syslog(LOG_WARNING, _("warning: setting capabilities failed: %s"), strerror(bad_capabilities));
-
-      my_syslog(LOG_WARNING, _("running as root"));
-    }
+  /* finished start-up - release original process */
+  if (err_pipe[1] != -1)
+    close(err_pipe[1]);
   
   if (daemon->port != 0)
     check_servers();
@@ -477,7 +579,7 @@ int main (int argc, char **argv)
 
       /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
       if (daemon->tftp_trans ||
-         ((daemon->options & OPT_DBUS) && !daemon->dbus))
+         (option_bool(OPT_DBUS) && !daemon->dbus))
        {
          t.tv_sec = 0;
          t.tv_usec = 250000;
@@ -488,11 +590,18 @@ int main (int argc, char **argv)
       set_dbus_listeners(&maxfd, &rset, &wset, &eset);
 #endif 
   
+#ifdef HAVE_DHCP
       if (daemon->dhcp)
        {
          FD_SET(daemon->dhcpfd, &rset);
          bump_maxfd(daemon->dhcpfd, &maxfd);
+         if (daemon->pxefd != -1)
+           {
+             FD_SET(daemon->pxefd, &rset);
+             bump_maxfd(daemon->pxefd, &maxfd);
+           }
        }
+#endif
 
 #ifdef HAVE_LINUX_NETWORK
       FD_SET(daemon->netlinkfd, &rset);
@@ -502,7 +611,8 @@ int main (int argc, char **argv)
       FD_SET(piperead, &rset);
       bump_maxfd(piperead, &maxfd);
 
-#ifndef NO_FORK
+#ifdef HAVE_DHCP
+#  ifdef HAVE_SCRIPT
       while (helper_buf_empty() && do_script_run(now));
 
       if (!helper_buf_empty())
@@ -510,11 +620,12 @@ int main (int argc, char **argv)
          FD_SET(daemon->helperfd, &wset);
          bump_maxfd(daemon->helperfd, &maxfd);
        }
-#else
+#  else
       /* need this for other side-effects */
       while (do_script_run(now));
+#  endif
 #endif
-      
+   
       /* must do this just before select(), when we know no
         more calls to my_syslog() can occur */
       set_log_writer(&wset, &maxfd);
@@ -529,32 +640,30 @@ int main (int argc, char **argv)
 
       check_log_writer(&wset);
 
+#ifdef HAVE_LINUX_NETWORK
+      if (FD_ISSET(daemon->netlinkfd, &rset))
+       netlink_multicast();
+#endif
+
       /* Check for changes to resolv files once per second max. */
       /* Don't go silent for long periods if the clock goes backwards. */
-      if (last == 0 || difftime(now, last) > 1.0 || difftime(now, last) < -1.0)
+      if (daemon->last_resolv == 0 || 
+         difftime(now, daemon->last_resolv) > 1.0 || 
+         difftime(now, daemon->last_resolv) < -1.0)
        {
-         last = now;
-
-#ifdef HAVE_ISC_READER
-         if (daemon->lease_file && !daemon->dhcp)
-           load_dhcp(now);
-#endif
+         /* poll_resolv doesn't need to reload first time through, since 
+            that's queued anyway. */
 
-         if (daemon->port != 0 && !(daemon->options & OPT_NO_POLL))
-           poll_resolv();
+         poll_resolv(0, daemon->last_resolv != 0, now);          
+         daemon->last_resolv = now;
        }
       
       if (FD_ISSET(piperead, &rset))
        async_event(piperead, now);
       
-#ifdef HAVE_LINUX_NETWORK
-      if (FD_ISSET(daemon->netlinkfd, &rset))
-       netlink_multicast();
-#endif
-      
 #ifdef HAVE_DBUS
       /* if we didn't create a DBus connection, retry now. */ 
-     if ((daemon->options & OPT_DBUS) && !daemon->dbus)
+     if (option_bool(OPT_DBUS) && !daemon->dbus)
        {
          char *err;
          if ((err = dbus_init()))
@@ -571,12 +680,19 @@ int main (int argc, char **argv)
       check_tftp_listeners(&rset, now);
 #endif      
 
-      if (daemon->dhcp && FD_ISSET(daemon->dhcpfd, &rset))
-       dhcp_packet(now);
+#ifdef HAVE_DHCP
+      if (daemon->dhcp)
+       {
+         if (FD_ISSET(daemon->dhcpfd, &rset))
+           dhcp_packet(now, 0);
+         if (daemon->pxefd != -1 && FD_ISSET(daemon->pxefd, &rset))
+           dhcp_packet(now, 1);
+       }
 
-#ifndef NO_FORK
+#  ifdef HAVE_SCRIPT
       if (daemon->helperfd != -1 && FD_ISSET(daemon->helperfd, &wset))
        helper_write();
+#  endif
 #endif
 
     }
@@ -628,11 +744,51 @@ void send_event(int fd, int event, int data)
   
   ev.event = event;
   ev.data = data;
-  /* pipe is non-blocking and struct event_desc is smaller than
-     PIPE_BUF, so this either fails or writes everything */
-  while (write(fd, &ev, sizeof(ev)) == -1 && errno == EINTR);
+  
+  /* error pipe, debug mode. */
+  if (fd == -1)
+    fatal_event(&ev);
+  else
+    /* pipe is non-blocking and struct event_desc is smaller than
+       PIPE_BUF, so this either fails or writes everything */
+    while (write(fd, &ev, sizeof(ev)) == -1 && errno == EINTR);
 }
 
+static void fatal_event(struct event_desc *ev)
+{
+  errno = ev->data;
+  
+  switch (ev->event)
+    {
+    case EVENT_DIE:
+      exit(0);
+
+    case EVENT_FORK_ERR:
+      die(_("cannot fork into background: %s"), NULL, EC_MISC);
+  
+    case EVENT_PIPE_ERR:
+      die(_("failed to create helper: %s"), NULL, EC_MISC);
+  
+    case EVENT_CAP_ERR:
+      die(_("setting capabilities failed: %s"), NULL, EC_MISC);
+
+    case EVENT_USER_ERR:
+    case EVENT_HUSER_ERR:
+      die(_("failed to change user-id to %s: %s"), 
+         ev->event == EVENT_USER_ERR ? daemon->username : daemon->scriptuser,
+         EC_MISC);
+
+    case EVENT_GROUP_ERR:
+      die(_("failed to change group-id to %s: %s"), daemon->groupname, EC_MISC);
+      
+    case EVENT_PIDFILE:
+      die(_("failed to open pidfile %s: %s"), daemon->runfile, EC_FILE);
+
+    case EVENT_LOG_ERR:
+      die(_("cannot open %s: %s"), daemon->log_file ? daemon->log_file : "log", EC_FILE);
+    }
+}      
+      
 static void async_event(int pipe, time_t now)
 {
   pid_t p;
@@ -644,12 +800,14 @@ static void async_event(int pipe, time_t now)
       {
       case EVENT_RELOAD:
        clear_cache_and_reload(now);
-       if (daemon->port != 0 && daemon->resolv_files && (daemon->options & OPT_NO_POLL))
+       if (daemon->port != 0 && daemon->resolv_files && option_bool(OPT_NO_POLL))
          {
            reload_servers(daemon->resolv_files->name);
            check_servers();
          }
+#ifdef HAVE_DHCP
        rerun_scripts();
+#endif
        break;
        
       case EVENT_DUMP:
@@ -658,11 +816,13 @@ static void async_event(int pipe, time_t now)
        break;
        
       case EVENT_ALARM:
+#ifdef HAVE_DHCP
        if (daemon->dhcp)
          {
            lease_prune(NULL, now);
            lease_update_file(now);
          }
+#endif
        break;
                
       case EVENT_CHILD:
@@ -688,11 +848,14 @@ static void async_event(int pipe, time_t now)
        break;
 
       case EVENT_EXEC_ERR:
-       my_syslog(LOG_ERR, _("failed to execute %s: %s"), daemon->lease_change_command, strerror(ev.data));
+       my_syslog(LOG_ERR, _("failed to execute %s: %s"), 
+                 daemon->lease_change_command, strerror(ev.data));
        break;
 
-      case EVENT_PIPE_ERR:
-       my_syslog(LOG_ERR, _("failed to create helper: %s"), strerror(ev.data));
+       /* necessary for fatal errors in helper */
+      case EVENT_HUSER_ERR:
+      case EVENT_DIE:
+       fatal_event(&ev);
        break;
 
       case EVENT_REOPEN:
@@ -709,7 +872,7 @@ static void async_event(int pipe, time_t now)
          if (daemon->tcp_pids[i] != 0)
            kill(daemon->tcp_pids[i], SIGALRM);
        
-#ifndef NO_FORK
+#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT)
        /* handle pending lease transitions */
        if (daemon->helperfd != -1)
          {
@@ -725,6 +888,9 @@ static void async_event(int pipe, time_t now)
        
        if (daemon->lease_stream)
          fclose(daemon->lease_stream);
+
+       if (daemon->runfile)
+         unlink(daemon->runfile);
        
        my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM"));
        flush_log();
@@ -732,7 +898,7 @@ static void async_event(int pipe, time_t now)
       }
 }
 
-static void poll_resolv()
+void poll_resolv(int force, int do_reload, time_t now)
 {
   struct resolvc *res, *latest;
   struct stat statbuf;
@@ -740,19 +906,37 @@ static void poll_resolv()
   /* There may be more than one possible file. 
      Go through and find the one which changed _last_.
      Warn of any which can't be read. */
+
+  if (daemon->port == 0 || option_bool(OPT_NO_POLL))
+    return;
+  
   for (latest = NULL, res = daemon->resolv_files; res; res = res->next)
     if (stat(res->name, &statbuf) == -1)
       {
+       if (force)
+         {
+           res->mtime = 0; 
+           continue;
+         }
+
        if (!res->logged)
          my_syslog(LOG_WARNING, _("failed to access %s: %s"), res->name, strerror(errno));
        res->logged = 1;
+       
+       if (res->mtime != 0)
+         { 
+           /* existing file evaporated, force selection of the latest
+              file even if its mtime hasn't changed since we last looked */
+           poll_resolv(1, do_reload, now);
+           return;
+         }
       }
     else
       {
        res->logged = 0;
-       if (statbuf.st_mtime != res->mtime)
-         {
-           res->mtime = statbuf.st_mtime;
+       if (force || (statbuf.st_mtime != res->mtime))
+          {
+            res->mtime = statbuf.st_mtime;
            if (difftime(statbuf.st_mtime, last_change) > 0.0)
              {
                last_change = statbuf.st_mtime;
@@ -769,8 +953,8 @@ static void poll_resolv()
          my_syslog(LOG_INFO, _("reading %s"), latest->name);
          warned = 0;
          check_servers();
-         if (daemon->options & OPT_RELOAD)
-           cache_reload(daemon->options, daemon->namebuff, daemon->domain_suffix, daemon->addn_hosts);
+         if (option_bool(OPT_RELOAD) && do_reload)
+           clear_cache_and_reload(now);
        }
       else 
        {
@@ -787,11 +971,12 @@ static void poll_resolv()
 void clear_cache_and_reload(time_t now)
 {
   if (daemon->port != 0)
-    cache_reload(daemon->options, daemon->namebuff, daemon->domain_suffix, daemon->addn_hosts);
+    cache_reload();
   
+#ifdef HAVE_DHCP
   if (daemon->dhcp)
     {
-      if (daemon->options & OPT_ETHERS)
+      if (option_bool(OPT_ETHERS))
        dhcp_read_ethers();
       reread_dhcp();
       dhcp_update_configs(daemon->dhcp_conf);
@@ -800,6 +985,7 @@ void clear_cache_and_reload(time_t now)
       lease_update_file(now); 
       lease_update_dns();
     }
+#endif
 }
 
 static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp)
@@ -828,7 +1014,15 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp)
       FD_SET(serverfdp->fd, set);
       bump_maxfd(serverfdp->fd, maxfdp);
     }
-         
+
+  if (daemon->port != 0 && !daemon->osport)
+    for (i = 0; i < RANDOM_SOCKS; i++)
+      if (daemon->randomsocks[i].refcount != 0)
+       {
+         FD_SET(daemon->randomsocks[i].fd, set);
+         bump_maxfd(daemon->randomsocks[i].fd, maxfdp);
+       }
+  
   for (listener = daemon->listeners; listener; listener = listener->next)
     {
       /* only listen for queries if we have resources */
@@ -865,17 +1059,24 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp)
 static void check_dns_listeners(fd_set *set, time_t now)
 {
   struct serverfd *serverfdp;
-  struct listener *listener;     
-  
+  struct listener *listener;
+  int i;
+
   for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
     if (FD_ISSET(serverfdp->fd, set))
-      reply_query(serverfdp, now);
+      reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now);
+  
+  if (daemon->port != 0 && !daemon->osport)
+    for (i = 0; i < RANDOM_SOCKS; i++)
+      if (daemon->randomsocks[i].refcount != 0 && 
+         FD_ISSET(daemon->randomsocks[i].fd, set))
+       reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now);
   
   for (listener = daemon->listeners; listener; listener = listener->next)
     {
       if (listener->fd != -1 && FD_ISSET(listener->fd, set))
        receive_query(listener, now); 
+      
 #ifdef HAVE_TFTP     
       if (listener->tftpfd != -1 && FD_ISSET(listener->tftpfd, set))
        tftp_request(listener, now);
@@ -892,7 +1093,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
          if (confd == -1)
            continue;
          
-         if (daemon->options & OPT_NOWILD)
+         if (option_bool(OPT_NOWILD))
            iface = listener->iface;
          else
            {
@@ -918,7 +1119,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
              close(confd);
            }
 #ifndef NO_FORK
-         else if (!(daemon->options & OPT_DEBUG) && (p = fork()) != 0)
+         else if (!option_bool(OPT_DEBUG) && (p = fork()) != 0)
            {
              if (p != -1)
                {
@@ -942,11 +1143,13 @@ static void check_dns_listeners(fd_set *set, time_t now)
              
              dst_addr_4.s_addr = 0;
              
-              /* Arrange for SIGALARM after CHILD_LIFETIME seconds to
-                 terminate the process. */
-             if (!(daemon->options & OPT_DEBUG))
+#ifndef NO_FORK
+             /* Arrange for SIGALARM after CHILD_LIFETIME seconds to
+                terminate the process. */
+             if (!option_bool(OPT_DEBUG))
                alarm(CHILD_LIFETIME);
-             
+#endif
+
              /* start with no upstream connections. */
              for (s = daemon->servers; s; s = s->next)
                 s->tcpfd = -1; 
@@ -975,7 +1178,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
                    close(s->tcpfd);
                  }
 #ifndef NO_FORK                   
-             if (!(daemon->options & OPT_DEBUG))
+             if (!option_bool(OPT_DEBUG))
                {
                  flush_log();
                  _exit(0);
@@ -986,7 +1189,7 @@ static void check_dns_listeners(fd_set *set, time_t now)
     }
 }
 
-
+#ifdef HAVE_DHCP
 int make_icmp_sock(void)
 {
   int fd;
@@ -1109,5 +1312,6 @@ int icmp_ping(struct in_addr addr)
 
   return gotreply;
 }
+#endif