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     NULL,
87     NULL,
88     NULL,
89     NULL,
90     NULL,
91     NULL,
92     NULL,
93     NULL,
94     NULL,
95 #endif
96     AVAHI_DAEMON_RUNTIME_DIR"/pid",
97     AVAHI_SOCKET
98 };
99 
100 static int helper_fd = -1;
101 
send_fd(int fd,int payload_fd)102 static int send_fd(int fd, int payload_fd) {
103     uint8_t dummy = AVAHI_CHROOT_SUCCESS;
104     struct iovec iov;
105     struct msghdr msg;
106     union {
107         struct cmsghdr hdr;
108         char buf[CMSG_SPACE(sizeof(int))];
109     } cmsg;
110 
111     /* Send a file descriptor over the socket */
112 
113     memset(&iov, 0, sizeof(iov));
114     memset(&msg, 0, sizeof(msg));
115     memset(&cmsg, 0, sizeof(cmsg));
116 
117     iov.iov_base = &dummy;
118     iov.iov_len = sizeof(dummy);
119 
120     msg.msg_iov = &iov;
121     msg.msg_iovlen = 1;
122     msg.msg_name = NULL;
123     msg.msg_namelen = 0;
124 
125     msg.msg_control = &cmsg;
126     msg.msg_controllen = sizeof(cmsg);
127     msg.msg_flags = 0;
128 
129     cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
130     cmsg.hdr.cmsg_level = SOL_SOCKET;
131     cmsg.hdr.cmsg_type = SCM_RIGHTS;
132     *((int*) CMSG_DATA(&cmsg.hdr)) = payload_fd;
133 
134     if (sendmsg(fd, &msg, 0) < 0) {
135         avahi_log_error("sendmsg() failed: %s", strerror(errno));
136         return -1;
137     }
138 
139     return 0;
140 }
141 
recv_fd(int fd)142 static int recv_fd(int fd) {
143     uint8_t dummy;
144     struct iovec iov;
145     struct msghdr msg;
146     union {
147         struct cmsghdr hdr;
148         char buf[CMSG_SPACE(sizeof(int))];
149     } cmsg;
150 
151     /* Receive a file descriptor from a socket */
152 
153     memset(&iov, 0, sizeof(iov));
154     memset(&msg, 0, sizeof(msg));
155     memset(&cmsg, 0, sizeof(cmsg));
156 
157     iov.iov_base = &dummy;
158     iov.iov_len = sizeof(dummy);
159 
160     msg.msg_iov = &iov;
161     msg.msg_iovlen = 1;
162     msg.msg_name = NULL;
163     msg.msg_namelen = 0;
164 
165     msg.msg_control = cmsg.buf;
166     msg.msg_controllen = sizeof(cmsg);
167     msg.msg_flags = 0;
168 
169     cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
170     cmsg.hdr.cmsg_level = SOL_SOCKET;
171     cmsg.hdr.cmsg_type = SCM_RIGHTS;
172     *((int*) CMSG_DATA(&cmsg.hdr)) = -1;
173 
174     if (recvmsg(fd, &msg, 0) <= 0) {
175         avahi_log_error("recvmsg() failed: %s", strerror(errno));
176         return -1;
177     } else {
178         struct cmsghdr* h;
179 
180         if (dummy != AVAHI_CHROOT_SUCCESS) {
181             errno = EINVAL;
182             return -1;
183         }
184 
185         if (!(h = CMSG_FIRSTHDR(&msg))) {
186             avahi_log_error("recvmsg() sent no fd.");
187             errno = EINVAL;
188             return -1;
189         }
190 
191         assert(h->cmsg_len = CMSG_LEN(sizeof(int)));
192         assert(h->cmsg_level = SOL_SOCKET);
193         assert(h->cmsg_type == SCM_RIGHTS);
194 
195         return *((int*)CMSG_DATA(h));
196     }
197 }
198 
helper_main(int fd)199 static int helper_main(int fd) {
200     int ret = 1;
201     assert(fd >= 0);
202 
203     /* This is the main function of our helper process which is forked
204      * off to access files outside the chroot environment. Keep in
205      * mind that this code is security sensitive! */
206 
207     avahi_log_debug(__FILE__": chroot() helper started");
208 
209     for (;;) {
210         uint8_t command;
211         ssize_t r;
212 
213         if ((r = read(fd, &command, sizeof(command))) <= 0) {
214 
215             /* EOF? */
216             if (r == 0)
217                 break;
218 
219             avahi_log_error(__FILE__": read() failed: %s", strerror(errno));
220             goto fail;
221         }
222 
223         assert(r == sizeof(command));
224 
225         avahi_log_debug(__FILE__": chroot() helper got command %02x", command);
226 
227         switch (command) {
228 #ifdef HAVE_DBUS
229             case AVAHI_CHROOT_GET_SERVER_INTROSPECT:
230             case AVAHI_CHROOT_GET_ENTRY_GROUP_INTROSPECT:
231             case AVAHI_CHROOT_GET_ADDRESS_RESOLVER_INTROSPECT:
232             case AVAHI_CHROOT_GET_DOMAIN_BROWSER_INTROSPECT:
233             case AVAHI_CHROOT_GET_HOST_NAME_RESOLVER_INTROSPECT:
234             case AVAHI_CHROOT_GET_SERVICE_BROWSER_INTROSPECT:
235             case AVAHI_CHROOT_GET_SERVICE_RESOLVER_INTROSPECT:
236             case AVAHI_CHROOT_GET_SERVICE_TYPE_BROWSER_INTROSPECT:
237             case AVAHI_CHROOT_GET_RECORD_BROWSER_INTROSPECT:
238 #endif
239             case AVAHI_CHROOT_GET_RESOLV_CONF: {
240                 int payload;
241 
242                 if ((payload = open(get_file_name_table[(int) command], O_RDONLY)) < 0) {
243                     uint8_t c = AVAHI_CHROOT_FAILURE;
244 
245                     avahi_log_error(__FILE__": open() failed: %s", strerror(errno));
246 
247                     if (write(fd, &c, sizeof(c)) != sizeof(c)) {
248                         avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno));
249                         goto fail;
250                     }
251 
252                     break;
253                 }
254 
255                 if (send_fd(fd, payload) < 0)
256                     goto fail;
257 
258                 close(payload);
259 
260                 break;
261             }
262 
263             case AVAHI_CHROOT_UNLINK_SOCKET:
264             case AVAHI_CHROOT_UNLINK_PID: {
265                 uint8_t c = AVAHI_CHROOT_SUCCESS;
266 
267                 unlink(unlink_file_name_table[(int) command]);
268 
269                 if (write(fd, &c, sizeof(c)) != sizeof(c)) {
270                     avahi_log_error(__FILE__": write() failed: %s\n", strerror(errno));
271                     goto fail;
272                 }
273 
274                 break;
275             }
276 
277             default:
278                 avahi_log_error(__FILE__": Unknown command %02x.", command);
279                 break;
280         }
281     }
282 
283     ret = 0;
284 
285 fail:
286 
287     avahi_log_debug(__FILE__": chroot() helper exiting with return value %i", ret);
288 
289     return ret;
290 }
291 
avahi_chroot_helper_start(const char * argv0)292 int avahi_chroot_helper_start(const char *argv0) {
293     int sock[2];
294     pid_t pid;
295 
296     assert(helper_fd < 0);
297 
298     if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) < 0) {
299         avahi_log_error("socketpair() failed: %s", strerror(errno));
300         return -1;
301     }
302 
303     if ((pid = fork()) < 0) {
304         close(sock[0]);
305         close(sock[1]);
306         avahi_log_error(__FILE__": fork() failed: %s", strerror(errno));
307         return -1;
308     } else if (pid == 0) {
309 
310         /* Drop all remaining capabilities */
311         avahi_caps_drop_all();
312 
313         avahi_set_proc_title(argv0, "%s: chroot helper", argv0);
314 
315         daemon_retval_done();
316 
317         close(sock[0]);
318         helper_main(sock[1]);
319         _exit(0);
320     }
321 
322     close(sock[1]);
323     helper_fd = sock[0];
324 
325     return 0;
326 }
327 
avahi_chroot_helper_shutdown(void)328 void avahi_chroot_helper_shutdown(void) {
329 
330     if (helper_fd <= 0)
331         return;
332 
333     close(helper_fd);
334     helper_fd = -1;
335 }
336 
avahi_chroot_helper_get_fd(const char * fname)337 int avahi_chroot_helper_get_fd(const char *fname) {
338 
339     if (helper_fd >= 0) {
340         uint8_t command;
341 
342         for (command = 2; command < AVAHI_CHROOT_MAX; command++)
343             if (get_file_name_table[(int) command] &&
344                 strcmp(fname, get_file_name_table[(int) command]) == 0)
345                 break;
346 
347         if (command >= AVAHI_CHROOT_MAX) {
348             avahi_log_error("chroot() helper accessed for invalid file name");
349             errno = EACCES;
350             return -1;
351         }
352 
353         assert(get_file_name_table[(int) command]);
354 
355         if (write(helper_fd, &command, sizeof(command)) < 0) {
356             avahi_log_error("write() failed: %s\n", strerror(errno));
357             return -1;
358         }
359 
360         return recv_fd(helper_fd);
361 
362     } else
363         return open(fname, O_RDONLY);
364 }
365 
366 
avahi_chroot_helper_get_file(const char * fname)367 FILE *avahi_chroot_helper_get_file(const char *fname) {
368     FILE *f;
369     int fd;
370 
371     if ((fd = avahi_chroot_helper_get_fd(fname)) < 0)
372         return NULL;
373 
374     f = fdopen(fd, "r");
375     assert(f);
376 
377     return f;
378 }
379 
avahi_chroot_helper_unlink(const char * fname)380 int avahi_chroot_helper_unlink(const char *fname) {
381 
382     if (helper_fd >= 0) {
383         uint8_t c, command;
384         ssize_t r;
385 
386         for (command = 2; command < AVAHI_CHROOT_MAX; command++)
387             if (unlink_file_name_table[(int) command] &&
388                 strcmp(fname, unlink_file_name_table[(int) command]) == 0)
389                 break;
390 
391         if (command >= AVAHI_CHROOT_MAX) {
392             avahi_log_error("chroot() helper accessed for invalid file name");
393             errno = EACCES;
394             return -1;
395         }
396 
397         if (write(helper_fd, &command, sizeof(command)) < 0 &&
398             (errno != EPIPE && errno != ECONNRESET)) {
399             avahi_log_error("write() failed: %s\n", strerror(errno));
400             return -1;
401         }
402 
403         if ((r = read(helper_fd, &c, sizeof(c))) < 0 &&
404             (errno != EPIPE && errno != ECONNRESET)) {
405             avahi_log_error("read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
406             return -1;
407         }
408 
409         return 0;
410 
411     } else
412 
413         return unlink(fname);
414 
415 }
416