/***
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
#ifdef AVAHI_DAEMON_RUNTIME_DIR
,
AVAHI_DAEMON_RUNTIME_DIR"/pid"
#endif
#ifdef AVAHI_SOCKET
,
AVAHI_SOCKET
#endif
};
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);
}