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