Source code upload
[framework/connectivity/dnsmasq.git] / src / helper.c
1 /* dnsmasq is Copyright (c) 2000-2011 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 static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end,  char *env, int *err);
35
36 struct script_data
37 {
38   unsigned char action, hwaddr_len, hwaddr_type;
39   unsigned char clid_len, hostname_len, ed_len;
40   struct in_addr addr, giaddr;
41   unsigned int remaining_time;
42 #ifdef HAVE_BROKEN_RTC
43   unsigned int length;
44 #else
45   time_t expires;
46 #endif
47   unsigned char hwaddr[DHCP_CHADDR_MAX];
48   char interface[IF_NAMESIZE];
49 };
50
51 static struct script_data *buf = NULL;
52 static size_t bytes_in_buf = 0, buf_size = 0;
53
54 int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
55 {
56   pid_t pid;
57   int i, pipefd[2];
58   struct sigaction sigact;
59
60   /* create the pipe through which the main program sends us commands,
61      then fork our process. */
62   if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1)
63     {
64       send_event(err_fd, EVENT_PIPE_ERR, errno);
65       _exit(0);
66     }
67
68   if (pid != 0)
69     {
70       close(pipefd[0]); /* close reader side */
71       return pipefd[1];
72     }
73
74   /* ignore SIGTERM, so that we can clean up when the main process gets hit
75      and SIGALRM so that we can use sleep() */
76   sigact.sa_handler = SIG_IGN;
77   sigact.sa_flags = 0;
78   sigemptyset(&sigact.sa_mask);
79   sigaction(SIGTERM, &sigact, NULL);
80   sigaction(SIGALRM, &sigact, NULL);
81
82   if (!option_bool(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 (option_bool(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 */
97               send_event(err_fd, EVENT_HUSER_ERR, errno);
98             }
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. */
105   for (max_fd--; max_fd >= 0; max_fd--)
106     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);
109   
110   /* 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;
116       unsigned char *end, *alloc_buff = NULL;
117       int err = 0;
118
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) 
135         p += sprintf(p, "%.2x-", data.hwaddr_type);
136       for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++)
137         {
138           p += sprintf(p, "%.2x", data.hwaddr[i]);
139           if (i != data.hwaddr_len - 1)
140             p += sprintf(p, ":");
141         }
142       
143       /* 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))
145         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
155       sprintf(daemon->dhcp_buff2, "%u", data.length);
156 #else
157       sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires);
158 #endif
159       
160       /* 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       
165       if (!read_write(pipefd[0], buf, 
166                       data.hostname_len + data.ed_len, 1))
167         continue;
168       
169       /* possible fork errors are all temporary resource problems */
170       while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
171         sleep(2);
172
173       free(alloc_buff);
174       
175       if (pid == -1)
176         continue;
177           
178       /* wait for child to complete */
179       if (pid != 0)
180         {
181           /* 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           
201           continue;
202         }
203       
204       if (data.clid_len != 0)
205         my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err);
206
207       if (strlen(data.interface) != 0)
208         my_setenv("DNSMASQ_INTERFACE", data.interface, &err);
209             
210 #ifdef HAVE_BROKEN_RTC
211       my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
212 #else
213       my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err); 
214 #endif
215       
216       if (data.hostname_len != 0)
217         {
218           char *dot;
219           hostname = (char *)buf;
220           hostname[data.hostname_len - 1] = 0;
221           if (!legal_hostname(hostname))
222             hostname = NULL;
223           else if ((dot = strchr(hostname, '.')))
224             {
225               my_setenv("DNSMASQ_DOMAIN", dot+1, &err);
226               *dot = 0;
227             } 
228           buf += data.hostname_len;
229         }
230
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       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
254       if (data.action == ACTION_OLD_HOSTNAME && hostname)
255         {
256           my_setenv("DNSMASQ_OLD_HOSTNAME", hostname, &err);
257           hostname = NULL;
258         }
259
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
265       p =  strrchr(daemon->lease_change_command, '/');
266       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         }
273       /* failed, send event so the main process logs the problem */
274       send_event(event_fd, EVENT_EXEC_ERR, err);
275       _exit(0); 
276     }
277 }
278
279 static void my_setenv(const char *name, const char *value, int *error)
280 {
281   if (*error == 0 && setenv(name, value, 1) != 0)
282     *error = errno;
283 }
284  
285 static 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
308 /* pack up lease data into a buffer */    
309 void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
310 {
311   unsigned char *p;
312   size_t size;
313   unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
314   
315   /* no script */
316   if (daemon->helperfd == -1)
317     return;
318
319   if (lease->extradata)
320     ed_len = lease->extradata_len;
321   if (lease->clid)
322     clid_len = lease->clid_len;
323   if (hostname)
324     hostname_len = strlen(hostname) + 1;
325
326   size = sizeof(struct script_data) +  clid_len + ed_len + hostname_len;
327
328   if (size > buf_size)
329     {
330       struct script_data *new;
331       
332       /* start with reasonable size, will almost never need extending. */
333       if (size < sizeof(struct script_data) + 200)
334         size = sizeof(struct script_data) + 200;
335
336       if (!(new = whine_malloc(size)))
337         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;
348   buf->ed_len = ed_len;
349   buf->hostname_len = hostname_len;
350   buf->addr = lease->addr;
351   buf->giaddr = lease->giaddr;
352   memcpy(buf->hwaddr, lease->hwaddr, lease->hwaddr_len);
353   if (!indextoname(daemon->dhcpfd, lease->last_interface, buf->interface))
354     buf->interface[0] = 0;
355   
356 #ifdef HAVE_BROKEN_RTC 
357   buf->length = lease->length;
358 #else
359   buf->expires = lease->expires;
360 #endif
361   buf->remaining_time = (unsigned int)difftime(lease->expires, now);
362
363   p = (unsigned char *)(buf+1);
364   if (clid_len != 0)
365     {
366       memcpy(p, lease->clid, clid_len);
367       p += clid_len;
368     }
369   if (hostname_len != 0)
370     {
371       memcpy(p, hostname, hostname_len);
372       p += hostname_len;
373     }
374   if (ed_len != 0)
375     {
376       memcpy(p, lease->extradata, ed_len);
377       p += ed_len;
378     }
379   bytes_in_buf = p - (unsigned char *)buf;
380 }
381
382 int helper_buf_empty(void)
383 {
384   return bytes_in_buf == 0;
385 }
386
387 void helper_write(void)
388 {
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
408 #endif
409
410