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 
send_fd(int fd,int payload_fd)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 
recv_fd(int fd)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 
helper_main(int fd)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 
avahi_chroot_helper_start(const char * argv0)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 
avahi_chroot_helper_shutdown(void)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 
avahi_chroot_helper_get_fd(const char * fname)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 
avahi_chroot_helper_get_file(const char * fname)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 
avahi_chroot_helper_unlink(const char * fname)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