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 
create_helper(int event_fd,int err_fd,uid_t uid,gid_t gid,long max_fd)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 
my_setenv(const char * name,const char * value,int * error)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 */
queue_script(int action,struct dhcp_lease * lease,char * hostname,time_t now)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 
helper_buf_empty(void)400 int helper_buf_empty(void)
401 {
402   return bytes_in_buf == 0;
403 }
404 
helper_write(void)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