From aedd4e87362371d83dd64d0bfb03ea3e5526607f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 30 Oct 2005 18:21:57 +0000 Subject: [PATCH] * add chroot() support on Linux git-svn-id: file:///home/lennart/svn/public/avahi/trunk@907 941a03a8-eaeb-0310-b9a0-b1bbd8fe43fe --- avahi-daemon/Makefile.am | 14 +- avahi-daemon/avahi-daemon.conf | 2 +- avahi-daemon/caps.c | 116 ++++++++++ avahi-daemon/caps.h | 29 +++ avahi-daemon/chroot.c | 406 +++++++++++++++++++++++++++++++++ avahi-daemon/chroot.h | 36 +++ avahi-daemon/dbus-util.c | 23 +- avahi-daemon/main.c | 137 +++++++++-- avahi-daemon/main.h | 1 - avahi-daemon/simple-protocol.c | 8 + avahi-daemon/static-services.c | 4 +- avahi-daemon/static-services.h | 2 +- configure.ac | 12 + 13 files changed, 757 insertions(+), 33 deletions(-) create mode 100644 avahi-daemon/caps.c create mode 100644 avahi-daemon/caps.h create mode 100644 avahi-daemon/chroot.c create mode 100644 avahi-daemon/chroot.h diff --git a/avahi-daemon/Makefile.am b/avahi-daemon/Makefile.am index 7081752..0dadede 100644 --- a/avahi-daemon/Makefile.am +++ b/avahi-daemon/Makefile.am @@ -35,7 +35,8 @@ AM_CFLAGS+= \ -DAVAHI_SOCKET=\"$(avahi_socket)\" \ -DAVAHI_SERVICE_DIR=\"$(servicedir)\" \ -DAVAHI_CONFIG_FILE=\"$(pkgsysconfdir)/avahi-daemon.conf\" \ - -DAVAHI_DBUS_INTROSPECTION_DIR=\"$(introspectiondir)\" + -DAVAHI_DBUS_INTROSPECTION_DIR=\"$(introspectiondir)\" \ + -DAVAHI_CONFIG_DIR=\"$(pkgsysconfdir)\" sbin_PROGRAMS = \ avahi-daemon @@ -70,6 +71,15 @@ service_DATA = \ pkgdata_DATA = \ avahi-service.dtd +if ENABLE_CHROOT + +avahi_daemon_SOURCES += \ + chroot.c chroot.h \ + caps.c caps.h + +avahi_daemon_LDADD += -lcap + +endif if HAVE_DBUS @@ -86,7 +96,7 @@ avahi_daemon_SOURCES += \ dbus-service-type-browser.c \ dbus-sync-address-resolver.c \ dbus-sync-host-name-resolver.c \ - dbus-sync-service-resolver.c + dbus-sync-service-resolver.c avahi_daemon_LDADD += \ ../avahi-common/libdbus-common.la \ diff --git a/avahi-daemon/avahi-daemon.conf b/avahi-daemon/avahi-daemon.conf index ed154be..b812d37 100644 --- a/avahi-daemon/avahi-daemon.conf +++ b/avahi-daemon/avahi-daemon.conf @@ -31,4 +31,4 @@ rlimit-data=4194304 rlimit-fsize=0 rlimit-nofile=30 rlimit-stack=4194304 -rlimit-nproc=1 +rlimit-nproc=3 diff --git a/avahi-daemon/caps.c b/avahi-daemon/caps.c new file mode 100644 index 0000000..2d0f39a --- /dev/null +++ b/avahi-daemon/caps.c @@ -0,0 +1,116 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "caps.h" + +int avahi_caps_reduce(void) { + int ret = 0; + cap_t caps; + static cap_value_t cap_values[] = { CAP_SYS_CHROOT, CAP_SETUID, CAP_SETGID }; + + /* Let's reduce our caps to the minimum set and tell Linux to keep + * them accross setuid(). This is called before we droppped + * priviliges. */ + + caps = cap_init(); + assert(caps); + cap_clear(caps); + + cap_set_flag(caps, CAP_EFFECTIVE, 3, cap_values, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, 3, cap_values, CAP_SET); + + if (cap_set_proc(caps) < 0) { + avahi_log_error("cap_set_proc() failed: %s", strerror(errno)); + ret = -1; + } + cap_free(caps); + + /* Retain capabilities accros setuid() */ + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + avahi_log_error("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); + ret = -1; + } + + return ret; +} + +int avahi_caps_reduce2(void) { + int ret = 0; + cap_t caps; + static cap_value_t cap_values[] = { CAP_SYS_CHROOT }; + + /* Reduce our caps to the bare minimum and tell Linux not to keep + * them across setuid(). This is called after we dropped + * privilige. */ + + /* No longer retain caps across setuid() */ + if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) < 0) { + avahi_log_error("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)); + ret = -1; + } + + caps = cap_init(); + assert(caps); + cap_clear(caps); + + /* setuid() zeroed our effective caps, let's get them back */ + cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_values, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, 1, cap_values, CAP_SET); + + if (cap_set_proc(caps) < 0) { + avahi_log_error("cap_set_proc() failed: %s", strerror(errno)); + ret = -1; + } + cap_free(caps); + + return ret; +} + +int avahi_caps_drop_all(void) { + cap_t caps; + int ret = 0; + + /* Drop all capabilities and turn ourselves into a normal user process */ + + caps = cap_init(); + assert(caps); + cap_clear(caps); + + if (cap_set_proc(caps) < 0) { + avahi_log_error("cap_set_proc() failed: %s", strerror(errno)); + ret = -1; + } + cap_free(caps); + + return ret; +} diff --git a/avahi-daemon/caps.h b/avahi-daemon/caps.h new file mode 100644 index 0000000..788aceb --- /dev/null +++ b/avahi-daemon/caps.h @@ -0,0 +1,29 @@ +#ifndef foocapshfoo +#define foocapshfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +int avahi_caps_reduce(void); +int avahi_caps_reduce2(void); +int avahi_caps_drop_all(void); + +#endif diff --git a/avahi-daemon/chroot.c b/avahi-daemon/chroot.c new file mode 100644 index 0000000..5504cbd --- /dev/null +++ b/avahi-daemon/chroot.c @@ -0,0 +1,406 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "chroot.h" +#include "caps.h" + +enum { + AVAHI_CHROOT_SUCCESS = 0, + AVAHI_CHROOT_FAILURE, + AVAHI_CHROOT_GET_RESOLV_CONF, +#ifdef HAVE_DBUS + AVAHI_CHROOT_GET_SERVER_INTROSPECT, + AVAHI_CHROOT_GET_ENTRY_GROUP_INTROSPECT, + AVAHI_CHROOT_GET_ADDRESS_RESOLVER_INTROSPECT, + AVAHI_CHROOT_GET_DOMAIN_BROWSER_INTROSPECT, + AVAHI_CHROOT_GET_HOST_NAME_RESOLVER_INTROSPECT, + AVAHI_CHROOT_GET_SERVICE_BROWSER_INTROSPECT, + AVAHI_CHROOT_GET_SERVICE_RESOLVER_INTROSPECT, + AVAHI_CHROOT_GET_SERVICE_TYPE_BROWSER_INTROSPECT, +#endif + AVAHI_CHROOT_UNLINK_PID, + AVAHI_CHROOT_UNLINK_SOCKET, + AVAHI_CHROOT_MAX +}; + +static const char* const get_file_name_table[AVAHI_CHROOT_MAX] = { + NULL, + NULL, + "/etc/resolv.conf", +#ifdef HAVE_DBUS + AVAHI_DBUS_INTROSPECTION_DIR"/Server.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/EntryGroup.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/AddressResolver.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/DomainBrowser.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/HostNameResolver.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/ServiceBrowser.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/ServiceResolver.introspect", + AVAHI_DBUS_INTROSPECTION_DIR"/ServiceTypeBrowser.introspect", +#endif + NULL, + NULL +}; + +static const char *const unlink_file_name_table[AVAHI_CHROOT_MAX] = { + NULL, + NULL, + NULL, +#ifdef HAVE_DBUS + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +#endif + AVAHI_DAEMON_RUNTIME_DIR"/pid", + AVAHI_SOCKET +}; + +static int helper_fd = -1; + +static int send_fd(int fd, int payload_fd) { + uint8_t dummy = AVAHI_CHROOT_SUCCESS; + struct iovec iov; + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsg; + + /* Send a file descriptor over the socket */ + + memset(&iov, 0, sizeof(iov)); + memset(&msg, 0, sizeof(msg)); + memset(&cmsg, 0, sizeof(cmsg)); + + iov.iov_base = &dummy; + iov.iov_len = sizeof(dummy); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + + msg.msg_control = &cmsg; + msg.msg_controllen = sizeof(cmsg); + msg.msg_flags = 0; + + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_RIGHTS; + *((int*) CMSG_DATA(&cmsg.hdr)) = payload_fd; + + if (sendmsg(fd, &msg, 0) < 0) { + avahi_log_error("sendmsg() failed: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int recv_fd(int fd) { + uint8_t dummy; + struct iovec iov; + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsg; + + /* Receive a file descriptor from a socket */ + + memset(&iov, 0, sizeof(iov)); + memset(&msg, 0, sizeof(msg)); + memset(&cmsg, 0, sizeof(cmsg)); + + iov.iov_base = &dummy; + iov.iov_len = sizeof(dummy); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + + msg.msg_control = cmsg.buf; + msg.msg_controllen = sizeof(cmsg); + msg.msg_flags = 0; + + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_RIGHTS; + *((int*) CMSG_DATA(&cmsg.hdr)) = -1; + + if (recvmsg(fd, &msg, 0) <= 0) { + avahi_log_error("recvmsg() failed: %s", strerror(errno)); + return -1; + } else { + struct cmsghdr* h; + + if (dummy != AVAHI_CHROOT_SUCCESS) { + errno = EINVAL; + return -1; + } + + if (!(h = CMSG_FIRSTHDR(&msg))) { + avahi_log_error("recvmsg() sent no fd."); + errno = EINVAL; + return -1; + } + + assert(h->cmsg_len = CMSG_LEN(sizeof(int))); + assert(h->cmsg_level = SOL_SOCKET); + assert(h->cmsg_type == SCM_RIGHTS); + + return *((int*)CMSG_DATA(h)); + } +} + +static int helper_main(int fd) { + int ret = 1; + assert(fd >= 0); + + /* This is the main function of our helper process which is forked + * off to access files outside the chroot environment. Keep in + * mind that this code is security sensitive! */ + + avahi_log_debug(__FILE__": chroot() helper started"); + + for (;;) { + uint8_t command; + ssize_t r; + + if ((r = read(fd, &command, sizeof(command))) <= 0) { + + /* EOF? */ + if (r == 0) + break; + + avahi_log_error(__FILE__": read() failed: %s", strerror(errno)); + goto fail; + } + + assert(r == sizeof(command)); + + avahi_log_debug(__FILE__": chroot() helper got command %02x", command); + + switch (command) { +#ifdef HAVE_DBUS + case AVAHI_CHROOT_GET_SERVER_INTROSPECT: + case AVAHI_CHROOT_GET_ENTRY_GROUP_INTROSPECT: + case AVAHI_CHROOT_GET_ADDRESS_RESOLVER_INTROSPECT: + case AVAHI_CHROOT_GET_DOMAIN_BROWSER_INTROSPECT: + case AVAHI_CHROOT_GET_HOST_NAME_RESOLVER_INTROSPECT: + case AVAHI_CHROOT_GET_SERVICE_BROWSER_INTROSPECT: + case AVAHI_CHROOT_GET_SERVICE_RESOLVER_INTROSPECT: + case AVAHI_CHROOT_GET_SERVICE_TYPE_BROWSER_INTROSPECT: +#endif + case AVAHI_CHROOT_GET_RESOLV_CONF: { + int payload; + + if ((payload = open(get_file_name_table[(int) command], O_RDONLY)) < 0) { + uint8_t c = AVAHI_CHROOT_FAILURE; + + avahi_log_error(__FILE__": open() failed: %s", strerror(errno)); + + if (write(fd, &c, sizeof(c)) != sizeof(c)) { + avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno)); + goto fail; + } + + break; + } + + if (send_fd(fd, payload) < 0) + goto fail; + + close(payload); + + break; + } + + case AVAHI_CHROOT_UNLINK_SOCKET: + case AVAHI_CHROOT_UNLINK_PID: { + uint8_t c = AVAHI_CHROOT_SUCCESS; + + unlink(unlink_file_name_table[(int) command]); + + if (write(fd, &c, sizeof(c)) != sizeof(c)) { + avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno)); + goto fail; + } + + break; + } + + default: + avahi_log_error(__FILE__": Unknown command %02x.", command); + break; + } + } + + ret = 0; + +fail: + + avahi_log_debug(__FILE__": chroot() helper exiting with return value %i", ret); + + return ret; +} + +int avahi_chroot_helper_start(void) { + int sock[2]; + pid_t pid; + + assert(helper_fd < 0); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) < 0) { + avahi_log_error("socketpair() failed: %s", strerror(errno)); + return -1; + } + + if ((pid = daemon_fork()) < 0) { + close(sock[0]); + close(sock[1]); + avahi_log_error(__FILE__": Failed to fork()"); + return -1; + } else if (pid == 0) { + + /* Drop all remaining capabilities */ + avahi_caps_drop_all(); + + close(sock[0]); + helper_main(sock[1]); + _exit(0); + } + + close(sock[1]); + helper_fd = sock[0]; + + return 0; +} + +void avahi_chroot_helper_shutdown(void) { + + if (helper_fd <= 0) + return; + + close(helper_fd); + helper_fd = -1; +} + +int avahi_chroot_helper_get_fd(const char *fname) { + + if (helper_fd >= 0) { + uint8_t command; + + for (command = 2; command < AVAHI_CHROOT_MAX; command++) + if (get_file_name_table[(int) command] && + strcmp(fname, get_file_name_table[(int) command]) == 0) + break; + + if (command >= AVAHI_CHROOT_MAX) { + avahi_log_error("chroot() helper accessed for invalid file name"); + errno = EACCES; + return -1; + } + + assert(get_file_name_table[(int) command]); + + if (write(helper_fd, &command, sizeof(command)) < 0) { + avahi_log_error("write() failed: %s\n", strerror(errno)); + return -1; + } + + return recv_fd(helper_fd); + + } else + return open(fname, O_RDONLY); +} + + +FILE *avahi_chroot_helper_get_file(const char *fname) { + FILE *f; + int fd; + + if ((fd = avahi_chroot_helper_get_fd(fname)) < 0) + return NULL; + + f = fdopen(fd, "r"); + assert(f); + + return f; +} + +int avahi_chroot_helper_unlink(const char *fname) { + + if (helper_fd >= 0) { + uint8_t c, command; + ssize_t r; + + for (command = 2; command < AVAHI_CHROOT_MAX; command++) + if (unlink_file_name_table[(int) command] && + strcmp(fname, unlink_file_name_table[(int) command]) == 0) + break; + + if (command >= AVAHI_CHROOT_MAX) { + avahi_log_error("chroot() helper accessed for invalid file name"); + errno = EACCES; + return -1; + } + + if (write(helper_fd, &command, sizeof(command)) < 0) { + avahi_log_error("write() failed: %s\n", strerror(errno)); + return -1; + } + + if ((r = read(helper_fd, &c, sizeof(c))) < 0) { + avahi_log_error("read() failed: %s\n", r < 0 ? strerror(errno) : "EOF"); + return -1; + } + + return 0; + + } else + + return unlink(fname); + +} diff --git a/avahi-daemon/chroot.h b/avahi-daemon/chroot.h new file mode 100644 index 0000000..1255739 --- /dev/null +++ b/avahi-daemon/chroot.h @@ -0,0 +1,36 @@ +#ifndef foochroothelperhfoo +#define foochroothelperhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + avahi is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +int avahi_chroot_helper_start(void); +void avahi_chroot_helper_shutdown(void); +int avahi_chroot_helper_get(const char *fname); + +int avahi_chroot_helper_get_fd(const char *fname); +FILE *avahi_chroot_helper_get_file(const char *fname); + +int avahi_chroot_helper_unlink(const char *fname); + +#endif diff --git a/avahi-daemon/dbus-util.c b/avahi-daemon/dbus-util.c index 09826d3..00d8a9d 100644 --- a/avahi-daemon/dbus-util.c +++ b/avahi-daemon/dbus-util.c @@ -37,6 +37,10 @@ #include #include +#ifdef ENABLE_CHROOT +#include "chroot.h" +#endif + #include "main.h" #include "dbus-util.h" @@ -155,15 +159,21 @@ const char *avahi_dbus_map_resolve_signal_name(AvahiResolverEvent e) { abort(); } -static char *file_get_contents(char *fname) { +static char *file_get_contents(const char *fname) { int fd = -1; struct stat st; ssize_t size; char *buf = NULL; - + assert(fname); + +#ifdef ENABLE_CHROOT + fd = avahi_chroot_helper_get_fd(fname); +#else + fd = open(fname, O_RDONLY); +#endif - if (!(fd = open(fname, O_RDONLY))) { + if (fd < 0) { avahi_log_error("Failed to open %s: %s", fname, strerror(errno)); goto fail; } @@ -193,12 +203,13 @@ static char *file_get_contents(char *fname) { buf[size] = 0; close(fd); + return buf; fail: if (fd >= 0) close(fd); - + if (buf) avahi_free(buf); @@ -207,7 +218,7 @@ fail: } DBusHandlerResult avahi_dbus_handle_introspect(DBusConnection *c, DBusMessage *m, const char *fname) { - char *path, *contents; + char *contents, *path; DBusError error; assert(c); @@ -220,7 +231,7 @@ DBusHandlerResult avahi_dbus_handle_introspect(DBusConnection *c, DBusMessage *m avahi_log_error("Error parsing Introspect message: %s", error.message); goto fail; } - + path = avahi_strdup_printf("%s/%s", AVAHI_DBUS_INTROSPECTION_DIR, fname); contents = file_get_contents(path); avahi_free(path); diff --git a/avahi-daemon/main.c b/avahi-daemon/main.c index 6f0624d..7ebfcd7 100644 --- a/avahi-daemon/main.c +++ b/avahi-daemon/main.c @@ -56,6 +56,11 @@ #include #include +#ifdef ENABLE_CHROOT +#include "chroot.h" +#include "caps.h" +#endif + #include "main.h" #include "simple-protocol.h" #include "static-services.h" @@ -83,12 +88,18 @@ typedef struct { int daemonize; int use_syslog; char *config_file; +#ifdef HAVE_DBUS int enable_dbus; int fail_on_missing_dbus; +#endif int drop_root; + int set_rlimits; +#ifdef ENABLE_CHROOT + int use_chroot; +#endif + int publish_resolv_conf; char ** publish_dns_servers; - int no_rlimits; int debug; int rlimit_as_set, rlimit_core_set, rlimit_data_set, rlimit_fsize_set, rlimit_nofile_set, rlimit_stack_set; @@ -125,8 +136,14 @@ static int load_resolv_conf(void) { avahi_strfreev(resolv_conf); resolv_conf = NULL; - if (!(f = fopen(RESOLV_CONF, "r"))) { - avahi_log_warn("Failed to open "RESOLV_CONF"."); +#ifdef ENABLE_CHROOT + f = avahi_chroot_helper_get_file(RESOLV_CONF); +#else + f = fopen(RESOLV_CONF, "r"); +#endif + + if (!f) { + avahi_log_warn("Failed to open "RESOLV_CONF": %s", strerror(errno)); goto finish; } @@ -208,6 +225,11 @@ static void update_wide_area_servers(void) { unsigned n = 0; char **p; + if (!resolv_conf) { + avahi_server_set_wide_area_servers(avahi_server, NULL, 0); + return; + } + for (p = resolv_conf; *p && n < AVAHI_WIDE_AREA_SERVERS_MAX; p++) { if (!avahi_address_parse(*p, AVAHI_PROTO_UNSPEC, &a[n])) avahi_log_warn("Failed to parse address '%s', ignoring.", *p); @@ -291,6 +313,9 @@ static void help(FILE *f, const char *argv0) { " "AVAHI_CONFIG_FILE"\n" " --no-rlimits Don't enforce resource limits\n" " --no-drop-root Don't drop privileges\n" +#ifdef ENABLE_CHROOT + " --no-chroot Don't chroot()\n" +#endif " --debug Increase verbosity\n", argv0); } @@ -302,6 +327,9 @@ static int parse_command_line(DaemonConfig *c, int argc, char *argv[]) { enum { OPTION_NO_RLIMITS = 256, OPTION_NO_DROP_ROOT, +#ifdef ENABLE_CHROOT + OPTION_NO_CHROOT, +#endif OPTION_DEBUG }; @@ -316,6 +344,9 @@ static int parse_command_line(DaemonConfig *c, int argc, char *argv[]) { { "syslog", no_argument, NULL, 's' }, { "no-rlimits", no_argument, NULL, OPTION_NO_RLIMITS }, { "no-drop-root", no_argument, NULL, OPTION_NO_DROP_ROOT }, +#ifdef ENABLE_CHROOT + { "no-chroot", no_argument, NULL, OPTION_NO_CHROOT }, +#endif { "debug", no_argument, NULL, OPTION_DEBUG }, { NULL, 0, NULL, 0 } }; @@ -352,11 +383,16 @@ static int parse_command_line(DaemonConfig *c, int argc, char *argv[]) { c->command = DAEMON_CHECK; break; case OPTION_NO_RLIMITS: - c->no_rlimits = 1; + c->set_rlimits = 0; break; case OPTION_NO_DROP_ROOT: c->drop_root = 0; break; +#ifdef ENABLE_CHROOT + case OPTION_NO_CHROOT: + c->use_chroot = 0; + break; +#endif case OPTION_DEBUG: c->debug = 1; break; @@ -588,7 +624,11 @@ static void signal_callback(AvahiWatch *watch, AVAHI_GCC_UNUSED int fd, AVAHI_GC case SIGHUP: avahi_log_info("Got SIGHUP, reloading."); - static_service_load(); +#ifdef ENABLE_CHROOT + static_service_load(config.use_chroot); +#else + static_service_load(0); +#endif static_service_add_to_server(); if (resolv_conf_entry_group) @@ -614,6 +654,7 @@ static void signal_callback(AvahiWatch *watch, AVAHI_GCC_UNUSED int fd, AVAHI_GC } } + static int run_server(DaemonConfig *c) { int r = -1; int error; @@ -641,8 +682,9 @@ static int run_server(DaemonConfig *c) { if (simple_protocol_setup(poll_api) < 0) goto finish; - if (c->enable_dbus) { + #ifdef HAVE_DBUS + if (c->enable_dbus) { if (dbus_protocol_setup(poll_api) < 0) { if (c->fail_on_missing_dbus) @@ -651,14 +693,35 @@ static int run_server(DaemonConfig *c) { avahi_log_warn("WARNING: Failed to contact D-BUS daemon, disabling D-BUS support."); c->enable_dbus = 0; } -#else - avahi_log_warn("WARNING: We are configured to enable D-BUS but it was not compiled in."); - c->enable_dbus = 0; + } #endif + +#ifdef ENABLE_CHROOT + + if (config.drop_root && config.use_chroot) { + if (chroot(AVAHI_CONFIG_DIR) < 0) { + avahi_log_error("Failed to chroot(): %s", strerror(errno)); + goto finish; + } + + chdir("/"); + + if (avahi_caps_drop_all() < 0) { + avahi_log_error("Failed to drop capabilities."); + goto finish; + } + + avahi_log_info("chroot() successful."); } +#endif + load_resolv_conf(); - static_service_load(); +#ifdef ENABLE_CHROOT + static_service_load(config.use_chroot); +#else + static_service_load(0); +#endif if (!(avahi_server = avahi_server_new(poll_api, &c->server_config, server_callback, c, &error))) { avahi_log_error("Failed to create server: %s", avahi_strerror(error)); @@ -778,7 +841,7 @@ static int drop_root(void) { set_env("USER", pw->pw_name); set_env("LOGNAME", pw->pw_name); set_env("HOME", pw->pw_dir); - + avahi_log_info("Successfully dropped root privileges."); return 0; @@ -904,15 +967,17 @@ int main(int argc, char *argv[]) { #ifdef HAVE_DBUS config.enable_dbus = 1; config.fail_on_missing_dbus = 1; -#else - config.enable_dbus = 0; - config.fail_on_missing_dbus = 0; #endif + config.drop_root = 1; + config.set_rlimits = 1; +#ifdef ENABLE_CHROOT + config.use_chroot = 1; +#endif + config.publish_dns_servers = NULL; config.publish_resolv_conf = 0; config.use_syslog = 0; - config.no_rlimits = 0; config.debug = 0; config.rlimit_as_set = 0; @@ -937,6 +1002,10 @@ int main(int argc, char *argv[]) { if (parse_command_line(&config, argc, argv) < 0) goto finish; +#ifdef ENABLE_CHROOT + config.use_chroot = config.use_chroot && config.drop_root; +#endif + if (config.command == DAEMON_HELP) { help(stdout, argv0); r = 0; @@ -1005,8 +1074,20 @@ int main(int argc, char *argv[]) { goto finish; if (config.drop_root) { +#ifdef ENABLE_CHROOT + if (config.use_chroot) + if (avahi_caps_reduce() < 0) + goto finish; +#endif + if (drop_root() < 0) goto finish; + +#ifdef ENABLE_CHROOT + if (config.use_chroot) + if (avahi_caps_reduce2() < 0) + goto finish; +#endif } if (daemon_pid_file_create() < 0) { @@ -1018,13 +1099,20 @@ int main(int argc, char *argv[]) { } else wrote_pid_file = 1; - if (!config.no_rlimits) + if (config.set_rlimits) enforce_rlimits(); chdir("/"); - + +#ifdef ENABLE_CHROOT + if (config.drop_root && config.use_chroot) + if (avahi_chroot_helper_start() < 0) { + avahi_log_error("failed to start chroot() helper daemon."); + goto finish; + } +#endif avahi_log_info("%s "PACKAGE_VERSION" starting up.", argv0); - + if (run_server(&config) == 0) r = 0; } @@ -1039,8 +1127,17 @@ finish: avahi_strfreev(config.publish_dns_servers); avahi_strfreev(resolv_conf); - if (wrote_pid_file) + if (wrote_pid_file) { +#ifdef ENABLE_CHROOT + avahi_chroot_helper_unlink(pid_file_proc()); +#else daemon_pid_file_remove(); - +#endif + } + +#if ENABLE_CHROOT + avahi_chroot_helper_shutdown(); +#endif + return r; } diff --git a/avahi-daemon/main.h b/avahi-daemon/main.h index 0c93337..951b5f6 100644 --- a/avahi-daemon/main.h +++ b/avahi-daemon/main.h @@ -28,5 +28,4 @@ extern AvahiServer *avahi_server; extern AvahiSimplePoll *simple_poll_api; - #endif diff --git a/avahi-daemon/simple-protocol.c b/avahi-daemon/simple-protocol.c index 2d806df..8aa427b 100644 --- a/avahi-daemon/simple-protocol.c +++ b/avahi-daemon/simple-protocol.c @@ -44,6 +44,10 @@ #include "simple-protocol.h" #include "main.h" +#ifdef ENABLE_CHROOT +#include "chroot.h" +#endif + #define BUFFER_SIZE (20*1024) #define CLIENTS_MAX 50 @@ -497,7 +501,11 @@ void simple_protocol_shutdown(void) { if (server) { if (server->bind_successful) +#ifdef ENABLE_CHROOT + avahi_chroot_helper_unlink(AVAHI_SOCKET); +#else unlink(AVAHI_SOCKET); +#endif while (server->clients) client_free(server->clients); diff --git a/avahi-daemon/static-services.c b/avahi-daemon/static-services.c index 129063c..7f910b0 100644 --- a/avahi-daemon/static-services.c +++ b/avahi-daemon/static-services.c @@ -648,7 +648,7 @@ static void load_file(char *n) { } } -void static_service_load(void) { +void static_service_load(int in_chroot) { StaticServiceGroup *g, *n; glob_t globbuf; char **p; @@ -677,7 +677,7 @@ void static_service_load(void) { } memset(&globbuf, 0, sizeof(globbuf)); - if (glob(AVAHI_SERVICE_DIR "/*.service", GLOB_ERR, NULL, &globbuf) != 0) + if (glob(in_chroot ? "/services/*.service" : AVAHI_SERVICE_DIR "/*.service", GLOB_ERR, NULL, &globbuf) != 0) avahi_log_error("Failed to read service directory."); else { for (p = globbuf.gl_pathv; *p; p++) diff --git a/avahi-daemon/static-services.h b/avahi-daemon/static-services.h index 1c50372..a2bc355 100644 --- a/avahi-daemon/static-services.h +++ b/avahi-daemon/static-services.h @@ -22,7 +22,7 @@ USA. ***/ -void static_service_load(void); +void static_service_load(int in_chroot); void static_service_free_all(void); void static_service_add_to_server(void); void static_service_remove_from_server(void); diff --git a/configure.ac b/configure.ac index 356a0ad..4e9a98a 100644 --- a/configure.ac +++ b/configure.ac @@ -215,6 +215,17 @@ AC_TYPE_PID_T AC_CHECK_DECLS(environ) +enable_chroot=yes +AC_CHECK_HEADERS([sys/capability.h],,enable_chroot=no) +AC_CHECK_HEADERS([sys/prctl.h],,enable_chroot=no) +AC_CHECK_FUNCS([chroot],,enable_chroot=no) + +AM_CONDITIONAL(ENABLE_CHROOT, test "x$enable_chroot" = "xyes") + +if test "x$enable_chroot" = "xyes" ; then + AC_DEFINE([ENABLE_CHROOT], 1, [Enable chroot() usage]) +fi + # Check for pkg-config manually first, as if its not installed the # PKG_PROG_PKG_CONFIG macro won't be defined. AC_CHECK_PROG(have_pkg_config, pkg-config, yes, no) @@ -729,6 +740,7 @@ echo " Linux Distro: ${with_distro} User for Avahi: ${AVAHI_USER} Group for Avahi: ${AVAHI_GROUP} + Enable chroot(): ${enable_chroot} " BUILD_DAEMON="no (!)" -- 2.39.5