| /*** |
| 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 <config.h> |
| #endif |
| |
| #include <inttypes.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/un.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| |
| #include <avahi-core/log.h> |
| #include <libdaemon/dfork.h> |
| |
| #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"/org.freedesktop.Avahi.Server.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.EntryGroup.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.AddressResolver.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.DomainBrowser.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.HostNameResolver.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.ServiceBrowser.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.ServiceResolver.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.ServiceTypeBrowser.xml", |
| AVAHI_DBUS_INTROSPECTION_DIR"/org.freedesktop.Avahi.RecordBrowser.xml", |
| #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) { |
| |
| /* 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 && |
| (errno != EPIPE && errno != ECONNRESET)) { |
| avahi_log_error("write() failed: %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| if ((r = read(helper_fd, &c, sizeof(c))) < 0 && |
| (errno != EPIPE && errno != ECONNRESET)) { |
| avahi_log_error("read() failed: %s\n", r < 0 ? strerror(errno) : "EOF"); |
| return -1; |
| } |
| |
| return 0; |
| |
| } else |
| |
| return unlink(fname); |
| |
| } |