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 unsigned char action, hwaddr_len, hwaddr_type;
37 unsigned char clid_len, hostname_len, uclass_len, vclass_len, shost_len;
38 struct in_addr addr, giaddr;
39 unsigned int remaining_time;
40 #ifdef HAVE_BROKEN_RTC
41 unsigned int length;
42 #else
43 time_t expires;
44 #endif
45 unsigned char hwaddr[DHCP_CHADDR_MAX];
46 char interface[IF_NAMESIZE];
47 };
48
49 static struct script_data* buf = NULL;
50 static size_t bytes_in_buf = 0, buf_size = 0;
51
create_helper(int event_fd,int err_fd,uid_t uid,gid_t gid,long max_fd)52 int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) {
53 pid_t pid;
54 int i, pipefd[2];
55 struct sigaction sigact;
56
57 /* create the pipe through which the main program sends us commands,
58 then fork our process. */
59 if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) {
60 send_event(err_fd, EVENT_PIPE_ERR, errno);
61 _exit(0);
62 }
63
64 if (pid != 0) {
65 close(pipefd[0]); /* close reader side */
66 return pipefd[1];
67 }
68
69 /* ignore SIGTERM, so that we can clean up when the main process gets hit
70 and SIGALRM so that we can use sleep() */
71 sigact.sa_handler = SIG_IGN;
72 sigact.sa_flags = 0;
73 sigemptyset(&sigact.sa_mask);
74 sigaction(SIGTERM, &sigact, NULL);
75 sigaction(SIGALRM, &sigact, NULL);
76
77 if (!(daemon->options & OPT_DEBUG) && uid != 0) {
78 gid_t dummy;
79 if (setgroups(0, &dummy) == -1 || setgid(gid) == -1 || setuid(uid) == -1) {
80 if (daemon->options & OPT_NO_FORK) /* send error to daemon process if no-fork */
81 send_event(event_fd, EVENT_HUSER_ERR, errno);
82 else {
83 /* kill daemon */
84 send_event(event_fd, EVENT_DIE, 0);
85 /* return error */
86 send_event(err_fd, EVENT_HUSER_ERR, errno);
87 }
88 _exit(0);
89 }
90 }
91
92 /* close all the sockets etc, we don't need them here. This closes err_fd, so that
93 main process can return. */
94 for (max_fd--; max_fd >= 0; max_fd--)
95 if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO &&
96 max_fd != pipefd[0] && max_fd != event_fd)
97 close(max_fd);
98
99 /* loop here */
100 while (1) {
101 struct script_data data;
102 char *p, *action_str, *hostname = NULL;
103 unsigned char* buf = (unsigned char*) daemon->namebuff;
104 int err = 0;
105
106 /* we read zero bytes when pipe closed: this is our signal to exit */
107 if (!read_write(pipefd[0], (unsigned char*) &data, sizeof(data), 1)) _exit(0);
108
109 if (data.action == ACTION_DEL)
110 action_str = "del";
111 else if (data.action == ACTION_ADD)
112 action_str = "add";
113 else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME)
114 action_str = "old";
115 else
116 continue;
117
118 /* stringify MAC into dhcp_buff */
119 p = daemon->dhcp_buff;
120 if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0)
121 p += sprintf(p, "%.2x-", data.hwaddr_type);
122 for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++) {
123 p += sprintf(p, "%.2x", data.hwaddr[i]);
124 if (i != data.hwaddr_len - 1) p += sprintf(p, ":");
125 }
126
127 /* and CLID into packet */
128 if (!read_write(pipefd[0], buf, data.clid_len, 1)) continue;
129 for (p = daemon->packet, i = 0; i < data.clid_len; i++) {
130 p += sprintf(p, "%.2x", buf[i]);
131 if (i != data.clid_len - 1) p += sprintf(p, ":");
132 }
133
134 /* and expiry or length into dhcp_buff2 */
135 #ifdef HAVE_BROKEN_RTC
136 sprintf(daemon->dhcp_buff2, "%u ", data.length);
137 #else
138 sprintf(daemon->dhcp_buff2, "%lu ", (unsigned long) data.expires);
139 #endif
140
141 if (!read_write(pipefd[0], buf,
142 data.hostname_len + data.uclass_len + data.vclass_len + data.shost_len, 1))
143 continue;
144
145 /* possible fork errors are all temporary resource problems */
146 while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM)) sleep(2);
147
148 if (pid == -1) continue;
149
150 /* wait for child to complete */
151 if (pid != 0) {
152 /* reap our children's children, if necessary */
153 while (1) {
154 int status;
155 pid_t rc = wait(&status);
156
157 if (rc == pid) {
158 /* On error send event back to main process for logging */
159 if (WIFSIGNALED(status))
160 send_event(event_fd, EVENT_KILLED, WTERMSIG(status));
161 else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
162 send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status));
163 break;
164 }
165
166 if (rc == -1 && errno != EINTR) break;
167 }
168
169 continue;
170 }
171
172 if (data.clid_len != 0) my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err);
173
174 if (strlen(data.interface) != 0) my_setenv("DNSMASQ_INTERFACE", data.interface, &err);
175
176 #ifdef HAVE_BROKEN_RTC
177 my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
178 #else
179 my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err);
180 #endif
181
182 if (data.vclass_len != 0) {
183 buf[data.vclass_len - 1] = 0; /* don't trust zero-term */
184 /* cannot have = chars in env - truncate if found . */
185 if ((p = strchr((char*) buf, '='))) *p = 0;
186 my_setenv("DNSMASQ_VENDOR_CLASS", (char*) buf, &err);
187 buf += data.vclass_len;
188 }
189
190 if (data.uclass_len != 0) {
191 unsigned char* end = buf + data.uclass_len;
192 buf[data.uclass_len - 1] = 0; /* don't trust zero-term */
193
194 for (i = 0; buf < end;) {
195 size_t len = strlen((char*) buf) + 1;
196 if ((p = strchr((char*) buf, '='))) *p = 0;
197 if (strlen((char*) buf) != 0) {
198 sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i++);
199 my_setenv(daemon->dhcp_buff2, (char*) buf, &err);
200 }
201 buf += len;
202 }
203 }
204
205 if (data.shost_len != 0) {
206 buf[data.shost_len - 1] = 0; /* don't trust zero-term */
207 /* cannot have = chars in env - truncate if found . */
208 if ((p = strchr((char*) buf, '='))) *p = 0;
209 my_setenv("DNSMASQ_SUPPLIED_HOSTNAME", (char*) buf, &err);
210 buf += data.shost_len;
211 }
212
213 if (data.giaddr.s_addr != 0)
214 my_setenv("DNSMASQ_RELAY_ADDRESS", inet_ntoa(data.giaddr), &err);
215
216 sprintf(daemon->dhcp_buff2, "%u ", data.remaining_time);
217 my_setenv("DNSMASQ_TIME_REMAINING", daemon->dhcp_buff2, &err);
218
219 if (data.hostname_len != 0) {
220 char* dot;
221 hostname = (char*) buf;
222 hostname[data.hostname_len - 1] = 0;
223 if (!legal_hostname(hostname))
224 hostname = NULL;
225 else if ((dot = strchr(hostname, '.'))) {
226 my_setenv("DNSMASQ_DOMAIN", dot + 1, &err);
227 *dot = 0;
228 }
229 }
230
231 if (data.action == ACTION_OLD_HOSTNAME && hostname) {
232 my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err);
233 hostname = NULL;
234 }
235
236 /* we need to have the event_fd around if exec fails */
237 if ((i = fcntl(event_fd, F_GETFD)) != -1) fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
238 close(pipefd[0]);
239
240 p = strrchr(daemon->lease_change_command, '/');
241 if (err == 0) {
242 execl(daemon->lease_change_command, p ? p + 1 : daemon->lease_change_command,
243 action_str, daemon->dhcp_buff, inet_ntoa(data.addr), hostname, (char*) NULL);
244 err = errno;
245 }
246 /* failed, send event so the main process logs the problem */
247 send_event(event_fd, EVENT_EXEC_ERR, err);
248 _exit(0);
249 }
250 }
251
my_setenv(const char * name,const char * value,int * error)252 static void my_setenv(const char* name, const char* value, int* error) {
253 if (*error == 0 && setenv(name, value, 1) != 0) *error = errno;
254 }
255
256 /* pack up lease data into a buffer */
queue_script(int action,struct dhcp_lease * lease,char * hostname,time_t now)257 void queue_script(int action, struct dhcp_lease* lease, char* hostname, time_t now) {
258 unsigned char* p;
259 size_t size;
260 unsigned int hostname_len = 0, clid_len = 0, vclass_len = 0;
261 unsigned int uclass_len = 0, shost_len = 0;
262
263 /* no script */
264 if (daemon->helperfd == -1) return;
265
266 if (lease->vendorclass) vclass_len = lease->vendorclass_len;
267 if (lease->userclass) uclass_len = lease->userclass_len;
268 if (lease->supplied_hostname) shost_len = lease->supplied_hostname_len;
269 if (lease->clid) clid_len = lease->clid_len;
270 if (hostname) hostname_len = strlen(hostname) + 1;
271
272 size =
273 sizeof(struct script_data) + clid_len + vclass_len + uclass_len + shost_len + hostname_len;
274
275 if (size > buf_size) {
276 struct script_data* new;
277
278 /* start with reasonable size, will almost never need extending. */
279 if (size < sizeof(struct script_data) + 200) size = sizeof(struct script_data) + 200;
280
281 if (!(new = whine_malloc(size))) return;
282 if (buf) free(buf);
283 buf = new;
284 buf_size = size;
285 }
286
287 buf->action = action;
288 buf->hwaddr_len = lease->hwaddr_len;
289 buf->hwaddr_type = lease->hwaddr_type;
290 buf->clid_len = clid_len;
291 buf->vclass_len = vclass_len;
292 buf->uclass_len = uclass_len;
293 buf->shost_len = shost_len;
294 buf->hostname_len = hostname_len;
295 buf->addr = lease->addr;
296 buf->giaddr = lease->giaddr;
297 memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len);
298 buf->interface[0] = 0;
299 #ifdef HAVE_LINUX_NETWORK
300 if (lease->last_interface != 0) {
301 struct ifreq ifr;
302 ifr.ifr_ifindex = lease->last_interface;
303 if (ioctl(daemon->dhcpfd, SIOCGIFNAME, &ifr) != -1)
304 strncpy(buf->interface, ifr.ifr_name, IF_NAMESIZE);
305 }
306 #else
307 if (lease->last_interface != 0) if_indextoname(lease->last_interface, buf->interface);
308 #endif
309
310 #ifdef HAVE_BROKEN_RTC
311 buf->length = lease->length;
312 #else
313 buf->expires = lease->expires;
314 #endif
315 buf->remaining_time = (unsigned int) difftime(lease->expires, now);
316
317 p = (unsigned char*) (buf + 1);
318 if (clid_len != 0) {
319 memcpy(p, lease->clid, clid_len);
320 p += clid_len;
321 }
322 if (vclass_len != 0) {
323 memcpy(p, lease->vendorclass, vclass_len);
324 p += vclass_len;
325 }
326 if (uclass_len != 0) {
327 memcpy(p, lease->userclass, uclass_len);
328 p += uclass_len;
329 }
330 if (shost_len != 0) {
331 memcpy(p, lease->supplied_hostname, shost_len);
332 p += shost_len;
333 }
334 if (hostname_len != 0) {
335 memcpy(p, hostname, hostname_len);
336 p += hostname_len;
337 }
338
339 bytes_in_buf = p - (unsigned char*) buf;
340 }
341
helper_buf_empty(void)342 int helper_buf_empty(void) {
343 return bytes_in_buf == 0;
344 }
345
helper_write(void)346 void helper_write(void) {
347 ssize_t rc;
348
349 if (bytes_in_buf == 0) return;
350
351 if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1) {
352 if (bytes_in_buf != (size_t) rc) memmove(buf, buf + rc, bytes_in_buf - rc);
353 bytes_in_buf -= rc;
354 } else {
355 if (errno == EAGAIN || errno == EINTR) return;
356 bytes_in_buf = 0;
357 }
358 }
359
360 #endif
361