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