blob: efc0ab3f7b691f4646f5b247ea23e382c413cdcd [file] [log] [blame]
Simon Kelley316e2732010-01-22 20:16:09 +00001/* dnsmasq is Copyright (c) 2000-2010 Simon Kelley
Simon Kelley16972692006-10-16 20:04:18 +01002
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
Simon Kelley824af852008-02-12 20:43:05 +00005 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
7
Simon Kelley16972692006-10-16 20:04:18 +01008 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.
Simon Kelley824af852008-02-12 20:43:05 +000012
Simon Kelley73a08a22009-02-05 20:28:08 +000013 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/>.
Simon Kelley16972692006-10-16 20:04:18 +010015*/
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
Simon Kelley1f15b812009-10-13 17:49:32 +010031#if defined(HAVE_DHCP) && defined(HAVE_SCRIPT)
Simon Kelley5aabfc72007-08-29 11:24:47 +010032
Simon Kelley824af852008-02-12 20:43:05 +000033static void my_setenv(const char *name, const char *value, int *error);
Simon Kelley316e2732010-01-22 20:16:09 +000034static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err);
Simon Kelley9009d742008-11-14 20:04:27 +000035
Simon Kelley16972692006-10-16 20:04:18 +010036struct script_data
37{
38 unsigned char action, hwaddr_len, hwaddr_type;
Simon Kelley316e2732010-01-22 20:16:09 +000039 unsigned char clid_len, hostname_len, ed_len;
Simon Kelley1f15b812009-10-13 17:49:32 +010040 struct in_addr addr, giaddr;
Simon Kelley5aabfc72007-08-29 11:24:47 +010041 unsigned int remaining_time;
Simon Kelley16972692006-10-16 20:04:18 +010042#ifdef HAVE_BROKEN_RTC
43 unsigned int length;
44#else
45 time_t expires;
46#endif
47 unsigned char hwaddr[DHCP_CHADDR_MAX];
Simon Kelley824af852008-02-12 20:43:05 +000048 char interface[IF_NAMESIZE];
Simon Kelley16972692006-10-16 20:04:18 +010049};
50
Simon Kelley5aabfc72007-08-29 11:24:47 +010051static struct script_data *buf = NULL;
52static size_t bytes_in_buf = 0, buf_size = 0;
Simon Kelley16972692006-10-16 20:04:18 +010053
Simon Kelley1a6bca82008-07-11 11:11:42 +010054int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
Simon Kelley16972692006-10-16 20:04:18 +010055{
56 pid_t pid;
57 int i, pipefd[2];
58 struct sigaction sigact;
Simon Kelley9e038942008-05-30 20:06:34 +010059
Simon Kelley5aabfc72007-08-29 11:24:47 +010060 /* create the pipe through which the main program sends us commands,
Simon Kelley1a6bca82008-07-11 11:11:42 +010061 then fork our process. */
Simon Kelley5aabfc72007-08-29 11:24:47 +010062 if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1)
63 {
Simon Kelley1a6bca82008-07-11 11:11:42 +010064 send_event(err_fd, EVENT_PIPE_ERR, errno);
65 _exit(0);
Simon Kelley5aabfc72007-08-29 11:24:47 +010066 }
67
Simon Kelley16972692006-10-16 20:04:18 +010068 if (pid != 0)
69 {
70 close(pipefd[0]); /* close reader side */
71 return pipefd[1];
72 }
73
Simon Kelley5aabfc72007-08-29 11:24:47 +010074 /* ignore SIGTERM, so that we can clean up when the main process gets hit
75 and SIGALRM so that we can use sleep() */
Simon Kelley16972692006-10-16 20:04:18 +010076 sigact.sa_handler = SIG_IGN;
77 sigact.sa_flags = 0;
78 sigemptyset(&sigact.sa_mask);
79 sigaction(SIGTERM, &sigact, NULL);
Simon Kelley5aabfc72007-08-29 11:24:47 +010080 sigaction(SIGALRM, &sigact, NULL);
Simon Kelley16972692006-10-16 20:04:18 +010081
Simon Kelley1a6bca82008-07-11 11:11:42 +010082 if (!(daemon->options & OPT_DEBUG) && uid != 0)
83 {
84 gid_t dummy;
85 if (setgroups(0, &dummy) == -1 ||
86 setgid(gid) == -1 ||
87 setuid(uid) == -1)
88 {
89 if (daemon->options & OPT_NO_FORK)
90 /* send error to daemon process if no-fork */
91 send_event(event_fd, EVENT_HUSER_ERR, errno);
92 else
93 {
94 /* kill daemon */
95 send_event(event_fd, EVENT_DIE, 0);
96 /* return error */
Simon Kelley73a08a22009-02-05 20:28:08 +000097 send_event(err_fd, EVENT_HUSER_ERR, errno);
Simon Kelley1a6bca82008-07-11 11:11:42 +010098 }
99 _exit(0);
100 }
101 }
102
103 /* close all the sockets etc, we don't need them here. This closes err_fd, so that
104 main process can return. */
Simon Kelley1f15b812009-10-13 17:49:32 +0100105 for (max_fd--; max_fd >= 0; max_fd--)
Simon Kelley5aabfc72007-08-29 11:24:47 +0100106 if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO &&
107 max_fd != STDIN_FILENO && max_fd != pipefd[0] && max_fd != event_fd)
108 close(max_fd);
Simon Kelley16972692006-10-16 20:04:18 +0100109
Simon Kelley16972692006-10-16 20:04:18 +0100110 /* loop here */
111 while(1)
112 {
113 struct script_data data;
114 char *p, *action_str, *hostname = NULL;
115 unsigned char *buf = (unsigned char *)daemon->namebuff;
Simon Kelley316e2732010-01-22 20:16:09 +0000116 unsigned char *end, *alloc_buff = NULL;
Simon Kelley824af852008-02-12 20:43:05 +0000117 int err = 0;
Simon Kelley16972692006-10-16 20:04:18 +0100118
119 /* we read zero bytes when pipe closed: this is our signal to exit */
120 if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1))
121 _exit(0);
122
123 if (data.action == ACTION_DEL)
124 action_str = "del";
125 else if (data.action == ACTION_ADD)
126 action_str = "add";
127 else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME)
128 action_str = "old";
129 else
130 continue;
131
132 /* stringify MAC into dhcp_buff */
133 p = daemon->dhcp_buff;
134 if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0)
Simon Kelley9009d742008-11-14 20:04:27 +0000135 p += sprintf(p, "%.2x-", data.hwaddr_type);
Simon Kelley16972692006-10-16 20:04:18 +0100136 for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++)
Simon Kelley9009d742008-11-14 20:04:27 +0000137 {
138 p += sprintf(p, "%.2x", data.hwaddr[i]);
139 if (i != data.hwaddr_len - 1)
140 p += sprintf(p, ":");
141 }
Simon Kelley16972692006-10-16 20:04:18 +0100142
Simon Kelley316e2732010-01-22 20:16:09 +0000143 /* and CLID into packet, avoid overwrite from bad data */
144 if ((data.clid_len > daemon->packet_buff_sz) || !read_write(pipefd[0], buf, data.clid_len, 1))
Simon Kelley16972692006-10-16 20:04:18 +0100145 continue;
146 for (p = daemon->packet, i = 0; i < data.clid_len; i++)
147 {
148 p += sprintf(p, "%.2x", buf[i]);
149 if (i != data.clid_len - 1)
150 p += sprintf(p, ":");
151 }
152
153 /* and expiry or length into dhcp_buff2 */
154#ifdef HAVE_BROKEN_RTC
Simon Kelley316e2732010-01-22 20:16:09 +0000155 sprintf(daemon->dhcp_buff2, "%u", data.length);
Simon Kelley16972692006-10-16 20:04:18 +0100156#else
Simon Kelley316e2732010-01-22 20:16:09 +0000157 sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires);
Simon Kelley16972692006-10-16 20:04:18 +0100158#endif
159
Simon Kelley316e2732010-01-22 20:16:09 +0000160 /* supplied data may just exceed normal buffer (unlikely) */
161 if ((data.hostname_len + data.ed_len) > daemon->packet_buff_sz &&
162 !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len)))
163 continue;
164
Simon Kelley1f15b812009-10-13 17:49:32 +0100165 if (!read_write(pipefd[0], buf,
Simon Kelley316e2732010-01-22 20:16:09 +0000166 data.hostname_len + data.ed_len, 1))
Simon Kelley16972692006-10-16 20:04:18 +0100167 continue;
168
Simon Kelley5aabfc72007-08-29 11:24:47 +0100169 /* possible fork errors are all temporary resource problems */
170 while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
171 sleep(2);
Simon Kelley316e2732010-01-22 20:16:09 +0000172
173 free(alloc_buff);
Simon Kelley16972692006-10-16 20:04:18 +0100174
Simon Kelley5aabfc72007-08-29 11:24:47 +0100175 if (pid == -1)
176 continue;
177
Simon Kelley16972692006-10-16 20:04:18 +0100178 /* wait for child to complete */
179 if (pid != 0)
180 {
Simon Kelley5aabfc72007-08-29 11:24:47 +0100181 /* reap our children's children, if necessary */
182 while (1)
183 {
184 int status;
185 pid_t rc = wait(&status);
186
187 if (rc == pid)
188 {
189 /* On error send event back to main process for logging */
190 if (WIFSIGNALED(status))
191 send_event(event_fd, EVENT_KILLED, WTERMSIG(status));
192 else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
193 send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status));
194 break;
195 }
196
197 if (rc == -1 && errno != EINTR)
198 break;
199 }
200
Simon Kelley16972692006-10-16 20:04:18 +0100201 continue;
202 }
203
204 if (data.clid_len != 0)
Simon Kelley824af852008-02-12 20:43:05 +0000205 my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err);
206
207 if (strlen(data.interface) != 0)
208 my_setenv("DNSMASQ_INTERFACE", data.interface, &err);
209
Simon Kelley16972692006-10-16 20:04:18 +0100210#ifdef HAVE_BROKEN_RTC
Simon Kelley824af852008-02-12 20:43:05 +0000211 my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
Simon Kelley16972692006-10-16 20:04:18 +0100212#else
Simon Kelley824af852008-02-12 20:43:05 +0000213 my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err);
Simon Kelley16972692006-10-16 20:04:18 +0100214#endif
215
Simon Kelley16972692006-10-16 20:04:18 +0100216 if (data.hostname_len != 0)
217 {
Simon Kelley9009d742008-11-14 20:04:27 +0000218 char *dot;
Simon Kelley16972692006-10-16 20:04:18 +0100219 hostname = (char *)buf;
220 hostname[data.hostname_len - 1] = 0;
Simon Kelley1f15b812009-10-13 17:49:32 +0100221 if (!legal_hostname(hostname))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100222 hostname = NULL;
Simon Kelley9009d742008-11-14 20:04:27 +0000223 else if ((dot = strchr(hostname, '.')))
224 {
225 my_setenv("DNSMASQ_DOMAIN", dot+1, &err);
226 *dot = 0;
Simon Kelley316e2732010-01-22 20:16:09 +0000227 }
228 buf += data.hostname_len;
Simon Kelley16972692006-10-16 20:04:18 +0100229 }
Simon Kelley316e2732010-01-22 20:16:09 +0000230
231 end = buf + data.ed_len;
232 buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err);
233 buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err);
234 buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err);
235 buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err);
236 buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err);
237 buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err);
238
239 for (i = 0; buf; i++)
240 {
241 sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i);
242 buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
243 }
244
245 if (data.giaddr.s_addr != 0)
246 my_setenv("DNSMASQ_RELAY_ADDRESS", inet_ntoa(data.giaddr), &err);
247
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100248 if (data.action != ACTION_DEL)
249 {
250 sprintf(daemon->dhcp_buff2, "%u", data.remaining_time);
251 my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err);
252 }
253
Simon Kelley16972692006-10-16 20:04:18 +0100254 if (data.action == ACTION_OLD_HOSTNAME && hostname)
255 {
Simon Kelley824af852008-02-12 20:43:05 +0000256 my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err);
Simon Kelley16972692006-10-16 20:04:18 +0100257 hostname = NULL;
258 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100259
260 /* we need to have the event_fd around if exec fails */
261 if ((i = fcntl(event_fd, F_GETFD)) != -1)
262 fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
263 close(pipefd[0]);
264
Simon Kelley16972692006-10-16 20:04:18 +0100265 p = strrchr(daemon->lease_change_command, '/');
Simon Kelley824af852008-02-12 20:43:05 +0000266 if (err == 0)
267 {
268 execl(daemon->lease_change_command,
269 p ? p+1 : daemon->lease_change_command,
270 action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*)NULL);
271 err = errno;
272 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100273 /* failed, send event so the main process logs the problem */
Simon Kelley824af852008-02-12 20:43:05 +0000274 send_event(event_fd, EVENT_EXEC_ERR, err);
Simon Kelley16972692006-10-16 20:04:18 +0100275 _exit(0);
276 }
277}
278
Simon Kelley824af852008-02-12 20:43:05 +0000279static void my_setenv(const char *name, const char *value, int *error)
280{
Simon Kelley7622fc02009-06-04 20:32:05 +0100281 if (*error == 0 && setenv(name, value, 1) != 0)
282 *error = errno;
Simon Kelley824af852008-02-12 20:43:05 +0000283}
284
Simon Kelley316e2732010-01-22 20:16:09 +0000285static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err)
286{
287 unsigned char *next;
288
289 if (!buf || (buf == end))
290 return NULL;
291
292 for (next = buf; *next != 0; next++)
293 if (next == end)
294 return NULL;
295
296 if (next != buf)
297 {
298 char *p;
299 /* No "=" in value */
300 if ((p = strchr((char *)buf, '=')))
301 *p = 0;
302 my_setenv(env, (char *)buf, err);
303 }
304
305 return next + 1;
306}
307
Simon Kelley16972692006-10-16 20:04:18 +0100308/* pack up lease data into a buffer */
Simon Kelley5aabfc72007-08-29 11:24:47 +0100309void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
Simon Kelley16972692006-10-16 20:04:18 +0100310{
311 unsigned char *p;
312 size_t size;
Simon Kelley316e2732010-01-22 20:16:09 +0000313 unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
Simon Kelley9009d742008-11-14 20:04:27 +0000314
Simon Kelley16972692006-10-16 20:04:18 +0100315 /* no script */
316 if (daemon->helperfd == -1)
317 return;
318
Simon Kelley316e2732010-01-22 20:16:09 +0000319 if (lease->extradata)
320 ed_len = lease->extradata_len;
Simon Kelley16972692006-10-16 20:04:18 +0100321 if (lease->clid)
322 clid_len = lease->clid_len;
323 if (hostname)
324 hostname_len = strlen(hostname) + 1;
325
Simon Kelley316e2732010-01-22 20:16:09 +0000326 size = sizeof(struct script_data) + clid_len + ed_len + hostname_len;
Simon Kelley16972692006-10-16 20:04:18 +0100327
328 if (size > buf_size)
329 {
330 struct script_data *new;
331
Simon Kelley9009d742008-11-14 20:04:27 +0000332 /* start with reasonable size, will almost never need extending. */
Simon Kelley16972692006-10-16 20:04:18 +0100333 if (size < sizeof(struct script_data) + 200)
334 size = sizeof(struct script_data) + 200;
335
Simon Kelley5aabfc72007-08-29 11:24:47 +0100336 if (!(new = whine_malloc(size)))
Simon Kelley16972692006-10-16 20:04:18 +0100337 return;
338 if (buf)
339 free(buf);
340 buf = new;
341 buf_size = size;
342 }
343
344 buf->action = action;
345 buf->hwaddr_len = lease->hwaddr_len;
346 buf->hwaddr_type = lease->hwaddr_type;
347 buf->clid_len = clid_len;
Simon Kelley316e2732010-01-22 20:16:09 +0000348 buf->ed_len = ed_len;
Simon Kelley16972692006-10-16 20:04:18 +0100349 buf->hostname_len = hostname_len;
350 buf->addr = lease->addr;
Simon Kelley1f15b812009-10-13 17:49:32 +0100351 buf->giaddr = lease->giaddr;
Simon Kelley16972692006-10-16 20:04:18 +0100352 memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len);
Simon Kelley316e2732010-01-22 20:16:09 +0000353 if (!indextoname(daemon->dhcpfd, lease->last_interface, buf->interface))
354 buf->interface[0] = 0;
Simon Kelley824af852008-02-12 20:43:05 +0000355
Simon Kelley16972692006-10-16 20:04:18 +0100356#ifdef HAVE_BROKEN_RTC
357 buf->length = lease->length;
358#else
359 buf->expires = lease->expires;
360#endif
Simon Kelley5aabfc72007-08-29 11:24:47 +0100361 buf->remaining_time = (unsigned int)difftime(lease->expires, now);
362
Simon Kelley16972692006-10-16 20:04:18 +0100363 p = (unsigned char *)(buf+1);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100364 if (clid_len != 0)
Simon Kelley16972692006-10-16 20:04:18 +0100365 {
366 memcpy(p, lease->clid, clid_len);
367 p += clid_len;
368 }
Simon Kelley1f15b812009-10-13 17:49:32 +0100369 if (hostname_len != 0)
370 {
371 memcpy(p, hostname, hostname_len);
372 p += hostname_len;
373 }
Simon Kelley316e2732010-01-22 20:16:09 +0000374 if (ed_len != 0)
375 {
376 memcpy(p, lease->extradata, ed_len);
377 p += ed_len;
378 }
Simon Kelley16972692006-10-16 20:04:18 +0100379 bytes_in_buf = p - (unsigned char *)buf;
380}
381
382int helper_buf_empty(void)
383{
384 return bytes_in_buf == 0;
385}
386
Simon Kelley5aabfc72007-08-29 11:24:47 +0100387void helper_write(void)
Simon Kelley16972692006-10-16 20:04:18 +0100388{
389 ssize_t rc;
390
391 if (bytes_in_buf == 0)
392 return;
393
394 if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1)
395 {
396 if (bytes_in_buf != (size_t)rc)
397 memmove(buf, buf + rc, bytes_in_buf - rc);
398 bytes_in_buf -= rc;
399 }
400 else
401 {
402 if (errno == EAGAIN || errno == EINTR)
403 return;
404 bytes_in_buf = 0;
405 }
406}
407
Simon Kelley5aabfc72007-08-29 11:24:47 +0100408#endif
Simon Kelley16972692006-10-16 20:04:18 +0100409
410