Improve kernel-capability manipulation code under Linux.

Dnsmasq now fails early if a required capability is not available,
and tries not to request capabilities not required by its
configuration.
diff --git a/CHANGELOG b/CHANGELOG
index b942ec2..f2d38a0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -28,7 +28,12 @@
 
 	Support TCP-fastopen (RFC-7413) on both incoming and
 	outgoing TCP connections, if supported and enabled in the OS.
-	
+
+	Improve kernel-capability manipulation code under Linux. Dnsmasq
+	now fails early if a required capability is not available, and
+	tries not to request capabilities not required by its
+	configuration.
+
 	
 version 2.80
 	Add support for RFC 4039 DHCP rapid commit. Thanks to Ashram Method
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 4036914..3f3edbd 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -52,6 +52,9 @@
 #if defined(HAVE_LINUX_NETWORK)
   cap_user_header_t hdr = NULL;
   cap_user_data_t data = NULL;
+  int need_cap_net_admin = 0;
+  int need_cap_net_raw = 0;
+  int need_cap_net_bind_service = 0;
   char *bound_device = NULL;
   int did_bind = 0;
 #endif 
@@ -285,11 +288,24 @@
     }
   
   if (daemon->dhcp || daemon->relay4)
-    dhcp_init();
+    {
+      dhcp_init();
+#   ifdef HAVE_LINUX_NETWORK
+      if (!option_bool(OPT_NO_PING))
+	need_cap_net_raw = 1;
+      need_cap_net_admin = 1;
+#   endif
+    }
   
 #  ifdef HAVE_DHCP6
   if (daemon->doing_ra || daemon->doing_dhcp6 || daemon->relay6)
-    ra_init(now);
+    {
+      ra_init(now);
+#   ifdef HAVE_LINUX_NETWORK
+      need_cap_net_raw = 1;
+      need_cap_net_admin = 1;
+#   endif
+    }
   
   if (daemon->doing_dhcp6 || daemon->relay6)
     dhcp6_init();
@@ -299,7 +315,12 @@
 
 #ifdef HAVE_IPSET
   if (daemon->ipsets)
-    ipset_init();
+    {
+      ipset_init();
+#  ifdef HAVE_LINUX_NETWORK
+      need_cap_net_admin = 1;
+#  endif
+    }
 #endif
 
 #if  defined(HAVE_LINUX_NETWORK)
@@ -440,28 +461,58 @@
     }
 
 #if defined(HAVE_LINUX_NETWORK)
+  /* We keep CAP_NETADMIN (for ARP-injection) and
+     CAP_NET_RAW (for icmp) if we're doing dhcp,
+     if we have yet to bind ports because of DAD, 
+     or we're doing it dynamically,
+     we need CAP_NET_BIND_SERVICE. */
+  if ((is_dad_listeners() || option_bool(OPT_CLEVERBIND)) &&
+      (option_bool(OPT_TFTP) || (daemon->port != 0 && daemon->port <= 1024)))
+    need_cap_net_bind_service = 1;
+
   /* 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 */
+  char *fail = NULL;
+  
+  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)
     {
-      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);
+      /* 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);
+  capget(hdr, data); /* Get current values, for verification */
+
+  if (need_cap_net_admin && !(data->permitted & (1 << CAP_NET_ADMIN)))
+    fail = "NET_ADMIN";
+  else if (need_cap_net_raw && !(data->permitted & (1 << CAP_NET_RAW)))
+    fail = "NET_RAW";
+  else if (need_cap_net_bind_service && !(data->permitted & (1 << CAP_NET_BIND_SERVICE)))
+    fail = "NET_BIND_SERVICE";
+  
+  if (fail)
+    die(_("process is missing required capability %s"), fail, EC_MISC);
+
+  /* Now set bitmaps to set caps after daemonising */
+  memset(data, 0, sizeof(*data) * capsize);
+  
+  if (need_cap_net_admin)
+    data->effective |= (1 << CAP_NET_ADMIN);
+  if (need_cap_net_raw)
+    data->effective |= (1 << CAP_NET_RAW);
+  if (need_cap_net_bind_service)
+    data->effective |= (1 << CAP_NET_BIND_SERVICE);
+  
+  data->permitted = data->effective;  
 #endif
 
   /* Use a pipe to carry signals and other events back to the event loop 
@@ -626,18 +677,9 @@
       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. If we have yet to bind 
-	     ports because of DAD, or we're doing it dynamically,
-	     we need CAP_NET_BIND_SERVICE too. */
-	  if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
-	    data->effective = data->permitted = data->inheritable =
-	      (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | 
-	      (1 << CAP_SETUID) | (1 << CAP_NET_BIND_SERVICE);
-	  else
-	    data->effective = data->permitted = data->inheritable =
-	      (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID);
-	  
+	  /* Need to be able to drop root. */
+	  data->effective |= (1 << CAP_SETUID);
+	  data->permitted |= (1 << CAP_SETUID);
 	  /* Tell kernel to not clear capabilities when dropping root */
 	  if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1)
 	    bad_capabilities = errno;
@@ -678,15 +720,10 @@
 	    }     
 
 #ifdef HAVE_LINUX_NETWORK
-	  if (is_dad_listeners() || option_bool(OPT_CLEVERBIND))
-	   data->effective = data->permitted =
-	     (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BIND_SERVICE);
-	 else
-	   data->effective = data->permitted = 
-	     (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW);
-	  data->inheritable = 0;
+	  data->effective &= ~(1 << CAP_SETUID);
+	  data->permitted &= ~(1 << CAP_SETUID);
 	  
-	  /* lose the setuid and setgid capabilities */
+	  /* lose the setuid capability */
 	  if (capset(hdr, data) == -1)
 	    {
 	      send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL);