87db9c70fd
Skip zero-length commands in execute_buffer().
953 lines
27 KiB
C
953 lines
27 KiB
C
/* ifchd.c - interface change daemon
|
|
*
|
|
* Copyright (c) 2004-2012 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"
|
|
|
|
enum states {
|
|
STATE_NOTHING,
|
|
STATE_INTERFACE,
|
|
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
|
|
};
|
|
|
|
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;
|
|
|
|
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(socket),
|
|
ALLOW_SYSCALL(getsockopt),
|
|
ALLOW_SYSCALL(accept),
|
|
ALLOW_SYSCALL(listen),
|
|
ALLOW_SYSCALL(ioctl),
|
|
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
|
|
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';
|
|
strlcpy(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';
|
|
strlcpy(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\n",
|
|
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\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
r = fsync(resolv_conf_fd);
|
|
if (r == -1) {
|
|
log_line("write_resolve_conf: fsync returned error: %s\n",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* XXX: addme */
|
|
static void perform_timezone(struct ifchd_client *cl, char *str)
|
|
{}
|
|
|
|
/* Add a dns server to the /etc/resolv.conf -- we already have a fd. */
|
|
static void perform_dns(struct ifchd_client *cl, char *str)
|
|
{
|
|
if (!str || resolv_conf_fd == -1)
|
|
return;
|
|
strlcpy(cl->namesvrs, str, MAX_BUF);
|
|
write_resolve_conf(cl);
|
|
}
|
|
|
|
/* Updates for print daemons are too non-standard to be useful. */
|
|
static void perform_lprsvr(struct ifchd_client *cl, char *str)
|
|
{}
|
|
|
|
/* Sets machine hostname. */
|
|
static void perform_hostname(struct ifchd_client *cl, char *str)
|
|
{
|
|
if (!allow_hostname || !str)
|
|
return;
|
|
if (sethostname(str, strlen(str) + 1) == -1)
|
|
log_line("sethostname returned %s\n", strerror(errno));
|
|
}
|
|
|
|
/* update "domain" and "search" in /etc/resolv.conf */
|
|
static void perform_domain(struct ifchd_client *cl, char *str)
|
|
{
|
|
if (!str || resolv_conf_fd == -1)
|
|
return;
|
|
strlcpy(cl->domains, str, MAX_BUF);
|
|
write_resolve_conf(cl);
|
|
}
|
|
|
|
/* I don't think this can be done without a netfilter extension
|
|
* that isn't in the mainline kernels. */
|
|
static void perform_ipttl(struct ifchd_client *cl, char *str)
|
|
{}
|
|
|
|
/* XXX: addme */
|
|
static void perform_ntpsrv(struct ifchd_client *cl, char *str)
|
|
{}
|
|
|
|
/* Maybe Samba cares about this feature? I don't know. */
|
|
static void perform_wins(struct ifchd_client *cl, char *str)
|
|
{}
|
|
|
|
static void ifchd_client_init(struct ifchd_client *p)
|
|
{
|
|
p->fd = -1;
|
|
p->idle_time = time(NULL);
|
|
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;
|
|
if (time(NULL) - p->idle_time > CONN_TIMEOUT)
|
|
ifchd_client_wipe(p);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns -1 on fatal error.
|
|
*/
|
|
static int execute_buffer(struct ifchd_client *cl, char *newbuf)
|
|
{
|
|
char buf[MAX_BUF * 2];
|
|
char *p = buf, *endp;
|
|
|
|
memset(buf, 0, sizeof buf);
|
|
strlcat(buf, cl->ibuf, sizeof buf);
|
|
strlcat(buf, newbuf, sizeof buf);
|
|
|
|
for (endp = p;;p = endp) {
|
|
if (cl->state == STATE_NOTHING) {
|
|
char *colon = strchr(p, ':');
|
|
if (!colon)
|
|
break;
|
|
char *semi = strchr(p, ';');
|
|
if (semi && semi < colon) {
|
|
log_line("bad syntax: STATE_NOTHING and a ';' came before ':'");
|
|
return -1;
|
|
}
|
|
*colon = '\0';
|
|
endp = colon + 1;
|
|
} else {
|
|
char *semi = strchr(p, ';');
|
|
if (!semi)
|
|
break;
|
|
char *colon = strchr(p, ':');
|
|
if (colon && colon < semi) {
|
|
log_line("bad syntax: !STATE_NOTHING and a ':' came before ';'");
|
|
return -1;
|
|
}
|
|
*semi = '\0';
|
|
endp = semi + 1;
|
|
}
|
|
|
|
if (!strlen(p))
|
|
continue;
|
|
|
|
switch (cl->state) {
|
|
case STATE_NOTHING:
|
|
if (strncmp(p, CMD_INTERFACE, sizeof(CMD_INTERFACE)) == 0)
|
|
cl->state = STATE_INTERFACE;
|
|
if (strncmp(p, CMD_IP, sizeof(CMD_IP)) == 0)
|
|
cl->state = STATE_IP;
|
|
if (strncmp(p, CMD_SUBNET, sizeof(CMD_SUBNET)) == 0)
|
|
cl->state = STATE_SUBNET;
|
|
if (strncmp(p, CMD_TIMEZONE, sizeof(CMD_TIMEZONE)) == 0)
|
|
cl->state = STATE_TIMEZONE;
|
|
if (strncmp(p, CMD_ROUTER, sizeof(CMD_ROUTER)) == 0)
|
|
cl->state = STATE_ROUTER;
|
|
if (strncmp(p, CMD_DNS, sizeof(CMD_DNS)) == 0)
|
|
cl->state = STATE_DNS;
|
|
if (strncmp(p, CMD_LPRSVR, sizeof(CMD_LPRSVR)) == 0)
|
|
cl->state = STATE_LPRSVR;
|
|
if (strncmp(p, CMD_HOSTNAME, sizeof(CMD_HOSTNAME)) == 0)
|
|
cl->state = STATE_HOSTNAME;
|
|
if (strncmp(p, CMD_DOMAIN, sizeof(CMD_DOMAIN)) == 0)
|
|
cl->state = STATE_DOMAIN;
|
|
if (strncmp(p, CMD_IPTTL, sizeof(CMD_IPTTL)) == 0)
|
|
cl->state = STATE_IPTTL;
|
|
if (strncmp(p, CMD_MTU, sizeof(CMD_MTU)) == 0)
|
|
cl->state = STATE_MTU;
|
|
if (strncmp(p, CMD_BROADCAST, sizeof(CMD_BROADCAST)) == 0)
|
|
cl->state = STATE_BROADCAST;
|
|
if (strncmp(p, CMD_NTPSVR, sizeof(CMD_NTPSVR)) == 0)
|
|
cl->state = STATE_NTPSVR;
|
|
if (strncmp(p, CMD_WINS, sizeof(CMD_WINS)) == 0)
|
|
cl->state = STATE_WINS;
|
|
break;
|
|
|
|
case STATE_INTERFACE:
|
|
perform_interface(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_IP:
|
|
perform_ip(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_SUBNET:
|
|
perform_subnet(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_TIMEZONE:
|
|
perform_timezone(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_ROUTER:
|
|
perform_router(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_DNS:
|
|
perform_dns(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_LPRSVR:
|
|
perform_lprsvr(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_HOSTNAME:
|
|
perform_hostname(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_DOMAIN:
|
|
perform_domain(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_IPTTL:
|
|
perform_ipttl(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_MTU:
|
|
perform_mtu(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_BROADCAST:
|
|
perform_broadcast(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_NTPSVR:
|
|
perform_ntpsrv(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
case STATE_WINS:
|
|
perform_wins(cl, p);
|
|
cl->state = STATE_NOTHING;
|
|
break;
|
|
|
|
default:
|
|
log_line("warning: invalid state in dispatch_work\n");
|
|
break;
|
|
}
|
|
}
|
|
size_t remsize = strlen(endp);
|
|
if (remsize > MAX_BUF - 1)
|
|
return -1;
|
|
strlcpy(cl->ibuf, endp, MAX_BUF);
|
|
return 0;
|
|
}
|
|
|
|
/* 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!\n", 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!\n", strerror(errno));
|
|
return;
|
|
|
|
default:
|
|
log_line("warning: accept returned a mysterious error: %s\n",
|
|
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");
|
|
|
|
cl->idle_time = time(NULL);
|
|
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. Licensed under 2-clause BSD.\n", IFCHD_VERSION);
|
|
printf(
|
|
"Copyright (C) 2004-2012 Nicholas J. Kain\nAll rights reserved.\n"
|
|
"This is free software; see the source for copying conditions. There is NO\n"
|
|
"WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\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':
|
|
strlcpy(chrootd, optarg, MAX_PATH_LENGTH);
|
|
break;
|
|
|
|
case 'p':
|
|
strlcpy(pidfile, optarg, MAX_PATH_LENGTH);
|
|
break;
|
|
|
|
case 'r':
|
|
strlcpy(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);
|
|
}
|