Home | History | Annotate | Download | only in src
      1 /* dnsmasq is Copyright (c) 2000-2009 Simon Kelley
      2 
      3    This program is free software; you can redistribute it and/or modify
      4    it under the terms of the GNU General Public License as published by
      5    the Free Software Foundation; version 2 dated June, 1991, or
      6    (at your option) version 3 dated 29 June, 2007.
      7 
      8    This program is distributed in the hope that it will be useful,
      9    but WITHOUT ANY WARRANTY; without even the implied warranty of
     10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11    GNU General Public License for more details.
     12 
     13    You should have received a copy of the GNU General Public License
     14    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15 */
     16 
     17 #include "dnsmasq.h"
     18 
     19 /* This file has code to fork a helper process which recieves data via a pipe
     20    shared with the main process and which is responsible for calling a script when
     21    DHCP leases change.
     22 
     23    The helper process is forked before the main process drops root, so it retains root
     24    privs to pass on to the script. For this reason it tries to be paranoid about
     25    data received from the main process, in case that has been compromised. We don't
     26    want the helper to give an attacker root. In particular, the script to be run is
     27    not settable via the pipe, once the fork has taken place it is not alterable by the
     28    main process.
     29 */
     30 
     31 #if defined(HAVE_DHCP) && defined(HAVE_SCRIPT)
     32 
     33 static void my_setenv(const char *name, const char *value, int *error);
     34 
     35 struct script_data
     36 {
     37   unsigned char action, hwaddr_len, hwaddr_type;
     38   unsigned char clid_len, hostname_len, uclass_len, vclass_len, shost_len;
     39   struct in_addr addr, giaddr;
     40   unsigned int remaining_time;
     41 #ifdef HAVE_BROKEN_RTC
     42   unsigned int length;
     43 #else
     44   time_t expires;
     45 #endif
     46   unsigned char hwaddr[DHCP_CHADDR_MAX];
     47   char interface[IF_NAMESIZE];
     48 };
     49 
     50 static struct script_data *buf = NULL;
     51 static size_t bytes_in_buf = 0, buf_size = 0;
     52 
     53 int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
     54 {
     55   pid_t pid;
     56   int i, pipefd[2];
     57   struct sigaction sigact;
     58 
     59   /* create the pipe through which the main program sends us commands,
     60      then fork our process. */
     61   if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1)
     62     {
     63       send_event(err_fd, EVENT_PIPE_ERR, errno);
     64       _exit(0);
     65     }
     66 
     67   if (pid != 0)
     68     {
     69       close(pipefd[0]); /* close reader side */
     70       return pipefd[1];
     71     }
     72 
     73   /* ignore SIGTERM, so that we can clean up when the main process gets hit
     74      and SIGALRM so that we can use sleep() */
     75   sigact.sa_handler = SIG_IGN;
     76   sigact.sa_flags = 0;
     77   sigemptyset(&sigact.sa_mask);
     78   sigaction(SIGTERM, &sigact, NULL);
     79   sigaction(SIGALRM, &sigact, NULL);
     80 
     81   if (!(daemon->options & OPT_DEBUG) && uid != 0)
     82     {
     83       gid_t dummy;
     84       if (setgroups(0, &dummy) == -1 ||
     85 	  setgid(gid) == -1 ||
     86 	  setuid(uid) == -1)
     87 	{
     88 	  if (daemon->options & OPT_NO_FORK)
     89 	    /* send error to daemon process if no-fork */
     90 	    send_event(event_fd, EVENT_HUSER_ERR, errno);
     91 	  else
     92 	    {
     93 	      /* kill daemon */
     94 	      send_event(event_fd, EVENT_DIE, 0);
     95 	      /* return error */
     96 	      send_event(err_fd, EVENT_HUSER_ERR, errno);
     97 	    }
     98 	  _exit(0);
     99 	}
    100     }
    101 
    102   /* close all the sockets etc, we don't need them here. This closes err_fd, so that
    103      main process can return. */
    104   for (max_fd--; max_fd >= 0; max_fd--)
    105     if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO &&
    106 	max_fd != STDIN_FILENO && max_fd != pipefd[0] && max_fd != event_fd)
    107       close(max_fd);
    108 
    109   /* loop here */
    110   while(1)
    111     {
    112       struct script_data data;
    113       char *p, *action_str, *hostname = NULL;
    114       unsigned char *buf = (unsigned char *)daemon->namebuff;
    115       int err = 0;
    116 
    117       /* we read zero bytes when pipe closed: this is our signal to exit */
    118       if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1))
    119 	_exit(0);
    120 
    121       if (data.action == ACTION_DEL)
    122 	action_str = "del";
    123       else if (data.action == ACTION_ADD)
    124 	action_str = "add";
    125       else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME)
    126 	action_str = "old";
    127       else
    128 	continue;
    129 
    130       /* stringify MAC into dhcp_buff */
    131       p = daemon->dhcp_buff;
    132       if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0)
    133         p += sprintf(p, "%.2x-", data.hwaddr_type);
    134       for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++)
    135         {
    136           p += sprintf(p, "%.2x", data.hwaddr[i]);
    137           if (i != data.hwaddr_len - 1)
    138             p += sprintf(p, ":");
    139         }
    140 
    141       /* and CLID into packet */
    142       if (!read_write(pipefd[0], buf, data.clid_len, 1))
    143 	continue;
    144       for (p = daemon->packet, i = 0; i < data.clid_len; i++)
    145 	{
    146 	  p += sprintf(p, "%.2x", buf[i]);
    147 	  if (i != data.clid_len - 1)
    148 	    p += sprintf(p, ":");
    149 	}
    150 
    151       /* and expiry or length into dhcp_buff2 */
    152 #ifdef HAVE_BROKEN_RTC
    153       sprintf(daemon->dhcp_buff2, "%u ", data.length);
    154 #else
    155       sprintf(daemon->dhcp_buff2, "%lu ", (unsigned long)data.expires);
    156 #endif
    157 
    158       if (!read_write(pipefd[0], buf,
    159 		      data.hostname_len + data.uclass_len + data.vclass_len + data.shost_len, 1))
    160 	continue;
    161 
    162       /* possible fork errors are all temporary resource problems */
    163       while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
    164 	sleep(2);
    165 
    166       if (pid == -1)
    167 	continue;
    168 
    169       /* wait for child to complete */
    170       if (pid != 0)
    171 	{
    172 	  /* reap our children's children, if necessary */
    173 	  while (1)
    174 	    {
    175 	      int status;
    176 	      pid_t rc = wait(&status);
    177 
    178 	      if (rc == pid)
    179 		{
    180 		  /* On error send event back to main process for logging */
    181 		  if (WIFSIGNALED(status))
    182 		    send_event(event_fd, EVENT_KILLED, WTERMSIG(status));
    183 		  else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
    184 		    send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status));
    185 		  break;
    186 		}
    187 
    188 	      if (rc == -1 && errno != EINTR)
    189 		break;
    190 	    }
    191 
    192 	  continue;
    193 	}
    194 
    195       if (data.clid_len != 0)
    196 	my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err);
    197 
    198       if (strlen(data.interface) != 0)
    199 	my_setenv("DNSMASQ_INTERFACE", data.interface, &err);
    200 
    201 #ifdef HAVE_BROKEN_RTC
    202       my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
    203 #else
    204       my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err);
    205 #endif
    206 
    207       if (data.vclass_len != 0)
    208 	{
    209 	  buf[data.vclass_len - 1] = 0; /* don't trust zero-term */
    210 	  /* cannot have = chars in env - truncate if found . */
    211 	  if ((p = strchr((char *)buf, '=')))
    212 	    *p = 0;
    213 	  my_setenv("DNSMASQ_VENDOR_CLASS", (char *)buf, &err);
    214 	  buf += data.vclass_len;
    215 	}
    216 
    217       if (data.uclass_len != 0)
    218 	{
    219 	  unsigned char *end = buf + data.uclass_len;
    220 	  buf[data.uclass_len - 1] = 0; /* don't trust zero-term */
    221 
    222 	  for (i = 0; buf < end;)
    223 	    {
    224 	      size_t len = strlen((char *)buf) + 1;
    225 	      if ((p = strchr((char *)buf, '=')))
    226 		*p = 0;
    227 	      if (strlen((char *)buf) != 0)
    228 		{
    229 		  sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i++);
    230 		  my_setenv(daemon->dhcp_buff2, (char *)buf, &err);
    231 		}
    232 	      buf += len;
    233 	    }
    234 	}
    235 
    236       if (data.shost_len != 0)
    237 	{
    238 	  buf[data.shost_len - 1] = 0; /* don't trust zero-term */
    239 	  /* cannot have = chars in env - truncate if found . */
    240 	  if ((p = strchr((char *)buf, '=')))
    241 	    *p = 0;
    242 	  my_setenv("DNSMASQ_SUPPLIED_HOSTNAME", (char *)buf, &err);
    243 	  buf += data.shost_len;
    244 	}
    245 
    246       if (data.giaddr.s_addr != 0)
    247 	my_setenv("DNSMASQ_RELAY_ADDRESS", inet_ntoa(data.giaddr), &err);
    248 
    249       sprintf(daemon->dhcp_buff2, "%u ", data.remaining_time);
    250       my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err);
    251 
    252       if (data.hostname_len != 0)
    253 	{
    254 	  char *dot;
    255 	  hostname = (char *)buf;
    256 	  hostname[data.hostname_len - 1] = 0;
    257 	  if (!legal_hostname(hostname))
    258 	    hostname = NULL;
    259 	  else if ((dot = strchr(hostname, '.')))
    260 	    {
    261 	      my_setenv("DNSMASQ_DOMAIN", dot+1, &err);
    262 	      *dot = 0;
    263 	    }
    264 	}
    265 
    266       if (data.action == ACTION_OLD_HOSTNAME && hostname)
    267 	{
    268 	  my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err);
    269 	  hostname = NULL;
    270 	}
    271 
    272       /* we need to have the event_fd around if exec fails */
    273       if ((i = fcntl(event_fd, F_GETFD)) != -1)
    274 	fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
    275       close(pipefd[0]);
    276 
    277       p =  strrchr(daemon->lease_change_command, '/');
    278       if (err == 0)
    279 	{
    280 	  execl(daemon->lease_change_command,
    281 		p ? p+1 : daemon->lease_change_command,
    282 		action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*)NULL);
    283 	  err = errno;
    284 	}
    285       /* failed, send event so the main process logs the problem */
    286       send_event(event_fd, EVENT_EXEC_ERR, err);
    287       _exit(0);
    288     }
    289 }
    290 
    291 static void my_setenv(const char *name, const char *value, int *error)
    292 {
    293   if (*error == 0 && setenv(name, value, 1) != 0)
    294     *error = errno;
    295 }
    296 
    297 /* pack up lease data into a buffer */
    298 void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
    299 {
    300   unsigned char *p;
    301   size_t size;
    302   unsigned int hostname_len = 0, clid_len = 0, vclass_len = 0;
    303   unsigned int uclass_len = 0, shost_len = 0;
    304 
    305   /* no script */
    306   if (daemon->helperfd == -1)
    307     return;
    308 
    309   if (lease->vendorclass)
    310     vclass_len = lease->vendorclass_len;
    311   if (lease->userclass)
    312     uclass_len = lease->userclass_len;
    313   if (lease->supplied_hostname)
    314     shost_len = lease->supplied_hostname_len;
    315   if (lease->clid)
    316     clid_len = lease->clid_len;
    317   if (hostname)
    318     hostname_len = strlen(hostname) + 1;
    319 
    320   size = sizeof(struct script_data) +  clid_len + vclass_len + uclass_len + shost_len + hostname_len;
    321 
    322   if (size > buf_size)
    323     {
    324       struct script_data *new;
    325 
    326       /* start with reasonable size, will almost never need extending. */
    327       if (size < sizeof(struct script_data) + 200)
    328 	size = sizeof(struct script_data) + 200;
    329 
    330       if (!(new = whine_malloc(size)))
    331 	return;
    332       if (buf)
    333 	free(buf);
    334       buf = new;
    335       buf_size = size;
    336     }
    337 
    338   buf->action = action;
    339   buf->hwaddr_len = lease->hwaddr_len;
    340   buf->hwaddr_type = lease->hwaddr_type;
    341   buf->clid_len = clid_len;
    342   buf->vclass_len = vclass_len;
    343   buf->uclass_len = uclass_len;
    344   buf->shost_len = shost_len;
    345   buf->hostname_len = hostname_len;
    346   buf->addr = lease->addr;
    347   buf->giaddr = lease->giaddr;
    348   memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len);
    349   buf->interface[0] = 0;
    350 #ifdef HAVE_LINUX_NETWORK
    351   if (lease->last_interface != 0)
    352     {
    353       struct ifreq ifr;
    354       ifr.ifr_ifindex = lease->last_interface;
    355       if (ioctl(daemon->dhcpfd, SIOCGIFNAME, &ifr) != -1)
    356 	strncpy(buf->interface, ifr.ifr_name, IF_NAMESIZE);
    357     }
    358 #else
    359   if (lease->last_interface != 0)
    360     if_indextoname(lease->last_interface, buf->interface);
    361 #endif
    362 
    363 #ifdef HAVE_BROKEN_RTC
    364   buf->length = lease->length;
    365 #else
    366   buf->expires = lease->expires;
    367 #endif
    368   buf->remaining_time = (unsigned int)difftime(lease->expires, now);
    369 
    370   p = (unsigned char *)(buf+1);
    371   if (clid_len != 0)
    372     {
    373       memcpy(p, lease->clid, clid_len);
    374       p += clid_len;
    375     }
    376   if (vclass_len != 0)
    377     {
    378       memcpy(p, lease->vendorclass, vclass_len);
    379       p += vclass_len;
    380     }
    381   if (uclass_len != 0)
    382     {
    383       memcpy(p, lease->userclass, uclass_len);
    384       p += uclass_len;
    385     }
    386   if (shost_len != 0)
    387     {
    388       memcpy(p, lease->supplied_hostname, shost_len);
    389       p += shost_len;
    390     }
    391   if (hostname_len != 0)
    392     {
    393       memcpy(p, hostname, hostname_len);
    394       p += hostname_len;
    395     }
    396 
    397   bytes_in_buf = p - (unsigned char *)buf;
    398 }
    399 
    400 int helper_buf_empty(void)
    401 {
    402   return bytes_in_buf == 0;
    403 }
    404 
    405 void helper_write(void)
    406 {
    407   ssize_t rc;
    408 
    409   if (bytes_in_buf == 0)
    410     return;
    411 
    412   if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1)
    413     {
    414       if (bytes_in_buf != (size_t)rc)
    415 	memmove(buf, buf + rc, bytes_in_buf - rc);
    416       bytes_in_buf -= rc;
    417     }
    418   else
    419     {
    420       if (errno == EAGAIN || errno == EINTR)
    421 	return;
    422       bytes_in_buf = 0;
    423     }
    424 }
    425 
    426 #endif
    427 
    428 
    429