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