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
33static void my_setenv(const char *name, const char *value, int *error);
34
35struct 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
50static struct script_data *buf = NULL;
51static size_t bytes_in_buf = 0, buf_size = 0;
52
53int 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
291static 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 */
298void 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
400int helper_buf_empty(void)
401{
402  return bytes_in_buf == 0;
403}
404
405void 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