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