blob: b1315017459ab555514c24bda009ade74cf2b7ac [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
248 sprintf(daemon->dhcp_buff2, "%u", data.remaining_time);
249 my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err);
Simon Kelley16972692006-10-16 20:04:18 +0100250
251 if (data.action == ACTION_OLD_HOSTNAME && hostname)
252 {
Simon Kelley824af852008-02-12 20:43:05 +0000253 my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err);
Simon Kelley16972692006-10-16 20:04:18 +0100254 hostname = NULL;
255 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100256
257 /* we need to have the event_fd around if exec fails */
258 if ((i = fcntl(event_fd, F_GETFD)) != -1)
259 fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
260 close(pipefd[0]);
261
Simon Kelley16972692006-10-16 20:04:18 +0100262 p = strrchr(daemon->lease_change_command, '/');
Simon Kelley824af852008-02-12 20:43:05 +0000263 if (err == 0)
264 {
265 execl(daemon->lease_change_command,
266 p ? p+1 : daemon->lease_change_command,
267 action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*)NULL);
268 err = errno;
269 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100270 /* failed, send event so the main process logs the problem */
Simon Kelley824af852008-02-12 20:43:05 +0000271 send_event(event_fd, EVENT_EXEC_ERR, err);
Simon Kelley16972692006-10-16 20:04:18 +0100272 _exit(0);
273 }
274}
275
Simon Kelley824af852008-02-12 20:43:05 +0000276static void my_setenv(const char *name, const char *value, int *error)
277{
Simon Kelley7622fc02009-06-04 20:32:05 +0100278 if (*error == 0 && setenv(name, value, 1) != 0)
279 *error = errno;
Simon Kelley824af852008-02-12 20:43:05 +0000280}
281
Simon Kelley316e2732010-01-22 20:16:09 +0000282static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err)
283{
284 unsigned char *next;
285
286 if (!buf || (buf == end))
287 return NULL;
288
289 for (next = buf; *next != 0; next++)
290 if (next == end)
291 return NULL;
292
293 if (next != buf)
294 {
295 char *p;
296 /* No "=" in value */
297 if ((p = strchr((char *)buf, '=')))
298 *p = 0;
299 my_setenv(env, (char *)buf, err);
300 }
301
302 return next + 1;
303}
304
Simon Kelley16972692006-10-16 20:04:18 +0100305/* pack up lease data into a buffer */
Simon Kelley5aabfc72007-08-29 11:24:47 +0100306void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
Simon Kelley16972692006-10-16 20:04:18 +0100307{
308 unsigned char *p;
309 size_t size;
Simon Kelley316e2732010-01-22 20:16:09 +0000310 unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
Simon Kelley9009d742008-11-14 20:04:27 +0000311
Simon Kelley16972692006-10-16 20:04:18 +0100312 /* no script */
313 if (daemon->helperfd == -1)
314 return;
315
Simon Kelley316e2732010-01-22 20:16:09 +0000316 if (lease->extradata)
317 ed_len = lease->extradata_len;
Simon Kelley16972692006-10-16 20:04:18 +0100318 if (lease->clid)
319 clid_len = lease->clid_len;
320 if (hostname)
321 hostname_len = strlen(hostname) + 1;
322
Simon Kelley316e2732010-01-22 20:16:09 +0000323 size = sizeof(struct script_data) + clid_len + ed_len + hostname_len;
Simon Kelley16972692006-10-16 20:04:18 +0100324
325 if (size > buf_size)
326 {
327 struct script_data *new;
328
Simon Kelley9009d742008-11-14 20:04:27 +0000329 /* start with reasonable size, will almost never need extending. */
Simon Kelley16972692006-10-16 20:04:18 +0100330 if (size < sizeof(struct script_data) + 200)
331 size = sizeof(struct script_data) + 200;
332
Simon Kelley5aabfc72007-08-29 11:24:47 +0100333 if (!(new = whine_malloc(size)))
Simon Kelley16972692006-10-16 20:04:18 +0100334 return;
335 if (buf)
336 free(buf);
337 buf = new;
338 buf_size = size;
339 }
340
341 buf->action = action;
342 buf->hwaddr_len = lease->hwaddr_len;
343 buf->hwaddr_type = lease->hwaddr_type;
344 buf->clid_len = clid_len;
Simon Kelley316e2732010-01-22 20:16:09 +0000345 buf->ed_len = ed_len;
Simon Kelley16972692006-10-16 20:04:18 +0100346 buf->hostname_len = hostname_len;
347 buf->addr = lease->addr;
Simon Kelley1f15b812009-10-13 17:49:32 +0100348 buf->giaddr = lease->giaddr;
Simon Kelley16972692006-10-16 20:04:18 +0100349 memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len);
Simon Kelley316e2732010-01-22 20:16:09 +0000350 if (!indextoname(daemon->dhcpfd, lease->last_interface, buf->interface))
351 buf->interface[0] = 0;
Simon Kelley824af852008-02-12 20:43:05 +0000352
Simon Kelley16972692006-10-16 20:04:18 +0100353#ifdef HAVE_BROKEN_RTC
354 buf->length = lease->length;
355#else
356 buf->expires = lease->expires;
357#endif
Simon Kelley5aabfc72007-08-29 11:24:47 +0100358 buf->remaining_time = (unsigned int)difftime(lease->expires, now);
359
Simon Kelley16972692006-10-16 20:04:18 +0100360 p = (unsigned char *)(buf+1);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100361 if (clid_len != 0)
Simon Kelley16972692006-10-16 20:04:18 +0100362 {
363 memcpy(p, lease->clid, clid_len);
364 p += clid_len;
365 }
Simon Kelley1f15b812009-10-13 17:49:32 +0100366 if (hostname_len != 0)
367 {
368 memcpy(p, hostname, hostname_len);
369 p += hostname_len;
370 }
Simon Kelley316e2732010-01-22 20:16:09 +0000371 if (ed_len != 0)
372 {
373 memcpy(p, lease->extradata, ed_len);
374 p += ed_len;
375 }
Simon Kelley16972692006-10-16 20:04:18 +0100376 bytes_in_buf = p - (unsigned char *)buf;
377}
378
379int helper_buf_empty(void)
380{
381 return bytes_in_buf == 0;
382}
383
Simon Kelley5aabfc72007-08-29 11:24:47 +0100384void helper_write(void)
Simon Kelley16972692006-10-16 20:04:18 +0100385{
386 ssize_t rc;
387
388 if (bytes_in_buf == 0)
389 return;
390
391 if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1)
392 {
393 if (bytes_in_buf != (size_t)rc)
394 memmove(buf, buf + rc, bytes_in_buf - rc);
395 bytes_in_buf -= rc;
396 }
397 else
398 {
399 if (errno == EAGAIN || errno == EINTR)
400 return;
401 bytes_in_buf = 0;
402 }
403}
404
Simon Kelley5aabfc72007-08-29 11:24:47 +0100405#endif
Simon Kelley16972692006-10-16 20:04:18 +0100406
407