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.
This commit is contained in:
Nicholas J. Kain 2014-03-10 00:52:56 -04:00
parent 06ff60bb6b
commit 1824802fb2
22 changed files with 998 additions and 1383 deletions

View File

@ -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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2012 Nicholas J. Kain <njkain at gmail dot com>
Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
All rights reserved.
Redistribution and use in source and binary forms, with or without

2
README
View File

@ -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:

View File

@ -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)

View File

@ -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 <net/if.h>
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_ */

View File

@ -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.

View File

@ -1,832 +0,0 @@
/* ifchd.c - interface change daemon
*
* Copyright (c) 2004-2013 Nicholas J. Kain <njkain at gmail dot com>
* 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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#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; i<SOCK_QUEUE; i++) {
struct ifchd_client *p = &clients[i];
if (p->fd == -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);
}

View File

@ -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)

View File

@ -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"

View File

@ -1,6 +1,6 @@
/* ifchange.c - functions to call the interface change daemon
*
* Copyright (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -30,12 +30,8 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <errno.h>
#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);

View File

@ -30,11 +30,14 @@
#include <string.h>
#include <arpa/inet.h>
#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) {

408
ndhc/ifchd.c Normal file
View File

@ -0,0 +1,408 @@
/* ifchd.c - interface change daemon
*
* Copyright (c) 2004-2014 Nicholas J. Kain <njkain at gmail dot com>
* 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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#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();
}

45
ndhc/ifchd.h Normal file
View File

@ -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 <net/if.h>
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_ */

View File

@ -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 <njkain at gmail dot com>
* All rights reserved.
@ -47,101 +47,35 @@
#include <errno.h>
#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);

View File

@ -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 <njkain at gmail dot com>
* 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

View File

@ -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_ */

View File

@ -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

View File

@ -28,6 +28,7 @@
#include <stdio.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
@ -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,18 +249,86 @@ 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'},
{"ifch-pidfile", required_argument, 0, 'P'},
{"leasefile", required_argument, 0, 'l'},
{"hostname", required_argument, 0, 'h'},
{"interface", required_argument, 0, 'i'},
@ -326,21 +337,25 @@ int main(int argc, char **argv)
{"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':
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!");
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);
}
write_pid(pidfile);
exit(EXIT_SUCCESS);
}
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();
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.
}

153
ndhc/seccomp.c Normal file
View File

@ -0,0 +1,153 @@
/* seccomp.h - seccomp syscall filters for ndhc
*
* Copyright (c) 2012-2014 Nicholas J. Kain <njkain at gmail dot com>
* 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 <stdbool.h>
#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;
}

38
ndhc/seccomp.h Normal file
View File

@ -0,0 +1,38 @@
/* seccomp.h - seccomp syscall filters for ndhc
*
* Copyright (c) 2012-2014 Nicholas J. Kain <njkain at gmail dot com>
* 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 <stdbool.h>
extern bool seccomp_enforce;
extern int enforce_seccomp_ndhc(void);
extern int enforce_seccomp_ifch(void);
#endif /* NJK_NDHC_SECCOMP_H_ */

View File

@ -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!");

View File

@ -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);