Home | History | Annotate | Download | only in src
      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 
     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 
    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 */
    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 
    342 int helper_buf_empty(void) {
    343     return bytes_in_buf == 0;
    344 }
    345 
    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