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