Index: src/dnsmasq.c =================================================================== --- src/dnsmasq.c (revision 696) +++ src/dnsmasq.c (revision 821) @@ -59,7 +59,6 @@ static int set_dns_listeners(time_t now, fd_set *set, int *maxfdp); static void check_dns_listeners(fd_set *set, time_t now); static void sig_handler(int sig); -static void async_event(int pipe, time_t now); static void fatal_event(struct event_desc *ev); static void poll_resolv(void); @@ -275,7 +274,7 @@ piperead = pipefd[0]; pipewrite = pipefd[1]; /* prime the pipe to load stuff first time. */ - send_event(pipewrite, EVENT_RELOAD, 0); + send_event(pipewrite, EVENT_RELOAD, 0, 0); err_pipe[1] = -1; @@ -340,7 +339,7 @@ } else if (getuid() == 0) { - send_event(err_pipe[1], EVENT_PIDFILE, errno); + send_event(err_pipe[1], EVENT_PIDFILE, errno, 0); _exit(0); } } @@ -372,7 +371,7 @@ (setgroups(0, &dummy) == -1 || setgid(gp->gr_gid) == -1)) { - send_event(err_pipe[1], EVENT_GROUP_ERR, errno); + send_event(err_pipe[1], EVENT_GROUP_ERR, errno, 0); _exit(0); } @@ -415,14 +414,14 @@ if (bad_capabilities != 0) { - send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities); + send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities, 0); _exit(0); } /* finally drop root */ if (setuid(ent_pw->pw_uid) == -1) { - send_event(err_pipe[1], EVENT_USER_ERR, errno); + send_event(err_pipe[1], EVENT_USER_ERR, errno, 0); _exit(0); } @@ -434,7 +433,7 @@ /* lose the setuid and setgid capbilities */ if (capset(hdr, data) == -1) { - send_event(err_pipe[1], EVENT_CAP_ERR, errno); + send_event(err_pipe[1], EVENT_CAP_ERR, errno, 0); _exit(0); } #endif @@ -647,7 +646,7 @@ } if (FD_ISSET(piperead, &rset)) - async_event(piperead, now); + async_event(piperead, now, NULL, 0); #ifdef HAVE_LINUX_NETWORK if (FD_ISSET(daemon->netlinkfd, &rset)) @@ -674,7 +673,7 @@ #endif if (daemon->dhcp && FD_ISSET(daemon->dhcpfd, &rset)) - dhcp_packet(now); + dhcp_packet(piperead, now); #ifndef NO_FORK if (daemon->helperfd != -1 && FD_ISSET(daemon->helperfd, &wset)) @@ -719,17 +718,18 @@ else return; - send_event(pipewrite, event, 0); + send_event(pipewrite, event, 0, 0); errno = errsave; } } -void send_event(int fd, int event, int data) +void send_event(int fd, int event, int data, int priv) { struct event_desc ev; ev.event = event; ev.data = data; + ev.priv = priv; /* error pipe, debug mode. */ if (fd == -1) @@ -771,14 +771,17 @@ die(_("cannot open %s: %s"), daemon->log_file ? daemon->log_file : "log", EC_FILE); } } - -static void async_event(int pipe, time_t now) + +/* returns the private data of the event + */ +int async_event(int pipe, time_t now, struct event_desc* event, unsigned int secs) { pid_t p; struct event_desc ev; int i; - if (read_write(pipe, (unsigned char *)&ev, sizeof(ev), 1)) + if (read_timeout(pipe, (unsigned char *)&ev, sizeof(ev), now, secs) > 0) + { switch (ev.event) { case EVENT_RELOAD: @@ -872,6 +875,14 @@ flush_log(); exit(EC_GOOD); } + } + else + return -1; /* timeout */ + + if (event) + memcpy( event, &ev, sizeof(ev)); + + return 0; } static void poll_resolv() Index: src/config.h =================================================================== --- src/config.h (revision 696) +++ src/config.h (revision 821) @@ -51,6 +51,8 @@ #define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */ #define LOG_MAX 5 /* log-queue length */ #define RANDFILE "/dev/urandom" +#define SCRIPT_TIMEOUT 6 +#define LEASE_CHECK_TIMEOUT 10 /* DBUS interface specifics */ #define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" Index: src/dnsmasq.h =================================================================== --- src/dnsmasq.h (revision 696) +++ src/dnsmasq.h (revision 821) @@ -116,6 +116,7 @@ /* Async event queue */ struct event_desc { int event, data; + unsigned int priv; }; #define EVENT_RELOAD 1 @@ -390,6 +391,7 @@ #define ACTION_OLD_HOSTNAME 2 #define ACTION_OLD 3 #define ACTION_ADD 4 +#define ACTION_ACCESS 5 #define DHCP_CHADDR_MAX 16 @@ -709,6 +711,7 @@ char *print_mac(char *buff, unsigned char *mac, int len); void bump_maxfd(int fd, int *max); int read_write(int fd, unsigned char *packet, int size, int rw); +int read_timeout(int fd, unsigned char *packet, int size, time_t now, int secs); /* log.c */ void die(char *message, char *arg1, int exit_code); @@ -748,7 +751,7 @@ /* dhcp.c */ void dhcp_init(void); -void dhcp_packet(time_t now); +void dhcp_packet(int piperead, time_t now); struct dhcp_context *address_available(struct dhcp_context *context, struct in_addr addr, @@ -792,14 +795,16 @@ void rerun_scripts(void); /* rfc2131.c */ -size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, +size_t dhcp_reply(int pipefd, struct dhcp_context *context, char *iface_name, int int_index, size_t sz, time_t now, int unicast_dest, int *is_inform); /* dnsmasq.c */ int make_icmp_sock(void); int icmp_ping(struct in_addr addr); -void send_event(int fd, int event, int data); +void send_event(int fd, int event, int data, int priv); void clear_cache_and_reload(time_t now); +int wait_for_child(int pipe); +int async_event(int pipe, time_t now, struct event_desc*, unsigned int timeout); /* isc.c */ #ifdef HAVE_ISC_READER @@ -832,9 +837,9 @@ /* helper.c */ #ifndef NO_FORK int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd); -void helper_write(void); +int helper_write(void); void queue_script(int action, struct dhcp_lease *lease, - char *hostname, time_t now); + char *hostname, time_t now, unsigned int uid); int helper_buf_empty(void); #endif Index: src/util.c =================================================================== --- src/util.c (revision 696) +++ src/util.c (revision 821) @@ -444,3 +444,38 @@ return 1; } +int read_timeout(int fd, unsigned char *packet, int size, time_t now, int secs) +{ + ssize_t n, done; + time_t expire; + + expire = now + secs; + + for (done = 0; done < size; done += n) + { + retry: + if (secs > 0) alarm(secs); + n = read(fd, &packet[done], (size_t)(size - done)); + + if (n == 0) + return 0; + else if (n == -1) + { + if (errno == EINTR) { + my_syslog(LOG_INFO, _("read timed out (errno %d)"), errno); + return 0; + } + + if (retry_send() || errno == ENOMEM || errno == ENOBUFS || errno == EAGAIN) + { + if (secs == 0 || (secs > 0 && dnsmasq_time() < expire)) + goto retry; + } + + my_syslog(LOG_INFO, _("error in read (timeout %d, errno %d)"), secs, errno); + return 0; + } + } + return 1; +} + Index: src/dhcp.c =================================================================== --- src/dhcp.c (revision 696) +++ src/dhcp.c (revision 821) @@ -103,7 +103,7 @@ daemon->dhcp_packet.iov_base = safe_malloc(daemon->dhcp_packet.iov_len); } -void dhcp_packet(time_t now) +void dhcp_packet(int piperead, time_t now) { struct dhcp_packet *mess; struct dhcp_context *context; @@ -239,7 +239,8 @@ if (!iface_enumerate(&parm, complete_context, NULL)) return; lease_prune(NULL, now); /* lose any expired leases */ - iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, + + iov.iov_len = dhcp_reply(piperead, parm.current, ifr.ifr_name, iface_index, (size_t)sz, now, unicast_dest, &is_inform); lease_update_file(now); lease_update_dns(); Index: src/helper.c =================================================================== --- src/helper.c (revision 696) +++ src/helper.c (revision 821) @@ -45,6 +45,7 @@ #endif unsigned char hwaddr[DHCP_CHADDR_MAX]; char interface[IF_NAMESIZE]; + unsigned int uid; }; static struct script_data *buf = NULL; @@ -60,7 +61,7 @@ then fork our process. */ if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1) { - send_event(err_fd, EVENT_PIPE_ERR, errno); + send_event(err_fd, EVENT_PIPE_ERR, errno, 0); _exit(0); } @@ -87,13 +88,13 @@ { if (daemon->options & OPT_NO_FORK) /* send error to daemon process if no-fork */ - send_event(event_fd, EVENT_HUSER_ERR, errno); + send_event(event_fd, EVENT_HUSER_ERR, errno, 0); else { /* kill daemon */ - send_event(event_fd, EVENT_DIE, 0); + send_event(event_fd, EVENT_DIE, 0, 0); /* return error */ - send_event(err_fd, EVENT_HUSER_ERR, errno);; + send_event(err_fd, EVENT_HUSER_ERR, errno, 0); } _exit(0); } @@ -122,6 +123,8 @@ action_str = "del"; else if (data.action == ACTION_ADD) action_str = "add"; + else if (data.action == ACTION_ACCESS) + action_str = "access"; else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME) action_str = "old"; else @@ -178,9 +181,11 @@ { /* On error send event back to main process for logging */ if (WIFSIGNALED(status)) - send_event(event_fd, EVENT_KILLED, WTERMSIG(status)); - else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) - send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status)); + send_event(event_fd, EVENT_KILLED, WTERMSIG(status), data.uid); + else if (WIFEXITED(status)) + send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status), data.uid); + else + send_event(event_fd, EVENT_EXITED, -1, data.uid); break; } @@ -263,7 +268,7 @@ err = errno; } /* failed, send event so the main process logs the problem */ - send_event(event_fd, EVENT_EXEC_ERR, err); + send_event(event_fd, EVENT_EXEC_ERR, err, data.uid); _exit(0); } } @@ -295,7 +300,7 @@ } /* pack up lease data into a buffer */ -void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now) +void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now, unsigned int uid) { unsigned char *p; size_t size; @@ -332,6 +337,7 @@ buf_size = size; } + buf->uid = uid; buf->action = action; buf->hwaddr_len = lease->hwaddr_len; buf->hwaddr_type = lease->hwaddr_type; @@ -393,12 +399,15 @@ return bytes_in_buf == 0; } -void helper_write(void) +/* returns -1 if write failed for a reason, 1 if no data exist + * and 0 if everything was ok. + */ +int helper_write(void) { ssize_t rc; if (bytes_in_buf == 0) - return; + return 1; if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1) { @@ -409,9 +418,11 @@ else { if (errno == EAGAIN || errno == EINTR) - return; + return -1; bytes_in_buf = 0; } + + return 0; } #endif Index: src/rfc2131.c =================================================================== --- src/rfc2131.c (revision 696) +++ src/rfc2131.c (revision 821) @@ -100,8 +100,49 @@ int clid_len, unsigned char *clid, int *len_out); static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); +static int check_access_script( int piperead, struct dhcp_lease *lease, struct dhcp_packet *mess, time_t now) +{ +#ifndef NO_FORK +unsigned int uid; +struct event_desc ev; +int ret; +struct dhcp_lease _lease; + + if (daemon->lease_change_command == NULL) return 0; /* ok */ + + if (!lease) { /* if host has not been seen before lease is NULL */ + memset(&_lease, 0, sizeof(_lease)); + lease = &_lease; + lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0); + } + + uid = rand16(); + queue_script(ACTION_ACCESS, lease, NULL, now, uid); + + /* send all data to helper process */ + do + { + helper_write(); + } while (helper_buf_empty() == 0); + + /* wait for our event */ + ret = 0; + do + { + ret = async_event( piperead, now, &ev, SCRIPT_TIMEOUT); + } + while(ev.priv != uid && ret >= 0); + + if (ret < 0 || ev.data != 0) /* timeout or error */ + { + return -1; + } + +#endif + return 0; /* ok */ +} -size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, +size_t dhcp_reply(int piperead, struct dhcp_context *context, char *iface_name, int int_index, size_t sz, time_t now, int unicast_dest, int *is_inform) { unsigned char *opt, *clid = NULL; @@ -252,7 +293,7 @@ mac->netid.next = netid; netid = &mac->netid; } - + /* Determine network for this packet. Our caller will have already linked all the contexts which match the addresses of the receiving interface but if the machine has an address already, or came via a relay, or we have a subnet selector, @@ -329,7 +370,7 @@ my_syslog(LOG_INFO, _("Available DHCP range: %s -- %s"), daemon->namebuff, inet_ntoa(context_tmp->end)); } } - + mess->op = BOOTREPLY; config = find_config(daemon->dhcp_conf, context, clid, clid_len, @@ -418,7 +459,7 @@ else mess->yiaddr = lease->addr; } - + if (!message && !lease && (!(lease = lease_allocate(mess->yiaddr)))) @@ -641,7 +682,14 @@ memcpy(req_options, option_ptr(opt, 0), option_len(opt)); req_options[option_len(opt)] = OPTION_END; } - + + if (mess_type == DHCPREQUEST || mess_type == DHCPDISCOVER) + if (check_access_script(piperead, lease, mess, now) < 0) + { + my_syslog(LOG_INFO, _("Ignoring client due to access script")); + return 0; + } + switch (mess_type) { case DHCPDECLINE: Index: src/log.c =================================================================== --- src/log.c (revision 696) +++ src/log.c (revision 821) @@ -73,7 +73,7 @@ if (!log_reopen(daemon->log_file)) { - send_event(errfd, EVENT_LOG_ERR, errno); + send_event(errfd, EVENT_LOG_ERR, errno, 0); _exit(0); } Index: src/lease.c =================================================================== --- src/lease.c (revision 696) +++ src/lease.c (revision 821) @@ -511,7 +511,7 @@ if (lease->old_hostname) { #ifndef NO_FORK - queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now); + queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now, 0); #endif free(lease->old_hostname); lease->old_hostname = NULL; @@ -520,7 +520,7 @@ else { #ifndef NO_FORK - queue_script(ACTION_DEL, lease, lease->hostname, now); + queue_script(ACTION_DEL, lease, lease->hostname, now, 0); #endif old_leases = lease->next; @@ -540,7 +540,7 @@ if (lease->old_hostname) { #ifndef NO_FORK - queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now); + queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now, 0); #endif free(lease->old_hostname); lease->old_hostname = NULL; @@ -552,7 +552,7 @@ (lease->aux_changed && (daemon->options & OPT_LEASE_RO))) { #ifndef NO_FORK - queue_script(lease->new ? ACTION_ADD : ACTION_OLD, lease, lease->hostname, now); + queue_script(lease->new ? ACTION_ADD : ACTION_OLD, lease, lease->hostname, now, 0); #endif lease->new = lease->changed = lease->aux_changed = 0; Index: man/dnsmasq.8 =================================================================== --- man/dnsmasq.8 (revision 696) +++ man/dnsmasq.8 (revision 821) @@ -724,12 +724,15 @@ .B \-6 --dhcp-script= Whenever a new DHCP lease is created, or an old one destroyed, the binary specified by this option is run. The arguments to the process -are "add", "old" or "del", the MAC +are "add", "old", "access" or "del", the MAC address of the host (or ""), the IP address, and the hostname, if known. "add" means a lease has been created, "del" means it has been destroyed, "old" is a notification of an existing lease when dnsmasq starts or a change to MAC address or hostname of an existing lease (also, lease length or expiry and client-id, if leasefile-ro is set). +The "access" keyword means that a request was just received and depending +on the script exit status request for address will be granted, if exit status +is zero or not if it is non-zero. The process is run as root (assuming that dnsmasq was originally run as root) even if dnsmasq is configured to change UID to an unprivileged user. The environment is inherited from the invoker of dnsmasq, and if the