From 1824802fb2775c5d4ff5011d8356053b333a047c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Kain" Date: Mon, 10 Mar 2014 00:52:56 -0400 Subject: [PATCH] Merge ifchd into ndhc. Rather than function as entirely separate daemons, ndhc will fork off an ifchd child that it will communicate with via pipes rather than by connecting to a SO_PEERCRED AF_UNIX socket. The advantages include: 1. Simpler configuration. Much easier for users and packagers to set up. 2. Drastically less complex code for the ifch functionality. More code is removed than added, and the result is a lot less complex. 3. Potentially better security. The ifch can only service the parent ndhc process, and it is restricted to issuing modifications to the single interface that ndhc manages. 4. Less memory used on systems that allow overcommit. The downsides: 1. Possibly more memory used on systems that run multiple ndhcs and use strict commit limits. At the same time, use netlink rather than ioctls so that the interface ip, subnet, and broadcast address can be set simultaneously. This change reduces the netlink notification spam greatly. The current code builds but isn't yet complete. Subsequent commits will flesh things out and polish out some remaining issues. --- CMakeLists.txt | 5 +- LICENSE | 2 +- README | 2 +- ifchd/CMakeLists.txt | 20 - ifchd/ifchd-defines.h | 58 --- ifchd/ifchd.8 | 91 ---- ifchd/ifchd.c | 832 --------------------------------- ndhc/CMakeLists.txt | 16 +- {ifchd => ndhc}/ifch_proto.h | 1 - ndhc/ifchange.c | 52 +-- {ifchd => ndhc}/ifchd-parse.rl | 80 ++-- ndhc/ifchd.c | 408 ++++++++++++++++ ndhc/ifchd.h | 45 ++ ifchd/linux.c => ndhc/ifset.c | 164 ++----- ifchd/linux.h => ndhc/ifset.h | 28 +- ndhc/ndhc-defines.h | 3 +- ndhc/ndhc.8 | 63 ++- ndhc/ndhc.c | 314 +++++++------ ndhc/seccomp.c | 153 ++++++ ndhc/seccomp.h | 38 ++ ndhc/sys.c | 4 +- ndhc/sys.h | 2 +- 22 files changed, 998 insertions(+), 1383 deletions(-) delete mode 100644 ifchd/CMakeLists.txt delete mode 100644 ifchd/ifchd-defines.h delete mode 100644 ifchd/ifchd.8 delete mode 100644 ifchd/ifchd.c rename {ifchd => ndhc}/ifch_proto.h (94%) rename {ifchd => ndhc}/ifchd-parse.rl (70%) create mode 100644 ndhc/ifchd.c create mode 100644 ndhc/ifchd.h rename ifchd/linux.c => ndhc/ifset.c (72%) rename ifchd/linux.h => ndhc/ifset.h (59%) create mode 100644 ndhc/seccomp.c create mode 100644 ndhc/seccomp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cddf7cc..9a3a942 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,7 @@ cmake_minimum_required (VERSION 2.6) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -Wno-format-extra-args -Wno-format-zero-length -Wformat-nonliteral -Wformat-security -lrt -lcap -D_GNU_SOURCE -DHAVE_CLEARENV -DLINUX") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -s -std=gnu99 -pedantic -Wall -Wno-format-extra-args -Wno-format-zero-length -Wformat-nonliteral -Wformat-security -lrt -lcap -D_GNU_SOURCE -DHAVE_CLEARENV -DLINUX") -include_directories( - "${PROJECT_SOURCE_DIR}/ncmlib" - "${PROJECT_SOURCE_DIR}/ifchd") +include_directories("${PROJECT_SOURCE_DIR}/ncmlib") add_subdirectory(ncmlib) -add_subdirectory(ifchd) add_subdirectory(ndhc) diff --git a/LICENSE b/LICENSE index 3c569fe..850bfbf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2012 Nicholas J. Kain +Copyright (c) 2004-2014 Nicholas J. Kain All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README b/README index 389ad8b..59c936d 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -ndhc + ifchd, Copyright (C) 2004-2011 Nicholas J. Kain. +ndhc + ifchd, Copyright (C) 2004-2014 Nicholas J. Kain. See LICENSE for licensing information. In short: Two-clause / New BSD. Requirements: diff --git a/ifchd/CMakeLists.txt b/ifchd/CMakeLists.txt deleted file mode 100644 index 70bc6a7..0000000 --- a/ifchd/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -project (ifchd) - -cmake_minimum_required (VERSION 2.6) - -set(RAGEL_IFCHD_PARSE ${CMAKE_CURRENT_BINARY_DIR}/ifchd-parse.c) - -find_program(RAGEL ragel) -add_custom_command( - OUTPUT ${RAGEL_IFCHD_PARSE} - COMMAND ${RAGEL} -G2 -o ${RAGEL_IFCHD_PARSE} ifchd-parse.rl - DEPENDS ifchd-parse.rl - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Compiling Ragel state machine: ifchd-parse.rl" - VERBATIM - ) - -file(GLOB IFCHD_SRCS "*.c") - -add_executable(ifchd ${RAGEL_IFCHD_PARSE} ${IFCHD_SRCS}) -target_link_libraries(ifchd ncmlib) diff --git a/ifchd/ifchd-defines.h b/ifchd/ifchd-defines.h deleted file mode 100644 index 94c9d05..0000000 --- a/ifchd/ifchd-defines.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef IFCHD_DEFINES_H_ -#define IFCHD_DEFINES_H_ - -#include "defines.h" - -#define PID_FILE_DEFAULT "/var/run/ifchd.pid" -#define IFCHD_VERSION "0.9" -#define MAX_BUF 384 -#define SOCK_QUEUE 2 -#define CONN_TIMEOUT 60 -#define MAX_IFACES 10 - -enum ifchd_states { - STATE_NOTHING, - STATE_INTERFACE, - STATE_IP4SET, - STATE_IP, - STATE_SUBNET, - STATE_TIMEZONE, - STATE_ROUTER, - STATE_DNS, - STATE_LPRSVR, - STATE_HOSTNAME, - STATE_DOMAIN, - STATE_IPTTL, - STATE_MTU, - STATE_BROADCAST, - STATE_NTPSVR, - STATE_WINS -}; - -#include -struct ifchd_client { - /* Socket fd, current state, and idle time for connection. */ - int fd; - int state; - int idle_time; - - /* Symbolic name of the interface associated with a connection. */ - char ifnam[IFNAMSIZ]; - /* Per-connection buffer. */ - char ibuf[MAX_BUF]; - /* ' '-delimited buffers of nameservers and domains */ - char namesvrs[MAX_BUF]; - char domains[MAX_BUF]; -}; - -extern void perform_timezone(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_dns(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_lprsvr(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_hostname(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_domain(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_ipttl(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_ntpsrv(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_wins(struct ifchd_client *cl, const char *str, size_t len); - -#endif /* IFCHD_DEFINES_H_ */ - diff --git a/ifchd/ifchd.8 b/ifchd/ifchd.8 deleted file mode 100644 index e61c910..0000000 --- a/ifchd/ifchd.8 +++ /dev/null @@ -1,91 +0,0 @@ -.TH IFCHD 8 2012-07-20 Linux "Linux Administrator's Manual" -.SH NAME -ifchd \- interface change daemon -.SH SYNOPSIS -.B ifchd -.RI [ OPTION ]... -.SH DESCRIPTION -The ifchd daemon changes network interface configuration information -(such as the IP address, broadcast address, subnet, etc) as well as -resolv.conf, the machine hostname, and other similar system configuration -bits on the request of authorized clients such as ndhc instances. -.SH OPTIONS -.TP -.BR \-d ,\ \-\-detach -Immediately fork into the background. This is the default behavior. -.TP -.BR \-n ,\ \-\-nodetach -Do not fork into the background. Useful for debugging or initial setup. -.TP -.BR \-q ,\ \-\-quiet -Don't print to standard out, standard error, or syslog. -.TP -.BI \-c\ CHROOTDIR ,\ \-\-chroot= CHROOTDIR -This option specifies the directory to which ifchd should confine itself via -chroot() after startup. This directory should be shared with associated ndhc -daemons, and should have access to dev/urandom and dev/null. For logging to -work, a dev/log socket or device should also exist. -.TP -.BI \-r\ RESOLVCONF ,\ \-\-resolve= RESOLVCONF -Specifies the path to the system resolv.conf. This file will typically be in -/etc/resolv.conf. If this option is specified, ndhc will update the contents -of this file to match the DNS servers specified by the remote DHCP server. If -this option is not specified, ifchd will never change the system DNS resolution -configuration. -.TP -.BR \-o ,\ \-\-hostname -If specified, ifchd will update the system host name in response to any -hostname option field provided by a remote DHCP server on the request of -a ndhc client. If this option is not specified, ifchd will never change -the system hostname. -.TP -.BI \-p\ PIDFILE ,\ \-\-pidfile= PIDFILE -Write the process id number of the ifchd instance into the specified file name. -The default is to not write the process id number into any file at all. -.TP -.BI \-u\ USER ,\ \-\-user= USER -This option specifies the user name or user id that ifchd will change to after -startup. This user should be unique to the ifchd daemon. -.TP -.BI \-g\ GROUP ,\ \-\-group= GROUP -This option specifies the group name or group id that ifchd will change to -after startup. -.TP -.BI \-U\ CLIENTUSER ,\ \-\-cuser= CLIENTUSER -This option specifies the user name or user id that will be required of -processes that wish to make interface change requests of ifchd. Any other -users will be denied a connection to the ifchange socket. -.TP -.BI \-G\ CLIENTGROUP ,\ \-\-cgroup= CLIENTGROUP -This option specifies the group name or group id that will be required of -processes that wish to make interface change requests of ifchd. Any other -groups will be denied a connection to the ifchange socket. -.TP -.BI \-P\ CLIENTPID ,\ \-\-cpid= CLIENTPID -This option specifies the process id that will be required to make interface -change requests of ifchd. Any process that has a process id that does not -equal this value will be denied a connection to the ifchange socket. -.TP -.BI \-i\ INTERFACE ,\ \-\-interface= INTERFACE -Specifies an interface by name (such as 'eth0') on which ifchd is allowed to -make configuration changes (such as IP address, subnet mask, etc). Multiple -interfaces may be whitelisted by using this switch multiple times. -.TP -.BR \-V ,\ \-\-verbose -Print detailed messages. Useful for debugging or setup. -.TP -.BR \-h ,\ \-\-help -Print basic help information and exit. -.TP -.BR \-v ,\ \-\-version -Display the ifchd version number and copyright information. -.SH SIGNALS -It is not necessary to sleep between sending signals, as signals received are -processed sequentially in the order they are received. ifchd does not -perform any unusual behavior on receipt of signals. -.SH NOTES -ifchd is rather minimal and doesn't do exotic things like change NTP server -configuration. This is intentional, since it needs to run as CAP_NET_ADMIN, -which although far less powerful than root, still gives it a fair amount -of privileged behavior. - diff --git a/ifchd/ifchd.c b/ifchd/ifchd.c deleted file mode 100644 index 7cf8ae1..0000000 --- a/ifchd/ifchd.c +++ /dev/null @@ -1,832 +0,0 @@ -/* ifchd.c - interface change daemon - * - * Copyright (c) 2004-2013 Nicholas J. Kain - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include "ifchd-defines.h" -#include "log.h" -#include "chroot.h" -#include "pidfile.h" -#include "signals.h" -#include "ifch_proto.h" -#include "strl.h" -#include "cap.h" -#include "io.h" -#include "linux.h" -#include "seccomp-bpf.h" - -struct ifchd_client clients[SOCK_QUEUE]; - -static int epollfd, signalFd; -/* Extra two event slots are for signalFd and the listen socket. */ -static struct epoll_event events[SOCK_QUEUE+2]; - -int resolv_conf_fd = -1; -/* int ntp_conf_fd = -1; */ - -/* If true, allow HOSTNAME changes from dhcp server. */ -int allow_hostname = 0; - -static uid_t peer_uid; -static gid_t peer_gid; -static pid_t peer_pid; - -static int gflags_verbose = 0; - -extern int execute_buffer(struct ifchd_client *cl, char *newbuf); - -static void writeordie(int fd, const char *buf, int len) -{ - if (safe_write(fd, buf, len) == -1) - suicide("write returned error"); -} - -static void epoll_add(int fd) -{ - struct epoll_event ev; - int r; - ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; - ev.data.fd = fd; - r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); - if (r == -1) - suicide("epoll_add failed %s", strerror(errno)); -} - -static void epoll_del(int fd) -{ - struct epoll_event ev; - int r; - ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; - ev.data.fd = fd; - r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); - if (r == -1) - suicide("epoll_del failed %s", strerror(errno)); -} - -static int enforce_seccomp(void) -{ - struct sock_filter filter[] = { - VALIDATE_ARCHITECTURE, - EXAMINE_SYSCALL, - ALLOW_SYSCALL(read), - ALLOW_SYSCALL(write), - ALLOW_SYSCALL(sendto), // used for glibc syslog routines - ALLOW_SYSCALL(epoll_wait), - ALLOW_SYSCALL(epoll_ctl), - ALLOW_SYSCALL(close), - ALLOW_SYSCALL(accept), - ALLOW_SYSCALL(socket), - ALLOW_SYSCALL(ioctl), - ALLOW_SYSCALL(getsockopt), - ALLOW_SYSCALL(getsockname), - ALLOW_SYSCALL(listen), - ALLOW_SYSCALL(open), - ALLOW_SYSCALL(fstat), - ALLOW_SYSCALL(connect), - ALLOW_SYSCALL(recvmsg), - ALLOW_SYSCALL(fsync), - ALLOW_SYSCALL(lseek), - ALLOW_SYSCALL(truncate), - ALLOW_SYSCALL(fcntl), - ALLOW_SYSCALL(unlink), - ALLOW_SYSCALL(bind), - ALLOW_SYSCALL(chmod), - - ALLOW_SYSCALL(rt_sigreturn), -#ifdef __NR_sigreturn - ALLOW_SYSCALL(sigreturn), -#endif - // Allowed by vDSO - ALLOW_SYSCALL(getcpu), - ALLOW_SYSCALL(time), - ALLOW_SYSCALL(gettimeofday), - ALLOW_SYSCALL(clock_gettime), - - ALLOW_SYSCALL(exit_group), - ALLOW_SYSCALL(exit), - KILL_PROCESS, - }; - struct sock_fprog prog = { - .len = (unsigned short)(sizeof filter / sizeof filter[0]), - .filter = filter, - }; - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) - return -1; - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) - return -1; - return 0; -} - -/* Writes a new resolv.conf based on the information we have received. */ -static void write_resolve_conf(struct ifchd_client *cl) -{ - const static char ns_str[] = "nameserver "; - const static char dom_str[] = "domain "; - const static char srch_str[] = "search "; - int r; - off_t off; - char buf[MAX_BUF]; - - if (resolv_conf_fd == -1) - return; - if (strlen(cl->namesvrs) == 0) - return; - - if (lseek(resolv_conf_fd, 0, SEEK_SET) == -1) - return; - - char *p = cl->namesvrs; - while (p && (*p != '\0')) { - char *q = strchr(p, ','); - if (!q) - q = strchr(p, '\0'); - else - *q++ = '\0'; - strnkcpy(buf, p, sizeof buf); - - writeordie(resolv_conf_fd, ns_str, strlen(ns_str)); - writeordie(resolv_conf_fd, buf, strlen(buf)); - writeordie(resolv_conf_fd, "\n", 1); - - p = q; - } - - p = cl->domains; - int numdoms = 0; - while (p && (*p != '\0')) { - char *q = strchr(p, ','); - if (!q) - q = strchr(p, '\0'); - else - *q++ = '\0'; - strnkcpy(buf, p, sizeof buf); - - if (numdoms == 0) { - writeordie(resolv_conf_fd, dom_str, strlen(dom_str)); - writeordie(resolv_conf_fd, buf, strlen(buf)); - } else { - if (numdoms == 1) { - writeordie(resolv_conf_fd, "\n", 1); - writeordie(resolv_conf_fd, srch_str, strlen(srch_str)); - writeordie(resolv_conf_fd, buf, strlen(buf)); - } else { - writeordie(resolv_conf_fd, " ", 1); - writeordie(resolv_conf_fd, buf, strlen(buf)); - } - } - - ++numdoms; - p = q; - if (numdoms > 6) - break; - } - writeordie(resolv_conf_fd, "\n", 1); - - off = lseek(resolv_conf_fd, 0, SEEK_CUR); - if (off == -1) { - log_line("write_resolve_conf: lseek returned error: %s", - strerror(errno)); - return; - } - retry: - r = ftruncate(resolv_conf_fd, off); - if (r == -1) { - if (errno == EINTR) - goto retry; - log_line("write_resolve_conf: ftruncate returned error: %s", - strerror(errno)); - return; - } - r = fsync(resolv_conf_fd); - if (r == -1) { - log_line("write_resolve_conf: fsync returned error: %s", - strerror(errno)); - return; - } -} - -/* XXX: addme */ -void perform_timezone(struct ifchd_client *cl, const char *str, size_t len) -{ - log_line("Timezone setting NYI: '%s'", str); -} - -/* Add a dns server to the /etc/resolv.conf -- we already have a fd. */ -void perform_dns(struct ifchd_client *cl, const char *str, size_t len) -{ - if (!str || resolv_conf_fd == -1) - return; - strnkcpy(cl->namesvrs, str, sizeof cl->namesvrs); - write_resolve_conf(cl); - log_line("Added DNS server: '%s'", str); -} - -/* Updates for print daemons are too non-standard to be useful. */ -void perform_lprsvr(struct ifchd_client *cl, const char *str, size_t len) -{ - log_line("Line printer server setting NYI: '%s'", str); -} - -/* Sets machine hostname. */ -void perform_hostname(struct ifchd_client *cl, const char *str, size_t len) -{ - if (!allow_hostname || !str) - return; - if (sethostname(str, strlen(str) + 1) == -1) - log_line("sethostname returned %s", strerror(errno)); - else - log_line("Set hostname: '%s'", str); -} - -/* update "domain" and "search" in /etc/resolv.conf */ -void perform_domain(struct ifchd_client *cl, const char *str, size_t len) -{ - if (!str || resolv_conf_fd == -1) - return; - strnkcpy(cl->domains, str, sizeof cl->domains); - write_resolve_conf(cl); - log_line("Added DNS domain: '%s'", str); -} - -/* I don't think this can be done without a netfilter extension - * that isn't in the mainline kernels. */ -void perform_ipttl(struct ifchd_client *cl, const char *str, size_t len) -{ - log_line("TTL setting NYI: '%s'", str); -} - -/* XXX: addme */ -void perform_ntpsrv(struct ifchd_client *cl, const char *str, size_t len) -{ - log_line("NTP server setting NYI: '%s'", str); -} - -/* Maybe Samba cares about this feature? I don't know. */ -void perform_wins(struct ifchd_client *cl, const char *str, size_t len) -{} - -static inline void clock_or_die(struct timespec *ts) -{ - if (clock_gettime(CLOCK_MONOTONIC, ts)) - suicide("clock_gettime failed %s", strerror(errno)); -} - -static void ifchd_client_init(struct ifchd_client *p) -{ - p->fd = -1; - struct timespec ts; - clock_or_die(&ts); - p->idle_time = ts.tv_sec; - p->state = STATE_NOTHING; - - memset(p->ibuf, 0, sizeof p->ibuf); - memset(p->ifnam, 0, sizeof p->ifnam); - memset(p->namesvrs, 0, sizeof p->namesvrs); - memset(p->domains, 0, sizeof p->domains); -} - -static void ifchd_client_wipe(struct ifchd_client *p) -{ - if (p->fd >= 0) { - epoll_del(p->fd); - close(p->fd); - } - ifchd_client_init(p); -} - -static void ifchd_client_new(struct ifchd_client *p, int fd) -{ - ifchd_client_wipe(p); - p->fd = fd; - epoll_add(fd); -} - -/* Conditionally accepts a new connection and initializes data structures. */ -static void add_sk(int sk) -{ - int i; - - if (authorized_peer(sk, peer_pid, peer_uid, peer_gid)) { - for (i = 0; i < SOCK_QUEUE; i++) { - struct ifchd_client *p = &clients[i]; - if (p->fd == -1) { - ifchd_client_new(p, sk); - return; - } - } - } - close(sk); -} - -/* Closes idle connections. */ -static void close_idle_sk(void) -{ - int i; - - for (i=0; ifd == -1) - continue; - struct timespec ts; - clock_or_die(&ts); - if (ts.tv_sec - p->idle_time > CONN_TIMEOUT) - ifchd_client_wipe(p); - } -} - -/* Opens a non-blocking listening socket with the appropriate properties. */ -static int get_listen(void) -{ - int lsock, ret; - static const struct sockaddr_un lsock_addr = - { - .sun_family = AF_UNIX, - .sun_path = "/var/state/ifchange" - }; - - lsock = socket(AF_UNIX, SOCK_STREAM, 0); - if (lsock == -1) - suicide("dispatch_work - failed to create socket"); - - fcntl(lsock, F_SETFL, O_NONBLOCK); - - (void) unlink("/var/state/ifchange"); - ret = bind(lsock, (struct sockaddr *) &lsock_addr, sizeof(lsock_addr)); - if (ret) - suicide("dispatch_work - failed to bind socket"); - ret = chmod("/var/state/ifchange", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (ret) - suicide("dispatch_work - failed to chmod socket"); - ret = listen(lsock, SOCK_QUEUE); - if (ret) - suicide("dispatch_work - failed to listen on socket"); - - return lsock; -} - -static void accept_conns(int *lsock) -{ - int ret; - struct sockaddr_un sock_addr; - socklen_t sock_len = sizeof(sock_addr); - - for(;;) - { - ret = accept(*lsock, (struct sockaddr *) &sock_addr, &sock_len); - if (ret != -1) { - add_sk(ret); - return; - } - switch (errno) { - case EAGAIN: -#ifdef LINUX - case ENETDOWN: - case EPROTO: - case ENOPROTOOPT: - case EHOSTDOWN: - case ENONET: - case EHOSTUNREACH: - case EOPNOTSUPP: - case ENETUNREACH: -#endif - return; - - case EINTR: - continue; - - case EBADF: - case ENOTSOCK: - case EINVAL: - log_line("warning: accept returned %s!", strerror(errno)); - - epoll_del(*lsock); - close(*lsock); - - *lsock = get_listen(); - epoll_add(*lsock); - return; - - case ECONNABORTED: - case EMFILE: - case ENFILE: - log_line("warning: accept returned %s!", strerror(errno)); - return; - - default: - log_line("warning: accept returned a mysterious error: %s", - strerror(errno)); - return; - } - } -} - -static void setup_signals() -{ - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGPIPE); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGUSR2); - sigaddset(&mask, SIGTSTP); - sigaddset(&mask, SIGTTIN); - sigaddset(&mask, SIGCHLD); - sigaddset(&mask, SIGHUP); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) - suicide("sigprocmask failed"); - signalFd = signalfd(-1, &mask, SFD_NONBLOCK); - if (signalFd < 0) - suicide("signalfd failed"); -} - -static void signal_dispatch() -{ - int t, off = 0; - struct signalfd_siginfo si; - again: - t = read(signalFd, (char *)&si + off, sizeof si - off); - if (t < sizeof si - off) { - if (t < 0) { - if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR) - goto again; - else - suicide("signalfd read error"); - } - off += t; - } - switch (si.ssi_signo) { - case SIGINT: - case SIGTERM: - exit(EXIT_SUCCESS); - default: - break; - } -} - -static void process_client_fd(int fd) -{ - struct ifchd_client *cl = NULL; - int r; - char buf[MAX_BUF]; - - for (int j = 0; j < SOCK_QUEUE; ++j) { - if (clients[j].fd == fd) { - cl = &clients[j]; - break; - } - } - if (!cl) - suicide("epoll returned pending read for untracked fd"); - - struct timespec ts; - clock_or_die(&ts); - cl->idle_time = ts.tv_sec; - memset(buf, '\0', sizeof buf); - - r = safe_read(cl->fd, buf, sizeof buf - 1); - if (r == 0) { - // Remote end hung up. - goto fail; - } else if (r < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) - return; - log_line("error reading from client fd: %s", strerror(errno)); - goto fail; - } - - if (execute_buffer(cl, buf) == -1) { - log_line("execute_buffer was passed invalid commands"); - goto fail; - } - return; - fail: - ifchd_client_wipe(cl); -} - -/* Core function that handles connections, gathers input, and calls - * the state machine to do actual work. */ -static void dispatch_work(void) -{ - int lsock; - - /* Initialize all structures to blank state. */ - for (int i = 0; i < SOCK_QUEUE; i++) - ifchd_client_init(&clients[i]); - - lsock = get_listen(); - - epoll_add(lsock); - epoll_add(signalFd); - - for (;;) { - int r = epoll_wait(epollfd, events, SOCK_QUEUE + 2, -1); - if (r == -1) { - if (errno == EINTR) - continue; - else - suicide("epoll_wait failed"); - } - for (int i = 0; i < r; ++i) { - int fd = events[i].data.fd; - if (fd == lsock) - accept_conns(&lsock); - else if (fd == signalFd) - signal_dispatch(); - else - process_client_fd(fd); - } - close_idle_sk(); - } -} - -int main(int argc, char** argv) { - int c, t; - uid_t uid = 0; - gid_t gid = 0; - char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT; - char chrootd[MAX_PATH_LENGTH] = ""; - char resolv_conf_d[MAX_PATH_LENGTH] = ""; - char *p; - struct passwd *pws; - struct group *grp; - - while (1) { - int option_index = 0; - static const struct option long_options[] = { - {"detach", 0, 0, 'd'}, - {"nodetach", 0, 0, 'n'}, - {"pidfile", 1, 0, 'p'}, - {"quiet", 0, 0, 'q'}, - {"chroot", 1, 0, 'c'}, - {"resolve", 1, 0, 'r'}, - {"hostname", 0, 0, 'o'}, - {"user", 1, 0, 'u'}, - {"group", 1, 0, 'g'}, - {"cuser", 1, 0, 'U'}, - {"cgroup", 1, 0, 'G'}, - {"cpid", 1, 0, 'P'}, - {"interface", 1, 0, 'i'}, - {"help", 0, 0, 'h'}, - {"version", 0, 0, 'v'}, - {"verbose", 0, 0, 'V'}, - {0, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "dnp:qc:r:ou:g:U:G:P:i:hvV", long_options, - &option_index); - if (c == -1) - break; - - switch (c) { - - case 'h': - printf( -"ifchd %s, if change daemon. Licensed under 2-clause BSD.\n", IFCHD_VERSION); - printf( -"Copyright (C) 2004-2012 Nicholas J. Kain\n" -"Usage: ifchd [OPTIONS]\n" -" -d, --detach detach from TTY and daemonize\n" -" -n, --nodetach stay attached to TTY\n" -" -q, --quiet don't print to std(out|err) or log\n" -" -c, --chroot path where ifchd should chroot\n" -" -r, --resolve path to resolv.conf or equiv\n" -" -o, --hostname allow dhcp to set machine hostname\n" -" -p, --pidfile pidfile path\n"); - printf( -" -u, --user user name that ifchd should run as\n" -" -g, --group group name that ifchd should run as\n" -" -U, --cuser user name of clients\n" -" -G, --cgroup group name of clients\n" -" -P, --cpid process id of client\n" -" -i, --interface ifchd clients may modify this interface\n" -" -V, --verbose log detailed messages\n" -" -h, --help print this help and exit\n" -" -v, --version print version information and exit\n"); - exit(EXIT_FAILURE); - break; - - case 'v': - printf("ifchd %s, if change daemon.\n", IFCHD_VERSION); - printf("Copyright (c) 2004-2013 Nicholas J. Kain\n" - "All rights reserved.\n\n" - "Redistribution and use in source and binary forms, with or without\n" - "modification, are permitted provided that the following conditions are met:\n\n" - "- Redistributions of source code must retain the above copyright notice,\n" - " this list of conditions and the following disclaimer.\n" - "- Redistributions in binary form must reproduce the above copyright notice,\n" - " this list of conditions and the following disclaimer in the documentation\n" - " and/or other materials provided with the distribution.\n\n" - "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n" - "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n" - "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" - "ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n" - "LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n" - "CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n" - "SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n" - "INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n" - "CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n" - "ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" - "POSSIBILITY OF SUCH DAMAGE.\n"); - exit(EXIT_FAILURE); - break; - - case 'd': - gflags_detach = 1; - break; - - case 'n': - gflags_detach = 0; - break; - - case 'q': - gflags_quiet = 1; - break; - - case 'c': - strnkcpy(chrootd, optarg, MAX_PATH_LENGTH); - break; - - case 'p': - strnkcpy(pidfile, optarg, MAX_PATH_LENGTH); - break; - - case 'r': - strnkcpy(resolv_conf_d, optarg, MAX_PATH_LENGTH); - break; - - case 'o': - allow_hostname = 1; - break; - - case 'u': - t = (unsigned int) strtol(optarg, &p, 10); - if (*p != '\0') { - pws = getpwnam(optarg); - if (pws) { - uid = (int)pws->pw_uid; - if (!gid) - gid = (int)pws->pw_gid; - } else suicide("FATAL - Invalid uid specified."); - } else - uid = t; - break; - - case 'g': - t = (unsigned int) strtol(optarg, &p, 10); - if (*p != '\0') { - grp = getgrnam(optarg); - if (grp) { - gid = (int)grp->gr_gid; - } else - suicide("FATAL - Invalid gid specified."); - } else - gid = t; - break; - - case 'U': - t = (unsigned int) strtol(optarg, &p, 10); - if (*p != '\0') { - pws = getpwnam(optarg); - if (pws) { - peer_uid = (int)pws->pw_uid; - if (!peer_gid) - peer_gid = (int)pws->pw_gid; - } else - suicide("FATAL - Invalid uid specified."); - } else - peer_uid = t; - break; - - case 'G': - t = (unsigned int) strtol(optarg, &p, 10); - if (*p != '\0') { - grp = getgrnam(optarg); - if (grp) { - peer_gid = (int)grp->gr_gid; - } else - suicide("FATAL - Invalid gid specified."); - } else - peer_gid = t; - break; - - case 'P': - t = (unsigned int) strtol(optarg, &p, 10); - if (*p == '\0') - peer_pid = t; - break; - - case 'i': - add_permitted_if(optarg); - break; - - case 'V': - gflags_verbose = 1; - break; - } - } - - if (getuid()) - suicide("FATAL - I need root for CAP_NET_ADMIN and chroot!"); - - if (gflags_detach) - if (daemon(0,0)) { - log_line("FATAL - detaching fork failed\n"); - exit(EXIT_FAILURE); - } - - if (file_exists(pidfile, "w") == -1) { - log_line("FATAL - cannot open pidfile for write!"); - exit(EXIT_FAILURE); - } - write_pid(pidfile); - - umask(077); - setup_signals(); - - /* If we are requested to update resolv.conf, preopen the fd before - * we drop root privileges, making sure that if we create - * resolv.conf, it will be world-readable. - */ - if (strncmp(resolv_conf_d, "", MAX_PATH_LENGTH)) { - umask(022); - resolv_conf_fd = open(resolv_conf_d, O_RDWR | O_CREAT, 644); - umask(077); - if (resolv_conf_fd == -1) { - suicide("FATAL - unable to open resolv.conf"); - } - } - - if (!strncmp(chrootd, "", MAX_PATH_LENGTH)) - suicide("FATAL - No chroot path specified. Refusing to run."); - - /* Note that failure cases are handled by called fns. */ - imprison(chrootd); - set_cap(uid, gid, "cap_net_admin=ep"); - drop_root(uid, gid); - - /* Cover our tracks... */ - memset(chrootd, '\0', sizeof(chrootd)); - memset(resolv_conf_d, '\0', sizeof(resolv_conf_d)); - memset(pidfile, '\0', sizeof(pidfile)); - - epollfd = epoll_create1(0); - if (epollfd == -1) - suicide("epoll_create1 failed"); - - if (enforce_seccomp()) - log_line("seccomp filter cannot be installed"); - - dispatch_work(); - - /* Explicitly freed so memory debugger output has less static. */ - for (size_t i = 0; i < SOCK_QUEUE; ++i) - ifchd_client_wipe(&clients[i]); - - exit(EXIT_SUCCESS); -} diff --git a/ndhc/CMakeLists.txt b/ndhc/CMakeLists.txt index ff69b71..2aa267a 100644 --- a/ndhc/CMakeLists.txt +++ b/ndhc/CMakeLists.txt @@ -2,7 +2,21 @@ project (ndhc) cmake_minimum_required (VERSION 2.6) +include_directories("${PROJECT_SOURCE_DIR}") + +set(RAGEL_IFCHD_PARSE ${CMAKE_CURRENT_BINARY_DIR}/ifchd-parse.c) + +find_program(RAGEL ragel) +add_custom_command( + OUTPUT ${RAGEL_IFCHD_PARSE} + COMMAND ${RAGEL} -G2 -o ${RAGEL_IFCHD_PARSE} ifchd-parse.rl + DEPENDS ifchd-parse.rl + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Compiling Ragel state machine: ifchd-parse.rl" + VERBATIM + ) + file(GLOB NDHC_SRCS "*.c") -add_executable(ndhc ${NDHC_SRCS}) +add_executable(ndhc ${RAGEL_IFCHD_PARSE} ${NDHC_SRCS}) target_link_libraries(ndhc ncmlib) diff --git a/ifchd/ifch_proto.h b/ndhc/ifch_proto.h similarity index 94% rename from ifchd/ifch_proto.h rename to ndhc/ifch_proto.h index b704d3c..91cb2cc 100644 --- a/ifchd/ifch_proto.h +++ b/ndhc/ifch_proto.h @@ -1,7 +1,6 @@ #ifndef NJK_IFCH_PROTO_H__ #define NJK_IFCH_PROTO_H__ 1 -#define CMD_INTERFACE "iface" #define CMD_IP4SET "ip4" #define CMD_IP "ip" #define CMD_SUBNET "snet" diff --git a/ndhc/ifchange.c b/ndhc/ifchange.c index 44f3785..78a0dd1 100644 --- a/ndhc/ifchange.c +++ b/ndhc/ifchange.c @@ -1,6 +1,6 @@ /* ifchange.c - functions to call the interface change daemon * - * Copyright (c) 2004-2011 Nicholas J. Kain + * Copyright (c) 2004-2014 Nicholas J. Kain * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,12 +30,8 @@ #include #include #include -#include -#include #include #include -#include -#include #include #include "options.h" @@ -49,6 +45,9 @@ #include "ifchange.h" #include "ifch_proto.h" +// XXX: Move to header +extern int pToIfchW; + static int cfg_deconfig; // Set if the interface has already been deconfigured. static struct dhcpmsg cfg_packet; // Copy of the current configuration packet. @@ -159,49 +158,25 @@ static int ifchd_cmd(char *buf, size_t buflen, uint8_t *optdata, } #undef IFCHD_SW_CMD -static int open_ifch(void) { - int sockfd, ret; - struct sockaddr_un address = { - .sun_family = AF_UNIX, - .sun_path = "/var/state/ifchange" - }; - - sockfd = socket(AF_UNIX, SOCK_STREAM, 0); - ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address)); - - if (ret == -1) { - log_error("unable to connect to ifchd!"); - exit(EXIT_FAILURE); - } - - return sockfd; -} - -static void sockwrite(int fd, const char *buf, size_t count) +static void pipewrite(const char *buf, size_t count) { - if (safe_write(fd, buf, count) == -1) - log_error("sockwrite: write failed: %s", strerror(errno)); + if (safe_write(pToIfchW, buf, count) == -1) + log_error("pipewrite: write failed: %s", strerror(errno)); } void ifchange_deconfig(void) { - int sockfd; char buf[256]; if (cfg_deconfig) return; - sockfd = open_ifch(); - - snprintf(buf, sizeof buf, CMD_INTERFACE ":%s;" CMD_IP ":0.0.0.0;", - client_config.interface); + snprintf(buf, sizeof buf, "ip4:0.0.0.0,255.255.255.255;"); log_line("Resetting %s IP configuration.", client_config.interface); - sockwrite(sockfd, buf, strlen(buf)); + pipewrite(buf, strlen(buf)); cfg_deconfig = 1; memset(&cfg_packet, 0, sizeof cfg_packet); - - close(sockfd); } static size_t send_client_ip(char *out, size_t olen, struct dhcpmsg *packet) @@ -288,13 +263,11 @@ static size_t send_cmd(char *out, size_t olen, struct dhcpmsg *packet, void ifchange_bind(struct dhcpmsg *packet) { char buf[2048]; - int sockfd; int tbs = 0; if (!packet) return; - snprintf(buf, sizeof buf, CMD_INTERFACE ":%s;", client_config.interface); tbs |= send_client_ip(buf, sizeof buf, packet); tbs |= send_cmd(buf, sizeof buf, packet, DCODE_ROUTER); tbs |= send_cmd(buf, sizeof buf, packet, DCODE_DNS); @@ -302,11 +275,8 @@ void ifchange_bind(struct dhcpmsg *packet) tbs |= send_cmd(buf, sizeof buf, packet, DCODE_DOMAIN); tbs |= send_cmd(buf, sizeof buf, packet, DCODE_MTU); tbs |= send_cmd(buf, sizeof buf, packet, DCODE_WINS); - if (tbs) { - sockfd = open_ifch(); - sockwrite(sockfd, buf, strlen(buf)); - close(sockfd); - } + if (tbs) + pipewrite(buf, strlen(buf)); cfg_deconfig = 0; memcpy(&cfg_packet, packet, sizeof cfg_packet); diff --git a/ifchd/ifchd-parse.rl b/ndhc/ifchd-parse.rl similarity index 70% rename from ifchd/ifchd-parse.rl rename to ndhc/ifchd-parse.rl index 091dae0..8b76676 100644 --- a/ifchd/ifchd-parse.rl +++ b/ndhc/ifchd-parse.rl @@ -30,11 +30,14 @@ #include #include -#include "ifchd-defines.h" +#include "ifchd.h" #include "log.h" #include "ifch_proto.h" #include "strl.h" -#include "linux.h" +#include "ifset.h" + +// XXX: Move to header +extern struct ifchd_client cl; %%{ machine ipv4set_parser; @@ -74,8 +77,7 @@ %% write data; -static void perform_ip4set(struct ifchd_client *cl, const char *buf, - size_t len) +static void perform_ip4set(const char *buf, size_t len) { char ip4_addr[INET_ADDRSTRLEN]; char ip4_subnet[INET_ADDRSTRLEN]; @@ -108,14 +110,14 @@ static void perform_ip4set(struct ifchd_client *cl, const char *buf, return; } - perform_ip_subnet_bcast(cl, ip4_addr, ip4_subnet, + perform_ip_subnet_bcast(ip4_addr, ip4_subnet, have_bcast ? ip4_bcast : NULL); } %%{ machine ifchd_parser; - action Reset { cl->state = STATE_NOTHING; } + action Reset { cl.state = STATE_NOTHING; } action ArgSt { arg_start = p; } action ArgEn { arg_len = p - arg_start; @@ -128,22 +130,21 @@ static void perform_ip4set(struct ifchd_client *cl, const char *buf, } action Dispatch { - switch (cl->state) { - case STATE_INTERFACE: perform_interface(cl, tb, arg_len); break; - case STATE_IP4SET: perform_ip4set(cl, tb, arg_len); break; - case STATE_IP: perform_ip(cl, tb, arg_len); break; - case STATE_SUBNET: perform_subnet(cl, tb, arg_len); break; - case STATE_TIMEZONE: perform_timezone(cl, tb, arg_len); break; - case STATE_ROUTER: perform_router(cl, tb, arg_len); break; - case STATE_DNS: perform_dns(cl, tb, arg_len); break; - case STATE_LPRSVR: perform_lprsvr(cl, tb, arg_len); break; - case STATE_HOSTNAME: perform_hostname(cl, tb, arg_len); break; - case STATE_DOMAIN: perform_domain(cl, tb, arg_len); break; - case STATE_IPTTL: perform_ipttl(cl, tb, arg_len); break; - case STATE_MTU: perform_mtu(cl, tb, arg_len); break; - case STATE_BROADCAST: perform_broadcast(cl, tb, arg_len); break; - case STATE_NTPSVR: perform_ntpsrv(cl, tb, arg_len); break; - case STATE_WINS: perform_wins(cl, tb, arg_len); break; + switch (cl.state) { + case STATE_IP4SET: perform_ip4set(tb, arg_len); break; + case STATE_IP: perform_ip(tb, arg_len); break; + case STATE_SUBNET: perform_subnet(tb, arg_len); break; + case STATE_TIMEZONE: perform_timezone( tb, arg_len); break; + case STATE_ROUTER: perform_router(tb, arg_len); break; + case STATE_DNS: perform_dns(tb, arg_len); break; + case STATE_LPRSVR: perform_lprsvr(tb, arg_len); break; + case STATE_HOSTNAME: perform_hostname(tb, arg_len); break; + case STATE_DOMAIN: perform_domain(tb, arg_len); break; + case STATE_IPTTL: perform_ipttl(tb, arg_len); break; + case STATE_MTU: perform_mtu(tb, arg_len); break; + case STATE_BROADCAST: perform_broadcast(tb, arg_len); break; + case STATE_NTPSVR: perform_ntpsrv(tb, arg_len); break; + case STATE_WINS: perform_wins(tb, arg_len); break; default: log_line("error: invalid state in dispatch_work"); return -1; @@ -160,24 +161,23 @@ static void perform_ip4set(struct ifchd_client *cl, const char *buf, u16_arg = (extend{2} > ArgSt % ArgEn) terminator; u8_arg = (extend{1} > ArgSt % ArgEn) terminator; - cmd_ip = ('ip:' % { cl->state = STATE_IP; } - |'snet:' % { cl->state = STATE_SUBNET; } - |'routr:' % { cl->state = STATE_ROUTER; } - |'bcast:' % { cl->state = STATE_BROADCAST; } + cmd_ip = ('ip:' % { cl.state = STATE_IP; } + |'snet:' % { cl.state = STATE_SUBNET; } + |'routr:' % { cl.state = STATE_ROUTER; } + |'bcast:' % { cl.state = STATE_BROADCAST; } ) ip_arg; - cmd_ip4set = ('ip4:' % { cl->state = STATE_IP4SET; }) ip4set_arg; - cmd_iplist = ('dns:' % { cl->state = STATE_DNS; } - |'lpr:' % { cl->state = STATE_LPRSVR; } - |'ntp:' % { cl->state = STATE_NTPSVR; } - |'wins:' % { cl->state = STATE_WINS; } + cmd_ip4set = ('ip4:' % { cl.state = STATE_IP4SET; }) ip4set_arg; + cmd_iplist = ('dns:' % { cl.state = STATE_DNS; } + |'lpr:' % { cl.state = STATE_LPRSVR; } + |'ntp:' % { cl.state = STATE_NTPSVR; } + |'wins:' % { cl.state = STATE_WINS; } ) iplist_arg; - cmd_str = ('iface:' % { cl->state = STATE_INTERFACE; } - |'host:' % { cl->state = STATE_HOSTNAME; } - |'dom:' % { cl->state = STATE_DOMAIN; } + cmd_str = ('host:' % { cl.state = STATE_HOSTNAME; } + |'dom:' % { cl.state = STATE_DOMAIN; } ) str_arg; - cmd_s32 = ('tzone:' % { cl->state = STATE_TIMEZONE; }) s32_arg; - cmd_u16 = ('mtu:' % { cl->state = STATE_MTU; }) u16_arg; - cmd_u8 = ('ipttl:' % { cl->state = STATE_IPTTL; }) u8_arg; + cmd_s32 = ('tzone:' % { cl.state = STATE_TIMEZONE; }) s32_arg; + cmd_u16 = ('mtu:' % { cl.state = STATE_MTU; }) u16_arg; + cmd_u8 = ('ipttl:' % { cl.state = STATE_IPTTL; }) u8_arg; command = (cmd_ip|cmd_iplist|cmd_str|cmd_s32|cmd_u16|cmd_u8); main := (command > Reset)+; @@ -188,12 +188,12 @@ static void perform_ip4set(struct ifchd_client *cl, const char *buf, /* * Returns -1 on fatal error; that leads to peer connection being closed. */ -int execute_buffer(struct ifchd_client *cl, char *newbuf) +int execute_buffer(char *newbuf) { char buf[MAX_BUF * 2]; char tb[MAX_BUF]; - if (strnkcpy(buf, cl->ibuf, sizeof buf)) + if (strnkcpy(buf, cl.ibuf, sizeof buf)) goto buftooshort; if (strnkcat(buf, newbuf, sizeof buf)) { buftooshort: @@ -214,7 +214,7 @@ buftooshort: size_t bytes_left = pe - p; if (bytes_left > 0) { size_t taken = init_siz - bytes_left; - strnkcpy(cl->ibuf, buf + taken, MAX_BUF); + strnkcpy(cl.ibuf, buf + taken, MAX_BUF); } if (cs < ifchd_parser_first_final) { diff --git a/ndhc/ifchd.c b/ndhc/ifchd.c new file mode 100644 index 0000000..6fed508 --- /dev/null +++ b/ndhc/ifchd.c @@ -0,0 +1,408 @@ +/* ifchd.c - interface change daemon + * + * Copyright (c) 2004-2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "ifchd.h" +#include "log.h" +#include "chroot.h" +#include "pidfile.h" +#include "signals.h" +#include "ifch_proto.h" +#include "strl.h" +#include "cap.h" +#include "io.h" +#include "ifset.h" +#include "seccomp.h" + +struct ifchd_client cl; + +static int epollfd, signalFd; +/* Slots are for signalFd and the ndhc -> ifchd pipe. */ +static struct epoll_event events[2]; + +int resolv_conf_fd = -1; +/* int ntp_conf_fd = -1; */ + +/* If true, allow HOSTNAME changes from dhcp server. */ +int allow_hostname = 0; + +char pidfile_ifch[MAX_PATH_LENGTH] = PID_FILE_IFCH_DEFAULT; +uid_t ifch_uid = 0; +gid_t ifch_gid = 0; + +// XXX: Move to header +extern int pToIfchR; +extern int pToNdhcW; +extern char chroot_dir[MAX_PATH_LENGTH]; +extern char resolv_conf_d[MAX_PATH_LENGTH]; + +extern int execute_buffer(char *newbuf); + +static void writeordie(int fd, const char *buf, int len) +{ + if (safe_write(fd, buf, len) == -1) + suicide("write returned error"); +} + +static void ep_add(int fd) +{ + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + ev.data.fd = fd; + int r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); + if (r == -1) + suicide("%s failed: %s", __func__, strerror(errno)); +} + +// XXX: Remove dead code. +#if 0 +static void epoll_del(int fd) +{ + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLHUP; + ev.data.fd = fd; + int r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev); + if (r == -1) + suicide("epoll_del failed %s", strerror(errno)); +} +#endif + +/* Writes a new resolv.conf based on the information we have received. */ +static void write_resolve_conf() +{ + const static char ns_str[] = "nameserver "; + const static char dom_str[] = "domain "; + const static char srch_str[] = "search "; + int r; + off_t off; + char buf[MAX_BUF]; + + if (resolv_conf_fd == -1) + return; + if (strlen(cl.namesvrs) == 0) + return; + + if (lseek(resolv_conf_fd, 0, SEEK_SET) == -1) + return; + + char *p = cl.namesvrs; + while (p && (*p != '\0')) { + char *q = strchr(p, ','); + if (!q) + q = strchr(p, '\0'); + else + *q++ = '\0'; + strnkcpy(buf, p, sizeof buf); + + writeordie(resolv_conf_fd, ns_str, strlen(ns_str)); + writeordie(resolv_conf_fd, buf, strlen(buf)); + writeordie(resolv_conf_fd, "\n", 1); + + p = q; + } + + p = cl.domains; + int numdoms = 0; + while (p && (*p != '\0')) { + char *q = strchr(p, ','); + if (!q) + q = strchr(p, '\0'); + else + *q++ = '\0'; + strnkcpy(buf, p, sizeof buf); + + if (numdoms == 0) { + writeordie(resolv_conf_fd, dom_str, strlen(dom_str)); + writeordie(resolv_conf_fd, buf, strlen(buf)); + } else { + if (numdoms == 1) { + writeordie(resolv_conf_fd, "\n", 1); + writeordie(resolv_conf_fd, srch_str, strlen(srch_str)); + writeordie(resolv_conf_fd, buf, strlen(buf)); + } else { + writeordie(resolv_conf_fd, " ", 1); + writeordie(resolv_conf_fd, buf, strlen(buf)); + } + } + + ++numdoms; + p = q; + if (numdoms > 6) + break; + } + writeordie(resolv_conf_fd, "\n", 1); + + off = lseek(resolv_conf_fd, 0, SEEK_CUR); + if (off == -1) { + log_line("write_resolve_conf: lseek returned error: %s", + strerror(errno)); + return; + } + retry: + r = ftruncate(resolv_conf_fd, off); + if (r == -1) { + if (errno == EINTR) + goto retry; + log_line("write_resolve_conf: ftruncate returned error: %s", + strerror(errno)); + return; + } + r = fsync(resolv_conf_fd); + if (r == -1) { + log_line("write_resolve_conf: fsync returned error: %s", + strerror(errno)); + return; + } +} + +/* XXX: addme */ +void perform_timezone(const char *str, size_t len) +{ + log_line("Timezone setting NYI: '%s'", str); +} + +/* Add a dns server to the /etc/resolv.conf -- we already have a fd. */ +void perform_dns(const char *str, size_t len) +{ + if (!str || resolv_conf_fd == -1) + return; + strnkcpy(cl.namesvrs, str, sizeof cl.namesvrs); + write_resolve_conf(); + log_line("Added DNS server: '%s'", str); +} + +/* Updates for print daemons are too non-standard to be useful. */ +void perform_lprsvr(const char *str, size_t len) +{ + log_line("Line printer server setting NYI: '%s'", str); +} + +/* Sets machine hostname. */ +void perform_hostname(const char *str, size_t len) +{ + if (!allow_hostname || !str) + return; + if (sethostname(str, strlen(str) + 1) == -1) + log_line("sethostname returned %s", strerror(errno)); + else + log_line("Set hostname: '%s'", str); +} + +/* update "domain" and "search" in /etc/resolv.conf */ +void perform_domain(const char *str, size_t len) +{ + if (!str || resolv_conf_fd == -1) + return; + strnkcpy(cl.domains, str, sizeof cl.domains); + write_resolve_conf(); + log_line("Added DNS domain: '%s'", str); +} + +/* I don't think this can be done without a netfilter extension + * that isn't in the mainline kernels. */ +void perform_ipttl(const char *str, size_t len) +{ + log_line("TTL setting NYI: '%s'", str); +} + +/* XXX: addme */ +void perform_ntpsrv(const char *str, size_t len) +{ + log_line("NTP server setting NYI: '%s'", str); +} + +/* Maybe Samba cares about this feature? I don't know. */ +void perform_wins(const char *str, size_t len) +{} + +static void ifchd_client_init() +{ + cl.state = STATE_NOTHING; + + memset(cl.ibuf, 0, sizeof cl.ibuf); + memset(cl.namesvrs, 0, sizeof cl.namesvrs); + memset(cl.domains, 0, sizeof cl.domains); +} + +static void setup_signals_ifch() +{ + sigset_t mask; + sigemptyset(&mask); + // XXX: Do we need to change SIGPIPE? + sigaddset(&mask, SIGPIPE); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGTSTP); + sigaddset(&mask, SIGTTIN); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + suicide("sigprocmask failed"); + signalFd = signalfd(-1, &mask, SFD_NONBLOCK); + if (signalFd < 0) + suicide("signalfd failed"); +} + +static void signal_dispatch() +{ + int t, off = 0; + struct signalfd_siginfo si; + again: + t = read(signalFd, (char *)&si + off, sizeof si - off); + if (t < sizeof si - off) { + if (t < 0) { + if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR) + goto again; + else + suicide("signalfd read error"); + } + off += t; + } + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + exit(EXIT_SUCCESS); + break; + case SIGPIPE: + // XXX: Handle SIGPIPE. + break; + default: + break; + } +} + +static void process_client_pipe() +{ + char buf[MAX_BUF]; + + memset(buf, '\0', sizeof buf); + int r = safe_read(pToIfchR, buf, sizeof buf - 1); + if (r == 0) { + // Remote end hung up. + exit(EXIT_SUCCESS); + } else if (r < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; + log_line("ifch: error reading from ndhc -> ifch pipe: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (execute_buffer(buf) == -1) { + log_line("ifch: execute_buffer was passed invalid commands"); + exit(EXIT_FAILURE); + } +} + +void do_ifch_work(void) +{ + epollfd = epoll_create1(0); + if (epollfd == -1) + suicide("epoll_create1 failed"); + + if (enforce_seccomp_ifch()) + log_line("ifch seccomp filter cannot be installed"); + + ifchd_client_init(); + + ep_add(pToIfchR); + ep_add(signalFd); + + for (;;) { + int r = epoll_wait(epollfd, events, 2, -1); + if (r == -1) { + if (errno == EINTR) + continue; + else + suicide("epoll_wait failed"); + } + for (int i = 0; i < r; ++i) { + int fd = events[i].data.fd; + if (fd == pToIfchR) { + process_client_pipe(); + } else if (fd == signalFd) { + signal_dispatch(); + } else { + log_line("ifch: unexpected fd while performing epoll"); + exit(EXIT_FAILURE); + } + } + } +} + +void ifch_main() { + if (file_exists(pidfile_ifch, "w") == -1) { + log_line("FATAL - can't open ifch-pidfile '%s' for write!", + pidfile_ifch); + exit(EXIT_FAILURE); + } + write_pid(pidfile_ifch); + memset(pidfile_ifch, '\0', sizeof pidfile_ifch); + + umask(077); + setup_signals_ifch(); + + // If we are requested to update resolv.conf, preopen the fd before + // we drop root privileges, making sure that if we create + // resolv.conf, it will be world-readable. + if (strncmp(resolv_conf_d, "", sizeof resolv_conf_d)) { + umask(022); + resolv_conf_fd = open(resolv_conf_d, O_RDWR | O_CREAT, 644); + umask(077); + if (resolv_conf_fd == -1) { + suicide("FATAL - unable to open resolv.conf"); + } + } + memset(resolv_conf_d, '\0', sizeof resolv_conf_d); + + imprison(chroot_dir); + memset(chroot_dir, '\0', sizeof chroot_dir); + set_cap(ifch_uid, ifch_gid, "cap_net_admin=ep"); + drop_root(ifch_uid, ifch_gid); + + do_ifch_work(); +} + diff --git a/ndhc/ifchd.h b/ndhc/ifchd.h new file mode 100644 index 0000000..f2fa1ca --- /dev/null +++ b/ndhc/ifchd.h @@ -0,0 +1,45 @@ +#ifndef NJK_IFCHD_H_ +#define NJK_IFCHD_H_ + +#include "ndhc-defines.h" + +enum ifchd_states { + STATE_NOTHING, + STATE_IP4SET, + STATE_IP, + STATE_SUBNET, + STATE_TIMEZONE, + STATE_ROUTER, + STATE_DNS, + STATE_LPRSVR, + STATE_HOSTNAME, + STATE_DOMAIN, + STATE_IPTTL, + STATE_MTU, + STATE_BROADCAST, + STATE_NTPSVR, + STATE_WINS +}; + +#include +struct ifchd_client { + /* Socket fd, current state, and idle time for connection. */ + int state; + /* Per-connection buffer. */ + char ibuf[MAX_BUF]; + /* ' '-delimited buffers of nameservers and domains */ + char namesvrs[MAX_BUF]; + char domains[MAX_BUF]; +}; + +extern void perform_timezone(const char *str, size_t len); +extern void perform_dns(const char *str, size_t len); +extern void perform_lprsvr(const char *str, size_t len); +extern void perform_hostname(const char *str, size_t len); +extern void perform_domain(const char *str, size_t len); +extern void perform_ipttl(const char *str, size_t len); +extern void perform_ntpsrv(const char *str, size_t len); +extern void perform_wins(const char *str, size_t len); + +#endif /* NJK_IFCHD_H_ */ + diff --git a/ifchd/linux.c b/ndhc/ifset.c similarity index 72% rename from ifchd/linux.c rename to ndhc/ifset.c index 50d04c1..92f365a 100644 --- a/ifchd/linux.c +++ b/ndhc/ifset.c @@ -1,4 +1,4 @@ -/* linux.c - ifchd Linux-specific functions +/* ifset.c - Linux-specific net interface settings include * * Copyright (c) 2004-2014 Nicholas J. Kain * All rights reserved. @@ -47,101 +47,35 @@ #include -#include "ifchd-defines.h" +#include "ifchd.h" +#include "config.h" #include "log.h" #include "ifch_proto.h" #include "strl.h" -extern struct ifchd_client clients[SOCK_QUEUE]; - -static size_t numokif; -static char okif[MAX_IFACES][IFNAMSIZ]; - -/* Adds to the list of interface names ifchd clients are allowed to change. */ -void add_permitted_if(char *s) -{ - if (numokif >= MAX_IFACES) - return; - strnkcpy(okif[numokif++], s, IFNAMSIZ); -} - -/* Checks if changes are permitted to a given interface. 1 == allowed */ -static int is_permitted(char *name) -{ - /* If empty, permit all. */ - if (!numokif) - return 1; - - if (!name || strlen(name) == 0) - return 0; - for (size_t i = 0; i < numokif; ++i) { - if (strcmp(name, okif[i]) == 0) - return 1; - } - log_line("attempt to modify interface %s denied", name); - return 0; -} - -/* Verify that peer is authorized to connect (return 1 on success). */ -int authorized_peer(int sk, pid_t pid, uid_t uid, gid_t gid) -{ - int ret = 0; - unsigned int cl; - struct ucred cr; - - /* No credentials to verify. */ - if ( !(pid || uid || gid) ) - return 1; - - /* Verify that peer has authorized uid/gid/pid. */ - cl = sizeof(struct ucred); - if (getsockopt(sk, SOL_SOCKET, SO_PEERCRED, &cr, &cl) != -1) { - if ((pid == 0 || cr.pid == pid) || - (uid == 0 || cr.uid == uid) || - (gid == 0 || cr.gid == gid)) - ret = 1; - } else - log_line("getsockopt returned an error: %s", strerror(errno)); - return ret; -} - -void perform_interface(struct ifchd_client *cl, const char *str, size_t len) -{ - if (!str) - return; - - /* Update interface name. */ - memset(cl->ifnam, '\0', IFNAMSIZ); - strnkcpy(cl->ifnam, str, IFNAMSIZ); - log_line("Subsequent commands alter interface: '%s'", str); -} - -static int set_if_flag(struct ifchd_client *cl, short flag) +static int set_if_flag(short flag) { int fd, ret = -1; struct ifreq ifrt; - if (!is_permitted(cl->ifnam)) - goto out0; - fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_line("%s: (set_if_flag) failed to open interface socket: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); goto out0; } - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); if (ioctl(fd, SIOCGIFFLAGS, &ifrt) < 0) { - log_line("%s: unknown interface: %s", cl->ifnam, strerror(errno)); + log_line("%s: unknown interface: %s", client_config.interface, strerror(errno)); goto out1; } if (((ifrt.ifr_flags & flag ) ^ flag) & flag) { - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); ifrt.ifr_flags |= flag; if (ioctl(fd, SIOCSIFFLAGS, &ifrt) < 0) { log_line("%s: failed to set interface flags: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); goto out1; } } else @@ -218,7 +152,7 @@ static inline int subnet4_to_prefixlen(uint32_t sn) } // str_bcast is optional. -void perform_ip_subnet_bcast(struct ifchd_client *cl, const char *str_ipaddr, +void perform_ip_subnet_bcast(const char *str_ipaddr, const char *str_subnet, const char *str_bcast) { uint8_t request[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + @@ -233,33 +167,31 @@ void perform_ip_subnet_bcast(struct ifchd_client *cl, const char *str_ipaddr, if (!str_ipaddr) { log_line("%s: (%s) interface ip address is NULL", - cl->ifnam, __func__); + client_config.interface, __func__); return; } if (!str_subnet) { log_line("%s: (%s) interface subnet address is NULL", - cl->ifnam, __func__); + client_config.interface, __func__); return; } - if (!is_permitted(cl->ifnam)) - return; - ifidx = get_ifindex(cl->ifnam); + ifidx = get_ifindex(client_config.interface); if (ifidx < 0) { log_line("%s: (%s) can't get interface index", - cl->ifnam, __func__); + client_config.interface, __func__); return; } if (inet_pton(AF_INET, str_ipaddr, &ipaddr) <= 0) { log_line("%s: (%s) bad interface ip address: '%s'", - cl->ifnam, __func__, str_ipaddr); + client_config.interface, __func__, str_ipaddr); return; } if (inet_pton(AF_INET, str_subnet, &subnet) <= 0) { log_line("%s: (%s) bad interface subnet address: '%s'", - cl->ifnam, __func__, str_subnet); + client_config.interface, __func__, str_subnet); return; } prefixlen = subnet4_to_prefixlen(subnet.s_addr); @@ -267,7 +199,7 @@ void perform_ip_subnet_bcast(struct ifchd_client *cl, const char *str_ipaddr, if (str_bcast) { if (inet_pton(AF_INET, str_bcast, &bcast) <= 0) { log_line("%s: (%s) bad interface broadcast address: '%s'", - cl->ifnam, __func__, str_bcast); + client_config.interface, __func__, str_bcast); return; } } else { @@ -292,20 +224,20 @@ void perform_ip_subnet_bcast(struct ifchd_client *cl, const char *str_ipaddr, if (add_rtattr(header, sizeof request, IFA_LOCAL, &ipaddr, sizeof ipaddr) < 0) { log_line("%s: (%s) couldn't add IFA_LOCAL to nlmsg", - cl->ifnam, __func__); + client_config.interface, __func__); return; } if (add_rtattr(header, sizeof request, IFA_BROADCAST, &bcast, sizeof bcast) < 0) { log_line("%s: (%s) couldn't add IFA_BROADCAST to nlmsg", - cl->ifnam, __func__); + client_config.interface, __func__); return; } nls = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); if (nls < 0) { log_line("%s: (%s) netlink socket open failed: %s", - cl->ifnam, __func__, strerror(errno)); + client_config.interface, __func__, strerror(errno)); return; } @@ -320,7 +252,7 @@ retry_sendto: goto retry_sendto; else { log_line("%s: (%s) netlink sendto socket failed: %s", - cl->ifnam, __func__, strerror(errno)); + client_config.interface, __func__, strerror(errno)); close(nls); return; } @@ -332,13 +264,13 @@ retry_sendto: log_line("Broadcast address set to: '%s'", str_bcast); // XXX: Would be nice to do this via netlink, too. - if (set_if_flag(cl, (IFF_UP | IFF_RUNNING))) + if (set_if_flag(IFF_UP | IFF_RUNNING)) return; } /* Sets IP address on an interface and brings it up. */ -void perform_ip(struct ifchd_client *cl, const char *str, size_t len) +void perform_ip(const char *str, size_t len) { int fd; struct in_addr ipaddr; @@ -347,14 +279,12 @@ void perform_ip(struct ifchd_client *cl, const char *str, size_t len) if (!str) return; - if (!is_permitted(cl->ifnam)) - return; if (inet_pton(AF_INET, str, &ipaddr) <= 0) return; - if (set_if_flag(cl, (IFF_UP | IFF_RUNNING))) + if (set_if_flag(IFF_UP | IFF_RUNNING)) return; - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); memset(&sin, 0, sizeof(struct sockaddr)); sin.sin_family = AF_INET; sin.sin_addr = ipaddr; @@ -363,19 +293,19 @@ void perform_ip(struct ifchd_client *cl, const char *str, size_t len) fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_line("%s: (perform_ip) failed to open interface socket: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); return; } if (ioctl(fd, SIOCSIFADDR, &ifrt) < 0) log_line("%s: failed to configure IP: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); else log_line("Interface IP set to: '%s'", str); close(fd); } /* Sets the subnet mask on an interface. */ -void perform_subnet(struct ifchd_client *cl, const char *str, size_t len) +void perform_subnet(const char *str, size_t len) { int fd; struct in_addr subnet; @@ -384,12 +314,10 @@ void perform_subnet(struct ifchd_client *cl, const char *str, size_t len) if (!str) return; - if (!is_permitted(cl->ifnam)) - return; if (inet_pton(AF_INET, str, &subnet) <= 0) return; - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); memset(&sin, 0, sizeof(struct sockaddr)); sin.sin_family = AF_INET; sin.sin_addr = subnet; @@ -398,20 +326,20 @@ void perform_subnet(struct ifchd_client *cl, const char *str, size_t len) fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_line("%s: (perform_ip) failed to open interface socket: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); return; } if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0) { sin.sin_addr.s_addr = 0xffffffff; if (ioctl(fd, SIOCSIFNETMASK, &ifrt) < 0) log_line("%s: failed to configure subnet: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); } else log_line("Interface subnet set to: '%s'", str); close(fd); } -void perform_router(struct ifchd_client *cl, const char *str, size_t len) +void perform_router(const char *str, size_t len) { struct rtentry rt; struct sockaddr_in *dest; @@ -422,8 +350,6 @@ void perform_router(struct ifchd_client *cl, const char *str, size_t len) if (!str) return; - if (!is_permitted(cl->ifnam)) - return; if (inet_pton(AF_INET, str, &router) <= 0) return; @@ -440,25 +366,25 @@ void perform_router(struct ifchd_client *cl, const char *str, size_t len) rt.rt_flags = RTF_UP | RTF_GATEWAY; if (mask->sin_addr.s_addr == 0xffffffff) rt.rt_flags |= RTF_HOST; - rt.rt_dev = cl->ifnam; + rt.rt_dev = client_config.interface; rt.rt_metric = 1; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_line("%s: (perform_router) failed to open interface socket: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); return; } if (ioctl(fd, SIOCADDRT, &rt)) { if (errno != EEXIST) log_line("%s: failed to set route: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); } else log_line("Gateway router set to: '%s'", str); close(fd); } -void perform_mtu(struct ifchd_client *cl, const char *str, size_t len) +void perform_mtu(const char *str, size_t len) { int fd; unsigned int mtu; @@ -466,31 +392,29 @@ void perform_mtu(struct ifchd_client *cl, const char *str, size_t len) if (!str) return; - if (!is_permitted(cl->ifnam)) - return; mtu = strtol(str, NULL, 10); // Minimum MTU for physical IPv4 links is 576 octets. if (mtu < 576) return; ifrt.ifr_mtu = mtu; - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { log_line("%s: (perform_mtu) failed to open interface socket: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); return; } if (ioctl(fd, SIOCSIFMTU, &ifrt) < 0) - log_line("%s: failed to set MTU (%d): %s", cl->ifnam, mtu, + log_line("%s: failed to set MTU (%d): %s", client_config.interface, mtu, strerror(errno)); else log_line("MTU set to: '%s'", str); close(fd); } -void perform_broadcast(struct ifchd_client *cl, const char *str, size_t len) +void perform_broadcast(const char *str, size_t len) { int fd; struct in_addr broadcast; @@ -499,12 +423,10 @@ void perform_broadcast(struct ifchd_client *cl, const char *str, size_t len) if (!str) return; - if (!is_permitted(cl->ifnam)) - return; if (inet_pton(AF_INET, str, &broadcast) <= 0) return; - strnkcpy(ifrt.ifr_name, cl->ifnam, IFNAMSIZ); + strnkcpy(ifrt.ifr_name, client_config.interface, IFNAMSIZ); memset(&sin, 0, sizeof(struct sockaddr)); sin.sin_family = AF_INET; sin.sin_addr = broadcast; @@ -512,12 +434,12 @@ void perform_broadcast(struct ifchd_client *cl, const char *str, size_t len) fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { - log_line("%s: (perform_broadcast) failed to open interface socket: %s", cl->ifnam, strerror(errno)); + log_line("%s: (perform_broadcast) failed to open interface socket: %s", client_config.interface, strerror(errno)); return; } if (ioctl(fd, SIOCSIFBRDADDR, &ifrt) < 0) log_line("%s: failed to set broadcast: %s", - cl->ifnam, strerror(errno)); + client_config.interface, strerror(errno)); else log_line("Broadcast address set to: '%s'", str); close(fd); diff --git a/ifchd/linux.h b/ndhc/ifset.h similarity index 59% rename from ifchd/linux.h rename to ndhc/ifset.h index 364de21..f9efc80 100644 --- a/ifchd/linux.h +++ b/ndhc/ifset.h @@ -1,4 +1,4 @@ -/* linux.h - ifchd Linux-specific functions include +/* ifset.h - Linux-specific net interface settings include * * Copyright (c) 2004-2014 Nicholas J. Kain * All rights reserved. @@ -26,25 +26,15 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef NJK_IFCHD_LINUX_H_ -#define NJK_IFCHD_LINUX_H_ -extern void clear_if_data(struct ifchd_client *cl); -extern void initialize_if_data(void); -extern void add_permitted_if(char *s); -extern int authorized_peer(int sk, pid_t pid, uid_t uid, gid_t gid); -extern void perform_interface(struct ifchd_client *cl, const char *str, - size_t len); -extern void perform_ip_subnet_bcast(struct ifchd_client *cl, - const char *str_ipaddr, +#ifndef NJK_IFSET_H_ +#define NJK_IFSET_H_ +extern void perform_ip_subnet_bcast(const char *str_ipaddr, const char *str_subnet, const char *str_bcast); -extern void perform_ip(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_subnet(struct ifchd_client *cl, const char *str, - size_t len); -extern void perform_router(struct ifchd_client *cl, const char *str, - size_t len); -extern void perform_mtu(struct ifchd_client *cl, const char *str, size_t len); -extern void perform_broadcast(struct ifchd_client *cl, const char *str, - size_t len); +extern void perform_ip(const char *str, size_t len); +extern void perform_subnet(const char *str, size_t len); +extern void perform_router(const char *str, size_t len); +extern void perform_mtu(const char *str, size_t len); +extern void perform_broadcast(const char *str, size_t len); #endif diff --git a/ndhc/ndhc-defines.h b/ndhc/ndhc-defines.h index 5cd83cd..c374d69 100644 --- a/ndhc/ndhc-defines.h +++ b/ndhc/ndhc-defines.h @@ -4,7 +4,8 @@ #include "defines.h" #define PID_FILE_DEFAULT "/var/run/ndhc.pid" -#define NDHC_VERSION "1.1" +#define PID_FILE_IFCH_DEFAULT "/var/run/ifchd.pid" +#define NDHC_VERSION "1.5" #define MAX_BUF 1024 #endif /* NDHC_DEFINES_H_ */ diff --git a/ndhc/ndhc.8 b/ndhc/ndhc.8 index adaa0eb..095ec17 100644 --- a/ndhc/ndhc.8 +++ b/ndhc/ndhc.8 @@ -39,9 +39,13 @@ Do not fork into the background after obtaining a lease. Immediately fork into the background, even before obtaining a lease. .TP .BI \-p\ PIDFILE ,\ \-\-pidfile= PIDFILE -Write the process id number of the ndhc instance into the specified file name. +Write the process id number of the ndhc process into the specified file name. The default is to not write the process id number into any file at all. .TP +.BI \-P\ PIDFILE ,\ \-\-ifch\-pidfile= PIDFILE +Write the process id number of the ndhc-ifch process into the specified file +name. The default is to not write the process id number into any file at all. +.TP .BI \-l\ LEASEFILE ,\ \-\-leasefile= LEASEFILE Write the IP address of the currently held DHCP lease into the specified file name. The default is to not write the lease IP address into any file at all. @@ -67,22 +71,65 @@ request if it would not conflict with another host. .BI \-u\ USER ,\ \-\-user= USER This option specifies the user name or user id that ndhc will change to after startup. ndhc will also change its group to match the default group of this -user. This user should have the ability to write to the ifchd socket of the -associated ifchd daemon. In practice, this requirement means that this user -should belong to the same group as the ifchd user. +user. +.TP +.BI \-U\ USER ,\ \-\-ifch\-user= USER +This option specifies the user name or user id that ndhc-ifch will change to +after startup. ndhc-ifch will also change its group to match the default group +of this user. .TP .BI \-C\ CHROOTDIR ,\ \-\-chroot= CHROOTDIR This option specifies the directory to which ndhc should confine itself via -chroot() after startup. This directory should be shared with the associated -ifchd daemon, and should have access to dev/urandom and dev/null. For -logging to work, a dev/log socket or device should also exist. +chroot() after startup. This directory should have access to dev/urandom and +dev/null. For logging to work, a dev/log socket or device should also exist. .TP -.BR \-d ,\ \-\-relentless-defense +.BR \-d ,\ \-\-relentless\-defense If specified, ndhc will never back down in defending the IP address that it has been assigned by the remote DHCP server. This behavior should not be specified for average machines, but is useful for servers or routers where the IP address of the machine must remain fixed for proper operation. .TP +.TP +.BI \-R\ RESOLVCONF ,\ \-\-resolv\-conf= RESOLVCONF +Specifies the path to the system resolv.conf. This file will typically be in +/etc/resolv.conf. If this option is specified, ndhc will update the contents +of this file to match the DNS servers specified by the remote DHCP server. If +this option is not specified, ifchd will never change the system DNS resolution +configuration. +.TP +.BI \-H ,\ \-\-dhcp\-set\-hostname +If specified, ndhc will update the system host name in response to any +hostname option field provided by a remote DHCP server on the request of +a ndhc client. If this option is not specified, ndhc will never change +the system hostname. +.BR \-S ,\ \-\-seccomp\-enforce +Enforces seccomp-based syscall whitelisting. System calls that ndhc and +ndhc-ifch are not expected to need are prohibited from being called if this +flag is set. The lists of allowed syscalls are hardcoded, and attempts +to call a non-listed syscall will result in the ndhc process being +terminated. As systems vary, it cannot be guaranteed that these system +call lists are accurate for your system, and thus seccomp filtering will +not be used unless this flag is set. +.TP +.BR \-w\ TIMEMS ,\ \-\-arp\-probe\-wait= TIMEMS +Adjusts the time that we wait for an ARP response when checking to see if +our lease assignment is already taken by an existing host. Default is +1000ms. +.TP +.BR \-W\ NUMPROBES ,\ \-\-arp\-probe\-num= NUMPROBES +Adjusts the number of ARP packets that we send when probing for collisions +with an existing host that is using our assigned IP. Once we have sent +the specified number of probe packets with no response, ndhc is willing +to believe that there is no colliding host. Default number is 3 probes. +.TP +.BR \-m\ TIMEMS ,\ \-\-arp\-probe\-min= TIMEMS +Adjusts the minimum time that we wait between sending probe packets. The +default is 1000ms. The precise inter-probe wait time is randomized. +.TP +.BR \-M\ TIMEMS ,\ \-\-arp\-probe\-max= TIMEMS +Adjusts the maximum time that we wait between sending probe packets. The +default is 2000ms. The precise inter-probe wait time is randomized. +.TP .BR \-v ,\ \-\-version Display the ndhc version number. .SH SIGNALS diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index 749c8be..3b8bb47 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -65,7 +66,7 @@ #include "strl.h" #include "pidfile.h" #include "io.h" -#include "seccomp-bpf.h" +#include "seccomp.h" struct client_state_t cs = { .init = 1, @@ -96,89 +97,31 @@ static void show_usage(void) " -f, --foreground Do not fork after getting lease\n" " -b, --background Fork to background if lease cannot be\n" " immediately negotiated.\n" -" -p, --pidfile=FILE File to which the pid will be written\n" +" -p, --pidfile=FILE File where the ndhc pid will be written\n" +" -P, --ifch-pidfile=FILE File where the ndhc-ifch pid will be written\n" " -l, --leasefile=FILE File to which the lease IP will be written\n" " -i, --interface=INTERFACE Interface to use (default: eth0)\n" " -n, --now Exit with failure if lease cannot be\n" " immediately negotiated.\n" " -q, --quit Quit after obtaining lease\n" " -r, --request=IP IP address to request (default: none)\n" -" -u, --user=USER Change privileges to this user\n" +" -u, --user=USER Change ndhc privileges to this user\n" +" -U, --ifch-user=USER Change ndhc-ifch privileges to this user\n" " -C, --chroot=DIR Chroot to this directory\n" +" -S, --seccomp-enforce Enforce seccomp syscall restrictions\n" " -d, --relentless-defense Never back off in defending IP against\n" " conflicting hosts (servers only)\n" " -w, --arp-probe-wait Time to delay before first ARP probe\n" " -W, --arp-probe-num Number of ARP probes before lease is ok\n" " -m, --arp-probe-min Min ms to wait for ARP response\n" " -M, --arp-probe-max Max ms to wait for ARP response\n" +" -R, --resolve-conf=FILE Path to resolv.conf or equivalent\n" +" -H, --dhcp-hostname Allow DHCP to set machine hostname\n" " -v, --version Display version\n" ); exit(EXIT_SUCCESS); } -static int enforce_seccomp(void) -{ - struct sock_filter filter[] = { - VALIDATE_ARCHITECTURE, - EXAMINE_SYSCALL, - ALLOW_SYSCALL(sendto), // used for glibc syslog routines - ALLOW_SYSCALL(epoll_wait), - ALLOW_SYSCALL(epoll_ctl), - ALLOW_SYSCALL(read), - ALLOW_SYSCALL(write), - ALLOW_SYSCALL(close), - ALLOW_SYSCALL(recvmsg), - ALLOW_SYSCALL(socket), - ALLOW_SYSCALL(setsockopt), - ALLOW_SYSCALL(fcntl), - ALLOW_SYSCALL(bind), - ALLOW_SYSCALL(open), - ALLOW_SYSCALL(connect), - ALLOW_SYSCALL(getsockname), - - // Allowed by vDSO - ALLOW_SYSCALL(getcpu), - ALLOW_SYSCALL(time), - ALLOW_SYSCALL(gettimeofday), - ALLOW_SYSCALL(clock_gettime), - - // These are for 'write_leasefile()' - ALLOW_SYSCALL(ftruncate), - ALLOW_SYSCALL(lseek), - ALLOW_SYSCALL(fsync), - - // These are for 'background()' - ALLOW_SYSCALL(socketpair), - ALLOW_SYSCALL(clone), - ALLOW_SYSCALL(set_robust_list), - ALLOW_SYSCALL(setsid), - ALLOW_SYSCALL(chdir), - ALLOW_SYSCALL(fstat), - ALLOW_SYSCALL(dup2), - ALLOW_SYSCALL(rt_sigprocmask), - ALLOW_SYSCALL(signalfd4), - ALLOW_SYSCALL(mmap), - ALLOW_SYSCALL(munmap), - - ALLOW_SYSCALL(rt_sigreturn), -#ifdef __NR_sigreturn - ALLOW_SYSCALL(sigreturn), -#endif - ALLOW_SYSCALL(exit_group), - ALLOW_SYSCALL(exit), - KILL_PROCESS, - }; - struct sock_fprog prog = { - .len = (unsigned short)(sizeof filter / sizeof filter[0]), - .filter = filter, - }; - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) - return -1; - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) - return -1; - return 0; -} - static void signal_dispatch() { int t, off = 0; @@ -236,7 +179,7 @@ static int get_clientid_mac_string(char *str, size_t slen) return 1; } -static void do_work(void) +static void do_ndhc_work(void) { struct epoll_event events[3]; long long nowts; @@ -246,10 +189,10 @@ static void do_work(void) if (cs.epollFd == -1) suicide("epoll_create1 failed"); - if (enforce_seccomp()) - log_line("seccomp filter cannot be installed"); + if (enforce_seccomp_ndhc()) + log_line("ndhc seccomp filter cannot be installed"); - setup_signals(&cs); + setup_signals_ndhc(&cs); epoll_add(&cs, cs.nlFd); set_listen_raw(&cs); @@ -306,41 +249,113 @@ jumpstart: } } +char chroot_dir[MAX_PATH_LENGTH] = ""; +char resolv_conf_d[MAX_PATH_LENGTH] = ""; +static uid_t ndhc_uid = 0; +static gid_t ndhc_gid = 0; +int pToNdhcR; +int pToNdhcW; +int pToIfchR; +int pToIfchW; + +static void create_ipc_pipes() { + int niPipe[2]; + int inPipe[2]; + + if (pipe2(niPipe, O_NONBLOCK)) { + log_line("FATAL - can't create ndhc -> ndhc-ifch pipe: %s", + strerror(errno)); + exit(EXIT_FAILURE); + } + pToNdhcR = niPipe[0]; + pToNdhcW = niPipe[1]; + if (pipe2(inPipe, O_NONBLOCK)) { + log_line("FATAL - can't create ndhc-ifch -> ndhc pipe: %s", + strerror(errno)); + exit(EXIT_FAILURE); + } + pToIfchR = inPipe[0]; + pToIfchW = inPipe[1]; +} + +void ndhc_main() { + log_line("ndhc client " NDHC_VERSION " started on interface [%s].", + client_config.interface); + + if (client_config.foreground && !client_config.background_if_no_lease) { + if (file_exists(pidfile, "w") == -1) { + log_line("FATAL - can't open pidfile '%s' for write!", pidfile); + exit(EXIT_FAILURE); + } + write_pid(pidfile); + } + + if ((cs.nlFd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, &cs.nlPortId)) < 0) { + log_line("FATAL - failed to open netlink socket"); + exit(EXIT_FAILURE); + } + if (nl_getifdata(&cs) < 0) { + log_line("FATAL - failed to get interface MAC and index"); + exit(EXIT_FAILURE); + } + + open_leasefile(); + + imprison(chroot_dir); + memset(chroot_dir, '\0', sizeof chroot_dir); + + set_cap(ndhc_uid, ndhc_gid, + "cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); + drop_root(ndhc_uid, ndhc_gid); + + if (cs.ifsPrevState != IFS_UP) + ifchange_deconfig(); + + do_ndhc_work(); +} + +// XXX: Move to a header. +extern int allow_hostname; +extern char pidfile_ifch[MAX_PATH_LENGTH]; +extern uid_t ifch_uid; +extern gid_t ifch_gid; +extern void ifch_main(); + int main(int argc, char **argv) { - char chroot_dir[MAX_PATH_LENGTH] = ""; - int c; - struct passwd *pwd; - uid_t uid = 0; - gid_t gid = 0; static const struct option arg_options[] = { - {"clientid", required_argument, 0, 'c'}, - {"foreground", no_argument, 0, 'f'}, - {"background", no_argument, 0, 'b'}, - {"pidfile", required_argument, 0, 'p'}, - {"leasefile", required_argument, 0, 'l'}, - {"hostname", required_argument, 0, 'h'}, - {"interface", required_argument, 0, 'i'}, - {"now", no_argument, 0, 'n'}, - {"quit", no_argument, 0, 'q'}, - {"request", required_argument, 0, 'r'}, - {"vendorid", required_argument, 0, 'V'}, - {"user", required_argument, 0, 'u'}, - {"chroot", required_argument, 0, 'C'}, - {"relentless-defense", no_argument, 0, 'd'}, - {"arp-probe-wait", required_argument, 0, 'w'}, - {"arp-probe-num", required_argument, 0, 'W'}, - {"arp-probe-min", required_argument, 0, 'm'}, - {"arp-probe-max", required_argument, 0, 'M'}, - {"version", no_argument, 0, 'v'}, - {"help", no_argument, 0, '?'}, + {"clientid", required_argument, 0, 'c'}, + {"foreground", no_argument, 0, 'f'}, + {"background", no_argument, 0, 'b'}, + {"pidfile", required_argument, 0, 'p'}, + {"ifch-pidfile", required_argument, 0, 'P'}, + {"leasefile", required_argument, 0, 'l'}, + {"hostname", required_argument, 0, 'h'}, + {"interface", required_argument, 0, 'i'}, + {"now", no_argument, 0, 'n'}, + {"quit", no_argument, 0, 'q'}, + {"request", required_argument, 0, 'r'}, + {"vendorid", required_argument, 0, 'V'}, + {"user", required_argument, 0, 'u'}, + {"ifch-user", required_argument, 0, 'U'}, + {"chroot", required_argument, 0, 'C'}, + {"seccomp-enforce", no_argument, 0, 'S'}, + {"relentless-defense", no_argument, 0, 'd'}, + {"arp-probe-wait", required_argument, 0, 'w'}, + {"arp-probe-num", required_argument, 0, 'W'}, + {"arp-probe-min", required_argument, 0, 'm'}, + {"arp-probe-max", required_argument, 0, 'M'}, + {"resolv-conf", required_argument, 0, 'R'}, + {"dhcp-hostname", no_argument, 0, 'H'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, '?'}, {0, 0, 0, 0} }; while (1) { - int option_index = 0; - c = getopt_long(argc, argv, "c:fbp:h:i:np:l:qr:V:u:C:dw:W:m:M:v", - arg_options, &option_index); + int c; + c = getopt_long(argc, argv, "c:fbp:P:l:h:i:nqr:V:u:U:CS:dw:W:m:M:R:Hv?", + arg_options, NULL); if (c == -1) break; switch (c) { @@ -362,6 +377,9 @@ int main(int argc, char **argv) case 'p': strnkcpy(pidfile, optarg, sizeof pidfile); break; + case 'P': + strnkcpy(pidfile_ifch, optarg, sizeof pidfile_ifch); + break; case 'l': set_leasefile(optarg); break; @@ -381,19 +399,46 @@ int main(int argc, char **argv) case 'r': cs.clientAddr = inet_addr(optarg); break; - case 'u': - pwd = getpwnam(optarg); + case 'u': { + struct passwd *pwd; + char *p; + uid_t uidt = strtol(optarg, &p, 10); + if (*p != '\0') + pwd = getpwnam(optarg); + else + pwd = getpwuid(uidt); if (pwd) { - uid = (int)pwd->pw_uid; - gid = (int)pwd->pw_gid; + ndhc_uid = (int)pwd->pw_uid; + ndhc_gid = (int)pwd->pw_gid; } else { - printf("Bad username provided.\n"); + printf("Bad username provided to '-u'.\n"); exit(EXIT_FAILURE); } break; + } + case 'U': { + struct passwd *pwd; + char *p; + uid_t uidt = strtol(optarg, &p, 10); + if (*p != '\0') + pwd = getpwnam(optarg); + else + pwd = getpwuid(uidt); + if (pwd) { + ifch_uid = (int)pwd->pw_uid; + ifch_gid = (int)pwd->pw_gid; + } else { + printf("Bad username provided to '-U'.\n"); + exit(EXIT_FAILURE); + } + break; + } case 'C': strnkcpy(chroot_dir, optarg, sizeof chroot_dir); break; + case 'S': + seccomp_enforce = true; + break; case 'd': arp_relentless_def = 1; break; @@ -424,7 +469,7 @@ int main(int argc, char **argv) } case 'v': printf("ndhc %s, dhcp client.\n", NDHC_VERSION); - printf("Copyright (c) 2004-2013 Nicholas J. Kain\n" + printf("Copyright (c) 2004-2014 Nicholas J. Kain\n" "All rights reserved.\n\n" "Redistribution and use in source and binary forms, with or without\n" "modification, are permitted provided that the following conditions are met:\n\n" @@ -450,49 +495,36 @@ int main(int argc, char **argv) strnkcpy(client_config.vendor, optarg, sizeof client_config.vendor); break; + case 'R': + strnkcpy(resolv_conf_d, optarg, sizeof resolv_conf_d); + break; + case 'H': + allow_hostname = 1; + break; default: show_usage(); } } - log_line("ndhc client " NDHC_VERSION " started."); + if (getuid()) + suicide("FATAL - I need to be started as root."); + if (!strncmp(chroot_dir, "", sizeof chroot_dir)) + suicide("FATAL - No chroot path specified. Refusing to run."); - if (client_config.foreground && !client_config.background_if_no_lease) { - if (file_exists(pidfile, "w") == -1) { - log_line("FATAL - cannot open pidfile for write!"); - exit(EXIT_FAILURE); - } - write_pid(pidfile); - } - - if ((cs.nlFd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, &cs.nlPortId)) < 0) { - log_line("FATAL - failed to open netlink socket"); + create_ipc_pipes(); + pid_t ifch_pid = fork(); + if (ifch_pid == 0) { + close(pToNdhcR); + close(pToIfchW); + ifch_main(); + } else if (ifch_pid > 0) { + close(pToIfchR); + close(pToNdhcW); + ndhc_main(); + } else { + log_line("FATAL - failed to fork ndhc-ifch: %s", strerror(errno)); exit(EXIT_FAILURE); } - if (nl_getifdata(&cs) < 0) { - log_line("FATAL - failed to get interface MAC and index"); - exit(EXIT_FAILURE); - } - - open_leasefile(); - - if (chdir(chroot_dir)) { - printf("Failed to chdir(%s)!\n", chroot_dir); - exit(EXIT_FAILURE); - } - - if (chroot(chroot_dir)) { - printf("Failed to chroot(%s)!\n", chroot_dir); - exit(EXIT_FAILURE); - } - - set_cap(uid, gid, - "cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); - drop_root(uid, gid); - - if (cs.ifsPrevState != IFS_UP) - ifchange_deconfig(); - - do_work(); - return EXIT_SUCCESS; // Never reached. + exit(EXIT_SUCCESS); } + diff --git a/ndhc/seccomp.c b/ndhc/seccomp.c new file mode 100644 index 0000000..968dc0a --- /dev/null +++ b/ndhc/seccomp.c @@ -0,0 +1,153 @@ +/* seccomp.h - seccomp syscall filters for ndhc + * + * Copyright (c) 2012-2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include "log.h" +#include "seccomp-bpf.h" + +bool seccomp_enforce = false; + +int enforce_seccomp_ndhc(void) +{ + if (!seccomp_enforce) + return 0; + struct sock_filter filter[] = { + VALIDATE_ARCHITECTURE, + EXAMINE_SYSCALL, + ALLOW_SYSCALL(sendto), // used for glibc syslog routines + ALLOW_SYSCALL(epoll_wait), + ALLOW_SYSCALL(epoll_ctl), + ALLOW_SYSCALL(read), + ALLOW_SYSCALL(write), + ALLOW_SYSCALL(close), + ALLOW_SYSCALL(recvmsg), + ALLOW_SYSCALL(socket), + ALLOW_SYSCALL(setsockopt), + ALLOW_SYSCALL(fcntl), + ALLOW_SYSCALL(bind), + ALLOW_SYSCALL(open), + ALLOW_SYSCALL(connect), + ALLOW_SYSCALL(getsockname), + + // Allowed by vDSO + ALLOW_SYSCALL(getcpu), + ALLOW_SYSCALL(time), + ALLOW_SYSCALL(gettimeofday), + ALLOW_SYSCALL(clock_gettime), + + // These are for 'write_leasefile()' + ALLOW_SYSCALL(ftruncate), + ALLOW_SYSCALL(lseek), + ALLOW_SYSCALL(fsync), + + // These are for 'background()' + ALLOW_SYSCALL(socketpair), + ALLOW_SYSCALL(clone), + ALLOW_SYSCALL(set_robust_list), + ALLOW_SYSCALL(setsid), + ALLOW_SYSCALL(chdir), + ALLOW_SYSCALL(fstat), + ALLOW_SYSCALL(dup2), + ALLOW_SYSCALL(rt_sigprocmask), + ALLOW_SYSCALL(signalfd4), + ALLOW_SYSCALL(mmap), + ALLOW_SYSCALL(munmap), + + ALLOW_SYSCALL(rt_sigreturn), +#ifdef __NR_sigreturn + ALLOW_SYSCALL(sigreturn), +#endif + ALLOW_SYSCALL(exit_group), + ALLOW_SYSCALL(exit), + KILL_PROCESS, + }; + struct sock_fprog prog = { + .len = (unsigned short)(sizeof filter / sizeof filter[0]), + .filter = filter, + }; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + return -1; + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + return -1; + log_line("ndhc seccomp filter installed. Please disable seccomp if you encounter problems."); + return 0; +} + +int enforce_seccomp_ifch(void) +{ + if (!seccomp_enforce) + return 0; + struct sock_filter filter[] = { + VALIDATE_ARCHITECTURE, + EXAMINE_SYSCALL, + ALLOW_SYSCALL(read), + ALLOW_SYSCALL(write), + ALLOW_SYSCALL(sendto), + ALLOW_SYSCALL(epoll_wait), + ALLOW_SYSCALL(epoll_ctl), + ALLOW_SYSCALL(close), + ALLOW_SYSCALL(socket), + ALLOW_SYSCALL(ioctl), + ALLOW_SYSCALL(getsockname), + ALLOW_SYSCALL(open), + ALLOW_SYSCALL(fstat), + ALLOW_SYSCALL(connect), + ALLOW_SYSCALL(recvmsg), + ALLOW_SYSCALL(fsync), + ALLOW_SYSCALL(lseek), + ALLOW_SYSCALL(truncate), + ALLOW_SYSCALL(fcntl), + ALLOW_SYSCALL(unlink), + ALLOW_SYSCALL(bind), + ALLOW_SYSCALL(chmod), + + ALLOW_SYSCALL(rt_sigreturn), +#ifdef __NR_sigreturn + ALLOW_SYSCALL(sigreturn), +#endif + // Allowed by vDSO + ALLOW_SYSCALL(getcpu), + ALLOW_SYSCALL(time), + ALLOW_SYSCALL(gettimeofday), + ALLOW_SYSCALL(clock_gettime), + + ALLOW_SYSCALL(exit_group), + ALLOW_SYSCALL(exit), + KILL_PROCESS, + }; + struct sock_fprog prog = { + .len = (unsigned short)(sizeof filter / sizeof filter[0]), + .filter = filter, + }; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + return -1; + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + return -1; + log_line("ndhc-ifch seccomp filter installed. Please disable seccomp if you encounter problems."); + return 0; +} + diff --git a/ndhc/seccomp.h b/ndhc/seccomp.h new file mode 100644 index 0000000..e63b5c1 --- /dev/null +++ b/ndhc/seccomp.h @@ -0,0 +1,38 @@ +/* seccomp.h - seccomp syscall filters for ndhc + * + * Copyright (c) 2012-2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef NJK_NDHC_SECCOMP_H_ +#define NJK_NDHC_SECCOMP_H_ + +#include + +extern bool seccomp_enforce; + +extern int enforce_seccomp_ndhc(void); +extern int enforce_seccomp_ifch(void); + +#endif /* NJK_NDHC_SECCOMP_H_ */ diff --git a/ndhc/sys.c b/ndhc/sys.c index e648b17..81cb369 100644 --- a/ndhc/sys.c +++ b/ndhc/sys.c @@ -41,7 +41,7 @@ char pidfile[MAX_PATH_LENGTH] = PID_FILE_DEFAULT; -void setup_signals(struct client_state_t *cs) +void setup_signals_ndhc(struct client_state_t *cs) { sigset_t mask; sigemptyset(&mask); @@ -71,7 +71,7 @@ void background(struct client_state_t *cs) exit(EXIT_SUCCESS); } if (cs) - setup_signals(cs); + setup_signals_ndhc(cs); } if (file_exists(pidfile, "w") == -1) { log_line("Cannot open pidfile for write!"); diff --git a/ndhc/sys.h b/ndhc/sys.h index e056153..d145101 100644 --- a/ndhc/sys.h +++ b/ndhc/sys.h @@ -41,7 +41,7 @@ static inline unsigned long long curms() extern char pidfile[MAX_PATH_LENGTH]; -void setup_signals(struct client_state_t *cs); +void setup_signals_ndhc(struct client_state_t *cs); void background(struct client_state_t *cs); void epoll_add(struct client_state_t *cs, int fd); void epoll_del(struct client_state_t *cs, int fd);