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 <assert.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <sys/un.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <sys/stat.h>
34
35 #include <avahi-common/llist.h>
36 #include "avahi-common/avahi-malloc.h"
37 #include <avahi-common/error.h>
38
39 #include <avahi-core/log.h>
40 #include <avahi-core/lookup.h>
41 #include <avahi-core/dns-srv-rr.h>
42
43 #include "simple-protocol.h"
44 #include "main.h"
45 #include "sd-daemon.h"
46
47 #ifdef ENABLE_CHROOT
48 #include "chroot.h"
49 #endif
50
51 #ifndef AF_LOCAL
52 #define AF_LOCAL AF_UNIX
53 #endif
54 #ifndef PF_LOCAL
55 #define PF_LOCAL PF_UNIX
56 #endif
57
58 #define BUFFER_SIZE (20*1024)
59
60 #define CLIENTS_MAX 50
61
62 typedef struct Client Client;
63 typedef struct Server Server;
64
65 typedef enum {
66 CLIENT_IDLE,
67 CLIENT_RESOLVE_HOSTNAME,
68 CLIENT_RESOLVE_ADDRESS,
69 CLIENT_BROWSE_DNS_SERVERS,
70 CLIENT_DEAD
71 } ClientState;
72
73 struct Client {
74 Server *server;
75
76 ClientState state;
77
78 int fd;
79 AvahiWatch *watch;
80
81 char inbuf[BUFFER_SIZE], outbuf[BUFFER_SIZE];
82 size_t inbuf_length, outbuf_length;
83
84 AvahiSHostNameResolver *host_name_resolver;
85 AvahiSAddressResolver *address_resolver;
86 AvahiSDNSServerBrowser *dns_server_browser;
87
88 AvahiProtocol afquery;
89
90 AVAHI_LLIST_FIELDS(Client, clients);
91 };
92
93 struct Server {
94 const AvahiPoll *poll_api;
95 int fd;
96 AvahiWatch *watch;
97 AVAHI_LLIST_HEAD(Client, clients);
98
99 unsigned n_clients;
100 int remove_socket;
101 };
102
103 static Server *server = NULL;
104
105 static void client_work(AvahiWatch *watch, int fd, AvahiWatchEvent events, void *userdata);
106
client_free(Client * c)107 static void client_free(Client *c) {
108 assert(c);
109
110 assert(c->server->n_clients >= 1);
111 c->server->n_clients--;
112
113 if (c->host_name_resolver)
114 avahi_s_host_name_resolver_free(c->host_name_resolver);
115
116 if (c->address_resolver)
117 avahi_s_address_resolver_free(c->address_resolver);
118
119 if (c->dns_server_browser)
120 avahi_s_dns_server_browser_free(c->dns_server_browser);
121
122 c->server->poll_api->watch_free(c->watch);
123 close(c->fd);
124
125 AVAHI_LLIST_REMOVE(Client, clients, c->server->clients, c);
126 avahi_free(c);
127 }
128
client_new(Server * s,int fd)129 static void client_new(Server *s, int fd) {
130 Client *c;
131
132 assert(fd >= 0);
133
134 c = avahi_new(Client, 1);
135 c->server = s;
136 c->fd = fd;
137 c->state = CLIENT_IDLE;
138
139 c->inbuf_length = c->outbuf_length = 0;
140
141 c->host_name_resolver = NULL;
142 c->address_resolver = NULL;
143 c->dns_server_browser = NULL;
144
145 c->watch = s->poll_api->watch_new(s->poll_api, fd, AVAHI_WATCH_IN, client_work, c);
146
147 AVAHI_LLIST_PREPEND(Client, clients, s->clients, c);
148 s->n_clients++;
149 }
150
client_output(Client * c,const uint8_t * data,size_t size)151 static void client_output(Client *c, const uint8_t*data, size_t size) {
152 size_t k, m;
153
154 assert(c);
155 assert(data);
156
157 if (!size)
158 return;
159
160 k = sizeof(c->outbuf) - c->outbuf_length;
161 m = size > k ? k : size;
162
163 memcpy(c->outbuf + c->outbuf_length, data, m);
164 c->outbuf_length += m;
165
166 server->poll_api->watch_update(c->watch, AVAHI_WATCH_OUT);
167 }
168
client_output_printf(Client * c,const char * format,...)169 static void client_output_printf(Client *c, const char *format, ...) {
170 char *t;
171 va_list ap;
172
173 va_start(ap, format);
174 t = avahi_strdup_vprintf(format, ap);
175 va_end(ap);
176
177 client_output(c, (uint8_t*) t, strlen(t));
178 avahi_free(t);
179 }
180
host_name_resolver_callback(AVAHI_GCC_UNUSED AvahiSHostNameResolver * r,AvahiIfIndex iface,AvahiProtocol protocol,AvahiResolverEvent event,const char * hostname,const AvahiAddress * a,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)181 static void host_name_resolver_callback(
182 AVAHI_GCC_UNUSED AvahiSHostNameResolver *r,
183 AvahiIfIndex iface,
184 AvahiProtocol protocol,
185 AvahiResolverEvent event,
186 const char *hostname,
187 const AvahiAddress *a,
188 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
189 void* userdata) {
190
191 Client *c = userdata;
192
193 assert(c);
194
195 if (event == AVAHI_RESOLVER_FAILURE)
196 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server)));
197 else if (event == AVAHI_RESOLVER_FOUND) {
198 char t[AVAHI_ADDRESS_STR_MAX];
199 avahi_address_snprint(t, sizeof(t), a);
200 client_output_printf(c, "+ %i %u %s %s\n", iface, protocol, hostname, t);
201 }
202
203 c->state = CLIENT_DEAD;
204 }
205
address_resolver_callback(AVAHI_GCC_UNUSED AvahiSAddressResolver * r,AvahiIfIndex iface,AvahiProtocol protocol,AvahiResolverEvent event,AVAHI_GCC_UNUSED const AvahiAddress * a,const char * hostname,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)206 static void address_resolver_callback(
207 AVAHI_GCC_UNUSED AvahiSAddressResolver *r,
208 AvahiIfIndex iface,
209 AvahiProtocol protocol,
210 AvahiResolverEvent event,
211 AVAHI_GCC_UNUSED const AvahiAddress *a,
212 const char *hostname,
213 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
214 void* userdata) {
215
216 Client *c = userdata;
217
218 assert(c);
219
220 if (event == AVAHI_RESOLVER_FAILURE)
221 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server)));
222 else if (event == AVAHI_RESOLVER_FOUND)
223 client_output_printf(c, "+ %i %u %s\n", iface, protocol, hostname);
224
225 c->state = CLIENT_DEAD;
226 }
227
dns_server_browser_callback(AVAHI_GCC_UNUSED AvahiSDNSServerBrowser * b,AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,AVAHI_GCC_UNUSED const char * host_name,const AvahiAddress * a,uint16_t port,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)228 static void dns_server_browser_callback(
229 AVAHI_GCC_UNUSED AvahiSDNSServerBrowser *b,
230 AvahiIfIndex interface,
231 AvahiProtocol protocol,
232 AvahiBrowserEvent event,
233 AVAHI_GCC_UNUSED const char *host_name,
234 const AvahiAddress *a,
235 uint16_t port,
236 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
237 void* userdata) {
238
239 Client *c = userdata;
240 char t[AVAHI_ADDRESS_STR_MAX];
241
242 assert(c);
243
244 if (!a)
245 return;
246
247 switch (event) {
248 case AVAHI_BROWSER_FAILURE:
249 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server)));
250 c->state = CLIENT_DEAD;
251 break;
252
253 case AVAHI_BROWSER_ALL_FOR_NOW:
254 case AVAHI_BROWSER_CACHE_EXHAUSTED:
255 break;
256
257 case AVAHI_BROWSER_NEW:
258 case AVAHI_BROWSER_REMOVE:
259
260 avahi_address_snprint(t, sizeof(t), a);
261 client_output_printf(c, "%c %i %u %s %u\n", event == AVAHI_BROWSER_NEW ? '>' : '<', interface, protocol, t, port);
262 break;
263 }
264 }
265
handle_line(Client * c,const char * s)266 static void handle_line(Client *c, const char *s) {
267 char cmd[64], arg[64];
268 int n_args;
269
270 assert(c);
271 assert(s);
272
273 if (c->state != CLIENT_IDLE)
274 return;
275
276 if ((n_args = sscanf(s, "%63s %63s", cmd, arg)) < 1 ) {
277 client_output_printf(c, "%+i Failed to parse command, try \"HELP\".\n", AVAHI_ERR_INVALID_OPERATION);
278 c->state = CLIENT_DEAD;
279 return;
280 }
281
282 if (strcmp(cmd, "HELP") == 0) {
283 client_output_printf(c,
284 "+ Available commands are:\n"
285 "+ RESOLVE-HOSTNAME <hostname>\n"
286 "+ RESOLVE-HOSTNAME-IPV6 <hostname>\n"
287 "+ RESOLVE-HOSTNAME-IPV4 <hostname>\n"
288 "+ RESOLVE-ADDRESS <address>\n"
289 "+ BROWSE-DNS-SERVERS\n"
290 "+ BROWSE-DNS-SERVERS-IPV4\n"
291 "+ BROWSE-DNS-SERVERS-IPV6\n");
292 c->state = CLIENT_DEAD; }
293 else if (strcmp(cmd, "FUCK") == 0 && n_args == 1) {
294 client_output_printf(c, "+ FUCK: Go fuck yourself!\n");
295 c->state = CLIENT_DEAD;
296 } else if (strcmp(cmd, "RESOLVE-HOSTNAME-IPV4") == 0 && n_args == 2) {
297 c->state = CLIENT_RESOLVE_HOSTNAME;
298 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_INET, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c)))
299 goto fail;
300
301 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg);
302 } else if (strcmp(cmd, "RESOLVE-HOSTNAME-IPV6") == 0 && n_args == 2) {
303 c->state = CLIENT_RESOLVE_HOSTNAME;
304 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_INET6, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c)))
305 goto fail;
306
307 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg);
308 } else if (strcmp(cmd, "RESOLVE-HOSTNAME") == 0 && n_args == 2) {
309 c->state = CLIENT_RESOLVE_HOSTNAME;
310 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c)))
311 goto fail;
312
313 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg);
314 } else if (strcmp(cmd, "RESOLVE-ADDRESS") == 0 && n_args == 2) {
315 AvahiAddress addr;
316
317 if (!(avahi_address_parse(arg, AVAHI_PROTO_UNSPEC, &addr))) {
318 client_output_printf(c, "%+i Failed to parse address \"%s\".\n", AVAHI_ERR_INVALID_ADDRESS, arg);
319 c->state = CLIENT_DEAD;
320 } else {
321 c->state = CLIENT_RESOLVE_ADDRESS;
322 if (!(c->address_resolver = avahi_s_address_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, &addr, AVAHI_LOOKUP_USE_MULTICAST, address_resolver_callback, c)))
323 goto fail;
324 }
325
326 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg);
327
328 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS-IPV4") == 0 && n_args == 1) {
329 c->state = CLIENT_BROWSE_DNS_SERVERS;
330 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_INET, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c)))
331 goto fail;
332 client_output_printf(c, "+ Browsing ...\n");
333
334 avahi_log_debug(__FILE__": Got %s request.", cmd);
335
336 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS-IPV6") == 0 && n_args == 1) {
337 c->state = CLIENT_BROWSE_DNS_SERVERS;
338 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_INET6, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c)))
339 goto fail;
340 client_output_printf(c, "+ Browsing ...\n");
341
342 avahi_log_debug(__FILE__": Got %s request.", cmd);
343
344 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS") == 0 && n_args == 1) {
345 c->state = CLIENT_BROWSE_DNS_SERVERS;
346 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c)))
347 goto fail;
348 client_output_printf(c, "+ Browsing ...\n");
349
350 avahi_log_debug(__FILE__": Got %s request.", cmd);
351
352 } else {
353 client_output_printf(c, "%+i Invalid command \"%s\", try \"HELP\".\n", AVAHI_ERR_INVALID_OPERATION, cmd);
354 c->state = CLIENT_DEAD;
355
356 avahi_log_debug(__FILE__": Got invalid request '%s'.", cmd);
357 }
358
359 return;
360
361 fail:
362 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server)));
363 c->state = CLIENT_DEAD;
364 }
365
handle_input(Client * c)366 static void handle_input(Client *c) {
367 assert(c);
368
369 for (;;) {
370 char *e;
371 size_t k;
372
373 if (!(e = memchr(c->inbuf, '\n', c->inbuf_length)))
374 break;
375
376 k = e - (char*) c->inbuf;
377 *e = 0;
378
379 handle_line(c, c->inbuf);
380 c->inbuf_length -= k + 1;
381 memmove(c->inbuf, e+1, c->inbuf_length);
382 }
383 }
384
client_work(AvahiWatch * watch,AVAHI_GCC_UNUSED int fd,AvahiWatchEvent events,void * userdata)385 static void client_work(AvahiWatch *watch, AVAHI_GCC_UNUSED int fd, AvahiWatchEvent events, void *userdata) {
386 Client *c = userdata;
387
388 assert(c);
389
390 if ((events & AVAHI_WATCH_IN) && c->inbuf_length < sizeof(c->inbuf)) {
391 ssize_t r;
392
393 if ((r = read(c->fd, c->inbuf + c->inbuf_length, sizeof(c->inbuf) - c->inbuf_length)) <= 0) {
394 if (r < 0)
395 avahi_log_warn("read(): %s", strerror(errno));
396 client_free(c);
397 return;
398 }
399
400 c->inbuf_length += r;
401 assert(c->inbuf_length <= sizeof(c->inbuf));
402
403 handle_input(c);
404 }
405
406 if ((events & AVAHI_WATCH_OUT) && c->outbuf_length > 0) {
407 ssize_t r;
408
409 if ((r = write(c->fd, c->outbuf, c->outbuf_length)) < 0) {
410 avahi_log_warn("write(): %s", strerror(errno));
411 client_free(c);
412 return;
413 }
414
415 assert((size_t) r <= c->outbuf_length);
416 c->outbuf_length -= r;
417
418 if (c->outbuf_length)
419 memmove(c->outbuf, c->outbuf + r, c->outbuf_length - r);
420
421 if (c->outbuf_length == 0 && c->state == CLIENT_DEAD) {
422 client_free(c);
423 return;
424 }
425 }
426
427 c->server->poll_api->watch_update(
428 watch,
429 (c->outbuf_length > 0 ? AVAHI_WATCH_OUT : 0) |
430 (c->inbuf_length < sizeof(c->inbuf) ? AVAHI_WATCH_IN : 0));
431 }
432
server_work(AVAHI_GCC_UNUSED AvahiWatch * watch,int fd,AvahiWatchEvent events,void * userdata)433 static void server_work(AVAHI_GCC_UNUSED AvahiWatch *watch, int fd, AvahiWatchEvent events, void *userdata) {
434 Server *s = userdata;
435
436 assert(s);
437
438 if (events & AVAHI_WATCH_IN) {
439 int cfd;
440
441 if ((cfd = accept(fd, NULL, NULL)) < 0)
442 avahi_log_error("accept(): %s", strerror(errno));
443 else
444 client_new(s, cfd);
445 }
446 }
447
simple_protocol_setup(const AvahiPoll * poll_api)448 int simple_protocol_setup(const AvahiPoll *poll_api) {
449 struct sockaddr_un sa;
450 mode_t u;
451 int n;
452
453 assert(!server);
454
455 server = avahi_new(Server, 1);
456 server->poll_api = poll_api;
457 server->remove_socket = 0;
458 server->fd = -1;
459 server->n_clients = 0;
460 AVAHI_LLIST_HEAD_INIT(Client, server->clients);
461 server->watch = NULL;
462
463 u = umask(0000);
464
465 if ((n = sd_listen_fds(1)) < 0) {
466 avahi_log_warn("Failed to acquire systemd file descriptors: %s", strerror(-n));
467 goto fail;
468 }
469
470 if (n > 1) {
471 avahi_log_warn("Too many systemd file descriptors passed.");
472 goto fail;
473 }
474
475 if (n == 1) {
476 int r;
477
478 if ((r = sd_is_socket(SD_LISTEN_FDS_START, AF_LOCAL, SOCK_STREAM, 1)) < 0) {
479 avahi_log_warn("Passed systemd file descriptor is of wrong type: %s", strerror(-r));
480 goto fail;
481 }
482
483 server->fd = SD_LISTEN_FDS_START;
484
485 } else {
486
487 if ((server->fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
488 avahi_log_warn("socket(AF_LOCAL, SOCK_STREAM, 0): %s", strerror(errno));
489 goto fail;
490 }
491
492 memset(&sa, 0, sizeof(sa));
493 sa.sun_family = AF_LOCAL;
494 strncpy(sa.sun_path, AVAHI_SOCKET, sizeof(sa.sun_path)-1);
495
496 /* We simply remove existing UNIX sockets under this name. The
497 Avahi daemon makes sure that it runs only once on a host,
498 therefore sockets that already exist are stale and may be
499 removed without any ill effects */
500
501 unlink(AVAHI_SOCKET);
502
503 if (bind(server->fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
504 avahi_log_warn("bind(): %s", strerror(errno));
505 goto fail;
506 }
507
508 server->remove_socket = 1;
509
510 if (listen(server->fd, SOMAXCONN) < 0) {
511 avahi_log_warn("listen(): %s", strerror(errno));
512 goto fail;
513 }
514 }
515
516 umask(u);
517
518 server->watch = poll_api->watch_new(poll_api, server->fd, AVAHI_WATCH_IN, server_work, server);
519
520 return 0;
521
522 fail:
523
524 umask(u);
525 simple_protocol_shutdown();
526
527 return -1;
528 }
529
simple_protocol_shutdown(void)530 void simple_protocol_shutdown(void) {
531
532 if (server) {
533
534 if (server->remove_socket)
535 #ifdef ENABLE_CHROOT
536 avahi_chroot_helper_unlink(AVAHI_SOCKET);
537 #else
538 unlink(AVAHI_SOCKET);
539 #endif
540
541 while (server->clients)
542 client_free(server->clients);
543
544 if (server->watch)
545 server->poll_api->watch_free(server->watch);
546
547 if (server->fd >= 0)
548 close(server->fd);
549
550 avahi_free(server);
551
552 server = NULL;
553 }
554 }
555
simple_protocol_restart_queries(void)556 void simple_protocol_restart_queries(void) {
557 Client *c;
558
559 /* Restart queries in case of local domain name changes */
560
561 assert(server);
562
563 for (c = server->clients; c; c = c->clients_next)
564 if (c->state == CLIENT_BROWSE_DNS_SERVERS && c->dns_server_browser) {
565 avahi_s_dns_server_browser_free(c->dns_server_browser);
566 c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c);
567 }
568 }
569