/* $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" #include "setproctitle.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, AVAHI_CHROOT_GET_RECORD_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", AVAHI_DBUS_INTROSPECTION_DIR"/RecordBrowser.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, 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: case AVAHI_CHROOT_GET_RECORD_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(const char *argv0) { 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 = fork()) < 0) { close(sock[0]); close(sock[1]); avahi_log_error(__FILE__": fork() failed: %s", strerror(errno)); return -1; } else if (pid == 0) { setsid(); /* Drop all remaining capabilities */ avahi_caps_drop_all(); avahi_set_proc_title(argv0, "%s: chroot helper", argv0); daemon_retval_done(); 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); }