1/* $Id$ */ 2 3/*** 4 This file is part of avahi. 5 6 avahi is free software; you can redistribute it and/or modify it 7 under the terms of the GNU Lesser General Public License as 8 published by the Free Software Foundation; either version 2.1 of the 9 License, or (at your option) any later version. 10 11 avahi is distributed in the hope that it will be useful, but WITHOUT 12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 14 Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with avahi; if not, write to the Free Software 18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 19 USA. 20***/ 21 22#ifdef HAVE_CONFIG_H 23#include <config.h> 24#endif 25 26#include <inttypes.h> 27#include <sys/socket.h> 28#include <sys/types.h> 29#include <fcntl.h> 30#include <stdlib.h> 31#include <unistd.h> 32#include <sys/un.h> 33#include <string.h> 34#include <errno.h> 35#include <assert.h> 36 37#include <avahi-core/log.h> 38#include <libdaemon/dfork.h> 39 40#include "chroot.h" 41#include "caps.h" 42#include "setproctitle.h" 43 44enum { 45 AVAHI_CHROOT_SUCCESS = 0, 46 AVAHI_CHROOT_FAILURE, 47 AVAHI_CHROOT_GET_RESOLV_CONF, 48#ifdef HAVE_DBUS 49 AVAHI_CHROOT_GET_SERVER_INTROSPECT, 50 AVAHI_CHROOT_GET_ENTRY_GROUP_INTROSPECT, 51 AVAHI_CHROOT_GET_ADDRESS_RESOLVER_INTROSPECT, 52 AVAHI_CHROOT_GET_DOMAIN_BROWSER_INTROSPECT, 53 AVAHI_CHROOT_GET_HOST_NAME_RESOLVER_INTROSPECT, 54 AVAHI_CHROOT_GET_SERVICE_BROWSER_INTROSPECT, 55 AVAHI_CHROOT_GET_SERVICE_RESOLVER_INTROSPECT, 56 AVAHI_CHROOT_GET_SERVICE_TYPE_BROWSER_INTROSPECT, 57 AVAHI_CHROOT_GET_RECORD_BROWSER_INTROSPECT, 58#endif 59 AVAHI_CHROOT_UNLINK_PID, 60 AVAHI_CHROOT_UNLINK_SOCKET, 61 AVAHI_CHROOT_MAX 62}; 63 64static const char* const get_file_name_table[AVAHI_CHROOT_MAX] = { 65 NULL, 66 NULL, 67 "/etc/resolv.conf", 68#ifdef HAVE_DBUS 69 AVAHI_DBUS_INTROSPECTION_DIR"/Server.introspect", 70 AVAHI_DBUS_INTROSPECTION_DIR"/EntryGroup.introspect", 71 AVAHI_DBUS_INTROSPECTION_DIR"/AddressResolver.introspect", 72 AVAHI_DBUS_INTROSPECTION_DIR"/DomainBrowser.introspect", 73 AVAHI_DBUS_INTROSPECTION_DIR"/HostNameResolver.introspect", 74 AVAHI_DBUS_INTROSPECTION_DIR"/ServiceBrowser.introspect", 75 AVAHI_DBUS_INTROSPECTION_DIR"/ServiceResolver.introspect", 76 AVAHI_DBUS_INTROSPECTION_DIR"/ServiceTypeBrowser.introspect", 77 AVAHI_DBUS_INTROSPECTION_DIR"/RecordBrowser.introspect", 78#endif 79 NULL, 80 NULL 81}; 82 83static const char *const unlink_file_name_table[AVAHI_CHROOT_MAX] = { 84 NULL, 85 NULL, 86 NULL, 87#ifdef HAVE_DBUS 88 NULL, 89 NULL, 90 NULL, 91 NULL, 92 NULL, 93 NULL, 94 NULL, 95 NULL, 96 NULL, 97#endif 98 AVAHI_DAEMON_RUNTIME_DIR"/pid", 99 AVAHI_SOCKET 100}; 101 102static int helper_fd = -1; 103 104static int send_fd(int fd, int payload_fd) { 105 uint8_t dummy = AVAHI_CHROOT_SUCCESS; 106 struct iovec iov; 107 struct msghdr msg; 108 union { 109 struct cmsghdr hdr; 110 char buf[CMSG_SPACE(sizeof(int))]; 111 } cmsg; 112 113 /* Send a file descriptor over the socket */ 114 115 memset(&iov, 0, sizeof(iov)); 116 memset(&msg, 0, sizeof(msg)); 117 memset(&cmsg, 0, sizeof(cmsg)); 118 119 iov.iov_base = &dummy; 120 iov.iov_len = sizeof(dummy); 121 122 msg.msg_iov = &iov; 123 msg.msg_iovlen = 1; 124 msg.msg_name = NULL; 125 msg.msg_namelen = 0; 126 127 msg.msg_control = &cmsg; 128 msg.msg_controllen = sizeof(cmsg); 129 msg.msg_flags = 0; 130 131 cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int)); 132 cmsg.hdr.cmsg_level = SOL_SOCKET; 133 cmsg.hdr.cmsg_type = SCM_RIGHTS; 134 *((int*) CMSG_DATA(&cmsg.hdr)) = payload_fd; 135 136 if (sendmsg(fd, &msg, 0) < 0) { 137 avahi_log_error("sendmsg() failed: %s", strerror(errno)); 138 return -1; 139 } 140 141 return 0; 142} 143 144static int recv_fd(int fd) { 145 uint8_t dummy; 146 struct iovec iov; 147 struct msghdr msg; 148 union { 149 struct cmsghdr hdr; 150 char buf[CMSG_SPACE(sizeof(int))]; 151 } cmsg; 152 153 /* Receive a file descriptor from a socket */ 154 155 memset(&iov, 0, sizeof(iov)); 156 memset(&msg, 0, sizeof(msg)); 157 memset(&cmsg, 0, sizeof(cmsg)); 158 159 iov.iov_base = &dummy; 160 iov.iov_len = sizeof(dummy); 161 162 msg.msg_iov = &iov; 163 msg.msg_iovlen = 1; 164 msg.msg_name = NULL; 165 msg.msg_namelen = 0; 166 167 msg.msg_control = cmsg.buf; 168 msg.msg_controllen = sizeof(cmsg); 169 msg.msg_flags = 0; 170 171 cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int)); 172 cmsg.hdr.cmsg_level = SOL_SOCKET; 173 cmsg.hdr.cmsg_type = SCM_RIGHTS; 174 *((int*) CMSG_DATA(&cmsg.hdr)) = -1; 175 176 if (recvmsg(fd, &msg, 0) <= 0) { 177 avahi_log_error("recvmsg() failed: %s", strerror(errno)); 178 return -1; 179 } else { 180 struct cmsghdr* h; 181 182 if (dummy != AVAHI_CHROOT_SUCCESS) { 183 errno = EINVAL; 184 return -1; 185 } 186 187 if (!(h = CMSG_FIRSTHDR(&msg))) { 188 avahi_log_error("recvmsg() sent no fd."); 189 errno = EINVAL; 190 return -1; 191 } 192 193 assert(h->cmsg_len = CMSG_LEN(sizeof(int))); 194 assert(h->cmsg_level = SOL_SOCKET); 195 assert(h->cmsg_type == SCM_RIGHTS); 196 197 return *((int*)CMSG_DATA(h)); 198 } 199} 200 201static int helper_main(int fd) { 202 int ret = 1; 203 assert(fd >= 0); 204 205 /* This is the main function of our helper process which is forked 206 * off to access files outside the chroot environment. Keep in 207 * mind that this code is security sensitive! */ 208 209 avahi_log_debug(__FILE__": chroot() helper started"); 210 211 for (;;) { 212 uint8_t command; 213 ssize_t r; 214 215 if ((r = read(fd, &command, sizeof(command))) <= 0) { 216 217 /* EOF? */ 218 if (r == 0) 219 break; 220 221 avahi_log_error(__FILE__": read() failed: %s", strerror(errno)); 222 goto fail; 223 } 224 225 assert(r == sizeof(command)); 226 227 avahi_log_debug(__FILE__": chroot() helper got command %02x", command); 228 229 switch (command) { 230#ifdef HAVE_DBUS 231 case AVAHI_CHROOT_GET_SERVER_INTROSPECT: 232 case AVAHI_CHROOT_GET_ENTRY_GROUP_INTROSPECT: 233 case AVAHI_CHROOT_GET_ADDRESS_RESOLVER_INTROSPECT: 234 case AVAHI_CHROOT_GET_DOMAIN_BROWSER_INTROSPECT: 235 case AVAHI_CHROOT_GET_HOST_NAME_RESOLVER_INTROSPECT: 236 case AVAHI_CHROOT_GET_SERVICE_BROWSER_INTROSPECT: 237 case AVAHI_CHROOT_GET_SERVICE_RESOLVER_INTROSPECT: 238 case AVAHI_CHROOT_GET_SERVICE_TYPE_BROWSER_INTROSPECT: 239 case AVAHI_CHROOT_GET_RECORD_BROWSER_INTROSPECT: 240#endif 241 case AVAHI_CHROOT_GET_RESOLV_CONF: { 242 int payload; 243 244 if ((payload = open(get_file_name_table[(int) command], O_RDONLY)) < 0) { 245 uint8_t c = AVAHI_CHROOT_FAILURE; 246 247 avahi_log_error(__FILE__": open() failed: %s", strerror(errno)); 248 249 if (write(fd, &c, sizeof(c)) != sizeof(c)) { 250 avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno)); 251 goto fail; 252 } 253 254 break; 255 } 256 257 if (send_fd(fd, payload) < 0) 258 goto fail; 259 260 close(payload); 261 262 break; 263 } 264 265 case AVAHI_CHROOT_UNLINK_SOCKET: 266 case AVAHI_CHROOT_UNLINK_PID: { 267 uint8_t c = AVAHI_CHROOT_SUCCESS; 268 269 unlink(unlink_file_name_table[(int) command]); 270 271 if (write(fd, &c, sizeof(c)) != sizeof(c)) { 272 avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno)); 273 goto fail; 274 } 275 276 break; 277 } 278 279 default: 280 avahi_log_error(__FILE__": Unknown command %02x.", command); 281 break; 282 } 283 } 284 285 ret = 0; 286 287fail: 288 289 avahi_log_debug(__FILE__": chroot() helper exiting with return value %i", ret); 290 291 return ret; 292} 293 294int avahi_chroot_helper_start(const char *argv0) { 295 int sock[2]; 296 pid_t pid; 297 298 assert(helper_fd < 0); 299 300 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) < 0) { 301 avahi_log_error("socketpair() failed: %s", strerror(errno)); 302 return -1; 303 } 304 305 if ((pid = fork()) < 0) { 306 close(sock[0]); 307 close(sock[1]); 308 avahi_log_error(__FILE__": fork() failed: %s", strerror(errno)); 309 return -1; 310 } else if (pid == 0) { 311 312 setsid(); 313 314 /* Drop all remaining capabilities */ 315 avahi_caps_drop_all(); 316 317 avahi_set_proc_title(argv0, "%s: chroot helper", argv0); 318 319 daemon_retval_done(); 320 321 close(sock[0]); 322 helper_main(sock[1]); 323 _exit(0); 324 } 325 326 close(sock[1]); 327 helper_fd = sock[0]; 328 329 return 0; 330} 331 332void avahi_chroot_helper_shutdown(void) { 333 334 if (helper_fd <= 0) 335 return; 336 337 close(helper_fd); 338 helper_fd = -1; 339} 340 341int avahi_chroot_helper_get_fd(const char *fname) { 342 343 if (helper_fd >= 0) { 344 uint8_t command; 345 346 for (command = 2; command < AVAHI_CHROOT_MAX; command++) 347 if (get_file_name_table[(int) command] && 348 strcmp(fname, get_file_name_table[(int) command]) == 0) 349 break; 350 351 if (command >= AVAHI_CHROOT_MAX) { 352 avahi_log_error("chroot() helper accessed for invalid file name"); 353 errno = EACCES; 354 return -1; 355 } 356 357 assert(get_file_name_table[(int) command]); 358 359 if (write(helper_fd, &command, sizeof(command)) < 0) { 360 avahi_log_error("write() failed: %s\n", strerror(errno)); 361 return -1; 362 } 363 364 return recv_fd(helper_fd); 365 366 } else 367 return open(fname, O_RDONLY); 368} 369 370 371FILE *avahi_chroot_helper_get_file(const char *fname) { 372 FILE *f; 373 int fd; 374 375 if ((fd = avahi_chroot_helper_get_fd(fname)) < 0) 376 return NULL; 377 378 f = fdopen(fd, "r"); 379 assert(f); 380 381 return f; 382} 383 384int avahi_chroot_helper_unlink(const char *fname) { 385 386 if (helper_fd >= 0) { 387 uint8_t c, command; 388 ssize_t r; 389 390 for (command = 2; command < AVAHI_CHROOT_MAX; command++) 391 if (unlink_file_name_table[(int) command] && 392 strcmp(fname, unlink_file_name_table[(int) command]) == 0) 393 break; 394 395 if (command >= AVAHI_CHROOT_MAX) { 396 avahi_log_error("chroot() helper accessed for invalid file name"); 397 errno = EACCES; 398 return -1; 399 } 400 401 if (write(helper_fd, &command, sizeof(command)) < 0) { 402 avahi_log_error("write() failed: %s\n", strerror(errno)); 403 return -1; 404 } 405 406 if ((r = read(helper_fd, &c, sizeof(c))) < 0) { 407 avahi_log_error("read() failed: %s\n", r < 0 ? strerror(errno) : "EOF"); 408 return -1; 409 } 410 411 return 0; 412 413 } else 414 415 return unlink(fname); 416 417} 418