blob: b992e78fdad7305f36c84e88a7c5d4f874b86809 [file] [log] [blame]
San Mehatffd68722010-01-20 09:56:15 -08001/* 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