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