]> git.meshlink.io Git - catta/blob - avahi-daemon/static-services.c
fix avahi_netlink_new to allow multiple netlinks per process
[catta] / avahi-daemon / static-services.c
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 <sys/stat.h>
25 #include <glob.h>
26 #include <limits.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32
33 #ifdef USE_EXPAT_H
34 #include <expat.h>
35 #endif /* USE_EXPAT_H */
36
37 #ifdef USE_BSDXML_H
38 #include <bsdxml.h>
39 #endif /* USE_BSDXML_H */
40
41 #include <avahi-common/llist.h>
42 #include <avahi-common/malloc.h>
43 #include <avahi-common/alternative.h>
44 #include <avahi-common/error.h>
45 #include <avahi-common/domain.h>
46 #include <avahi-core/log.h>
47 #include <avahi-core/publish.h>
48
49 #include "main.h"
50 #include "static-services.h"
51
52 typedef struct StaticService StaticService;
53 typedef struct StaticServiceGroup StaticServiceGroup;
54
55 struct StaticService {
56     StaticServiceGroup *group;
57
58     char *type;
59     char *domain_name;
60     char *host_name;
61     uint16_t port;
62     int protocol;
63
64     AvahiStringList *subtypes;
65
66     AvahiStringList *txt_records;
67
68     AVAHI_LLIST_FIELDS(StaticService, services);
69 };
70
71 struct StaticServiceGroup {
72     char *filename;
73     time_t mtime;
74
75     char *name, *chosen_name;
76     int replace_wildcards;
77
78     AvahiSEntryGroup *entry_group;
79     AVAHI_LLIST_HEAD(StaticService, services);
80     AVAHI_LLIST_FIELDS(StaticServiceGroup, groups);
81 };
82
83 static AVAHI_LLIST_HEAD(StaticServiceGroup, groups) = NULL;
84
85 static char *replacestr(const char *pattern, const char *a, const char *b) {
86     char *r = NULL, *e, *n;
87
88     while ((e = strstr(pattern, a))) {
89         char *k;
90
91         k = avahi_strndup(pattern, e - pattern);
92         if (r)
93             n = avahi_strdup_printf("%s%s%s", r, k, b);
94         else
95             n = avahi_strdup_printf("%s%s", k, b);
96
97         avahi_free(k);
98         avahi_free(r);
99         r = n;
100
101         pattern = e + strlen(a);
102     }
103
104     if (!r)
105         return avahi_strdup(pattern);
106
107     n = avahi_strdup_printf("%s%s", r, pattern);
108     avahi_free(r);
109
110     return n;
111 }
112
113 static void add_static_service_group_to_server(StaticServiceGroup *g);
114 static void remove_static_service_group_from_server(StaticServiceGroup *g);
115
116 static StaticService *static_service_new(StaticServiceGroup *group) {
117     StaticService *s;
118
119     assert(group);
120     s = avahi_new(StaticService, 1);
121     s->group = group;
122
123     s->type = s->host_name = s->domain_name = NULL;
124     s->port = 0;
125     s->protocol = AVAHI_PROTO_UNSPEC;
126
127     s->txt_records = NULL;
128     s->subtypes = NULL;
129
130     AVAHI_LLIST_PREPEND(StaticService, services, group->services, s);
131
132     return s;
133 }
134
135 static StaticServiceGroup *static_service_group_new(char *filename) {
136     StaticServiceGroup *g;
137     assert(filename);
138
139     g = avahi_new(StaticServiceGroup, 1);
140     g->filename = avahi_strdup(filename);
141     g->mtime = 0;
142     g->name = g->chosen_name = NULL;
143     g->replace_wildcards = 0;
144     g->entry_group = NULL;
145
146     AVAHI_LLIST_HEAD_INIT(StaticService, g->services);
147     AVAHI_LLIST_PREPEND(StaticServiceGroup, groups, groups, g);
148
149     return g;
150 }
151
152 static void static_service_free(StaticService *s) {
153     assert(s);
154
155     AVAHI_LLIST_REMOVE(StaticService, services, s->group->services, s);
156
157     avahi_free(s->type);
158     avahi_free(s->host_name);
159     avahi_free(s->domain_name);
160
161     avahi_string_list_free(s->txt_records);
162     avahi_string_list_free(s->subtypes);
163
164     avahi_free(s);
165 }
166
167 static void static_service_group_free(StaticServiceGroup *g) {
168     assert(g);
169
170     if (g->entry_group)
171         avahi_s_entry_group_free(g->entry_group);
172
173     while (g->services)
174         static_service_free(g->services);
175
176     AVAHI_LLIST_REMOVE(StaticServiceGroup, groups, groups, g);
177
178     avahi_free(g->filename);
179     avahi_free(g->name);
180     avahi_free(g->chosen_name);
181     avahi_free(g);
182 }
183
184 static void entry_group_callback(AvahiServer *s, AVAHI_GCC_UNUSED AvahiSEntryGroup *eg, AvahiEntryGroupState state, void* userdata) {
185     StaticServiceGroup *g = userdata;
186
187     assert(s);
188     assert(g);
189
190     switch (state) {
191
192         case AVAHI_ENTRY_GROUP_COLLISION: {
193             char *n;
194
195             remove_static_service_group_from_server(g);
196
197             n = avahi_alternative_service_name(g->chosen_name);
198             avahi_free(g->chosen_name);
199             g->chosen_name = n;
200
201             avahi_log_notice("Service name conflict for \"%s\" (%s), retrying with \"%s\".", g->name, g->filename, g->chosen_name);
202
203             add_static_service_group_to_server(g);
204             break;
205         }
206
207         case AVAHI_ENTRY_GROUP_ESTABLISHED:
208             avahi_log_info("Service \"%s\" (%s) successfully established.", g->chosen_name, g->filename);
209             break;
210
211         case AVAHI_ENTRY_GROUP_FAILURE:
212             avahi_log_warn("Failed to publish service \"%s\" (%s): %s", g->chosen_name, g->filename, avahi_strerror(avahi_server_errno(s)));
213             remove_static_service_group_from_server(g);
214             break;
215
216         case AVAHI_ENTRY_GROUP_UNCOMMITED:
217         case AVAHI_ENTRY_GROUP_REGISTERING:
218             ;
219     }
220 }
221
222 static void add_static_service_group_to_server(StaticServiceGroup *g) {
223     StaticService *s;
224
225     assert(g);
226
227     if (g->entry_group && !avahi_s_entry_group_is_empty(g->entry_group))
228         /* This service group is already registered in the server */
229         return;
230
231     if (!g->chosen_name || (g->replace_wildcards && strstr(g->name, "%h"))) {
232
233         avahi_free(g->chosen_name);
234
235         if (g->replace_wildcards) {
236             char label[AVAHI_LABEL_MAX];
237             const char *p;
238
239             p = avahi_server_get_host_name(avahi_server);
240             avahi_unescape_label(&p, label, sizeof(label));
241
242             g->chosen_name = replacestr(g->name, "%h", label);
243         } else
244             g->chosen_name = avahi_strdup(g->name);
245
246     }
247
248     if (!g->entry_group)
249         g->entry_group = avahi_s_entry_group_new(avahi_server, entry_group_callback, g);
250
251     assert(avahi_s_entry_group_is_empty(g->entry_group));
252
253     for (s = g->services; s; s = s->services_next) {
254         AvahiStringList *i;
255
256         if (avahi_server_add_service_strlst(
257                 avahi_server,
258                 g->entry_group,
259                 AVAHI_IF_UNSPEC, s->protocol,
260                 0,
261                 g->chosen_name, s->type, s->domain_name,
262                 s->host_name, s->port,
263                 s->txt_records) < 0) {
264             avahi_log_error("Failed to add service '%s' of type '%s', ignoring service group (%s): %s",
265                             g->chosen_name, s->type, g->filename,
266                             avahi_strerror(avahi_server_errno(avahi_server)));
267             remove_static_service_group_from_server(g);
268             return;
269         }
270
271         for (i = s->subtypes; i; i = i->next) {
272
273             if (avahi_server_add_service_subtype(
274                     avahi_server,
275                     g->entry_group,
276                     AVAHI_IF_UNSPEC, s->protocol,
277                     0,
278                     g->chosen_name, s->type, s->domain_name,
279                     (char*) i->text) < 0) {
280
281                 avahi_log_error("Failed to add subtype '%s' for service '%s' of type '%s', ignoring subtype (%s): %s",
282                                 i->text, g->chosen_name, s->type, g->filename,
283                                 avahi_strerror(avahi_server_errno(avahi_server)));
284             }
285         }
286     }
287
288     avahi_s_entry_group_commit(g->entry_group);
289 }
290
291 static void remove_static_service_group_from_server(StaticServiceGroup *g) {
292     assert(g);
293
294     if (g->entry_group)
295         avahi_s_entry_group_reset(g->entry_group);
296 }
297
298 typedef enum {
299     XML_TAG_INVALID,
300     XML_TAG_SERVICE_GROUP,
301     XML_TAG_NAME,
302     XML_TAG_SERVICE,
303     XML_TAG_TYPE,
304     XML_TAG_SUBTYPE,
305     XML_TAG_DOMAIN_NAME,
306     XML_TAG_HOST_NAME,
307     XML_TAG_PORT,
308     XML_TAG_TXT_RECORD
309 } xml_tag_name;
310
311 struct xml_userdata {
312     StaticServiceGroup *group;
313     StaticService *service;
314     xml_tag_name current_tag;
315     int failed;
316     char *buf;
317 };
318
319 #ifndef XMLCALL
320 #define XMLCALL
321 #endif
322
323 static void XMLCALL xml_start(void *data, const char *el, const char *attr[]) {
324     struct xml_userdata *u = data;
325
326     assert(u);
327
328     if (u->failed)
329         return;
330
331     if (u->current_tag == XML_TAG_INVALID && strcmp(el, "service-group") == 0) {
332
333         if (attr[0])
334             goto invalid_attr;
335
336         u->current_tag = XML_TAG_SERVICE_GROUP;
337     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "name") == 0) {
338         u->current_tag = XML_TAG_NAME;
339
340         if (attr[0]) {
341             if (strcmp(attr[0], "replace-wildcards") == 0)
342                 u->group->replace_wildcards = strcmp(attr[1], "yes") == 0;
343             else
344                 goto invalid_attr;
345
346             if (attr[2])
347                 goto invalid_attr;
348         }
349
350     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "service") == 0) {
351         u->current_tag = XML_TAG_SERVICE;
352
353         assert(!u->service);
354         u->service = static_service_new(u->group);
355
356         if (attr[0]) {
357             if (strcmp(attr[0], "protocol") == 0) {
358                 AvahiProtocol protocol;
359
360                 if (strcmp(attr[1], "ipv4") == 0) {
361                     protocol = AVAHI_PROTO_INET;
362                 } else if (strcmp(attr[1], "ipv6") == 0) {
363                     protocol = AVAHI_PROTO_INET6;
364                 } else if (strcmp(attr[1], "any") == 0) {
365                     protocol = AVAHI_PROTO_UNSPEC;
366                 } else {
367                     avahi_log_error("%s: parse failure: invalid protocol specification \"%s\".", u->group->filename, attr[1]);
368                     u->failed = 1;
369                     return;
370                 }
371
372                 u->service->protocol = protocol;
373             } else
374                 goto invalid_attr;
375
376             if (attr[2])
377                 goto invalid_attr;
378         }
379
380     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "type") == 0) {
381         if (attr[0])
382             goto invalid_attr;
383
384         u->current_tag = XML_TAG_TYPE;
385     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "subtype") == 0) {
386         if (attr[0])
387             goto invalid_attr;
388
389         u->current_tag = XML_TAG_SUBTYPE;
390     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "domain-name") == 0) {
391         if (attr[0])
392             goto invalid_attr;
393
394         u->current_tag = XML_TAG_DOMAIN_NAME;
395     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "host-name") == 0) {
396         if (attr[0])
397             goto invalid_attr;
398
399         u->current_tag = XML_TAG_HOST_NAME;
400     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "port") == 0) {
401         if (attr[0])
402             goto invalid_attr;
403
404         u->current_tag = XML_TAG_PORT;
405     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "txt-record") == 0) {
406         if (attr[0])
407             goto invalid_attr;
408
409         u->current_tag = XML_TAG_TXT_RECORD;
410     } else {
411         avahi_log_error("%s: parse failure: didn't expect element <%s>.", u->group->filename, el);
412         u->failed = 1;
413     }
414
415     return;
416
417 invalid_attr:
418     avahi_log_error("%s: parse failure: invalid attribute for element <%s>.", u->group->filename, el);
419     u->failed = 1;
420     return;
421 }
422
423 static void XMLCALL xml_end(void *data, AVAHI_GCC_UNUSED const char *el) {
424     struct xml_userdata *u = data;
425     assert(u);
426
427     if (u->failed)
428         return;
429
430     switch (u->current_tag) {
431         case XML_TAG_SERVICE_GROUP:
432
433             if (!u->group->name || !u->group->services) {
434                 avahi_log_error("%s: parse failure: service group incomplete.", u->group->filename);
435                 u->failed = 1;
436                 return;
437             }
438
439             u->current_tag = XML_TAG_INVALID;
440             break;
441
442         case XML_TAG_SERVICE:
443
444             if (!u->service->type) {
445                 avahi_log_error("%s: parse failure: service incomplete.", u->group->filename);
446                 u->failed = 1;
447                 return;
448             }
449
450             u->service = NULL;
451             u->current_tag = XML_TAG_SERVICE_GROUP;
452             break;
453
454         case XML_TAG_NAME:
455             u->current_tag = XML_TAG_SERVICE_GROUP;
456             break;
457
458         case XML_TAG_PORT: {
459             int p;
460             assert(u->service);
461
462             p = u->buf ? atoi(u->buf) : 0;
463
464             if (p < 0 || p > 0xFFFF) {
465                 avahi_log_error("%s: parse failure: invalid port specification \"%s\".", u->group->filename, u->buf);
466                 u->failed = 1;
467                 return;
468             }
469
470             u->service->port = (uint16_t) p;
471
472             u->current_tag = XML_TAG_SERVICE;
473             break;
474         }
475
476         case XML_TAG_TXT_RECORD: {
477             assert(u->service);
478
479             u->service->txt_records = avahi_string_list_add(u->service->txt_records, u->buf ? u->buf : "");
480             u->current_tag = XML_TAG_SERVICE;
481             break;
482         }
483
484         case XML_TAG_SUBTYPE: {
485             assert(u->service);
486
487             u->service->subtypes = avahi_string_list_add(u->service->subtypes, u->buf ? u->buf : "");
488             u->current_tag = XML_TAG_SERVICE;
489             break;
490         }
491
492         case XML_TAG_TYPE:
493         case XML_TAG_DOMAIN_NAME:
494         case XML_TAG_HOST_NAME:
495             u->current_tag = XML_TAG_SERVICE;
496             break;
497
498         case XML_TAG_INVALID:
499             ;
500     }
501
502     avahi_free(u->buf);
503     u->buf = NULL;
504 }
505
506 static char *append_cdata(char *t, const char *n, int length) {
507     char *r, *k;
508
509     if (!length)
510         return t;
511
512
513     k = avahi_strndup(n, length);
514
515     if (t) {
516         r = avahi_strdup_printf("%s%s", t, k);
517         avahi_free(k);
518         avahi_free(t);
519     } else
520         r = k;
521
522     return r;
523 }
524
525 static void XMLCALL xml_cdata(void *data, const XML_Char *s, int len) {
526     struct xml_userdata *u = data;
527     assert(u);
528
529     if (u->failed)
530         return;
531
532     switch (u->current_tag) {
533         case XML_TAG_NAME:
534             u->group->name = append_cdata(u->group->name, s, len);
535             break;
536
537         case XML_TAG_TYPE:
538             assert(u->service);
539             u->service->type = append_cdata(u->service->type, s, len);
540             break;
541
542         case XML_TAG_DOMAIN_NAME:
543             assert(u->service);
544             u->service->domain_name = append_cdata(u->service->domain_name, s, len);
545             break;
546
547         case XML_TAG_HOST_NAME:
548             assert(u->service);
549             u->service->host_name = append_cdata(u->service->host_name, s, len);
550             break;
551
552         case XML_TAG_PORT:
553         case XML_TAG_TXT_RECORD:
554         case XML_TAG_SUBTYPE:
555             assert(u->service);
556             u->buf = append_cdata(u->buf, s, len);
557             break;
558
559         case XML_TAG_SERVICE_GROUP:
560         case XML_TAG_SERVICE:
561         case XML_TAG_INVALID:
562             ;
563     }
564 }
565
566 static int static_service_group_load(StaticServiceGroup *g) {
567     XML_Parser parser = NULL;
568     int fd = -1;
569     struct xml_userdata u;
570     int r = -1;
571     struct stat st;
572     ssize_t n;
573
574     assert(g);
575
576     u.buf = NULL;
577     u.group = g;
578     u.service = NULL;
579     u.current_tag = XML_TAG_INVALID;
580     u.failed = 0;
581
582     /* Cleanup old data in this service group, if available */
583     remove_static_service_group_from_server(g);
584     while (g->services)
585         static_service_free(g->services);
586
587     avahi_free(g->name);
588     avahi_free(g->chosen_name);
589     g->name = g->chosen_name = NULL;
590     g->replace_wildcards = 0;
591
592     if (!(parser = XML_ParserCreate(NULL))) {
593         avahi_log_error("XML_ParserCreate() failed.");
594         goto finish;
595     }
596
597     if ((fd = open(g->filename, O_RDONLY)) < 0) {
598         avahi_log_error("open(\"%s\", O_RDONLY): %s", g->filename, strerror(errno));
599         goto finish;
600     }
601
602     if (fstat(fd, &st) < 0) {
603         avahi_log_error("fstat(): %s", strerror(errno));
604         goto finish;
605     }
606
607     g->mtime = st.st_mtime;
608
609     XML_SetUserData(parser, &u);
610
611     XML_SetElementHandler(parser, xml_start, xml_end);
612     XML_SetCharacterDataHandler(parser, xml_cdata);
613
614     do {
615         void *buffer;
616
617 #define BUFSIZE (10*1024)
618
619         if (!(buffer = XML_GetBuffer(parser, BUFSIZE))) {
620             avahi_log_error("XML_GetBuffer() failed.");
621             goto finish;
622         }
623
624         if ((n = read(fd, buffer, BUFSIZE)) < 0) {
625             avahi_log_error("read(): %s\n", strerror(errno));
626             goto finish;
627         }
628
629         if (!XML_ParseBuffer(parser, n, n == 0)) {
630             avahi_log_error("XML_ParseBuffer() failed at line %d: %s.\n", (int) XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser)));
631             goto finish;
632         }
633
634     } while (n != 0);
635
636     if (!u.failed)
637         r = 0;
638
639 finish:
640
641     if (fd >= 0)
642         close(fd);
643
644     if (parser)
645         XML_ParserFree(parser);
646
647     avahi_free(u.buf);
648
649     return r;
650 }
651
652 static void load_file(char *n) {
653     StaticServiceGroup *g;
654     assert(n);
655
656     for (g = groups; g; g = g->groups_next)
657         if (strcmp(g->filename, n) == 0)
658             return;
659
660     avahi_log_info("Loading service file %s.", n);
661
662     g = static_service_group_new(n);
663     if (static_service_group_load(g) < 0) {
664         avahi_log_error("Failed to load service group file %s, ignoring.", g->filename);
665         static_service_group_free(g);
666     }
667 }
668
669 void static_service_load(int in_chroot) {
670     StaticServiceGroup *g, *n;
671     glob_t globbuf;
672     int globret;
673     char **p;
674
675     for (g = groups; g; g = n) {
676         struct stat st;
677
678         n = g->groups_next;
679
680         if (stat(g->filename, &st) < 0) {
681
682             if (errno == ENOENT)
683                 avahi_log_info("Service group file %s vanished, removing services.", g->filename);
684             else
685                 avahi_log_warn("Failed to stat() file %s, ignoring: %s", g->filename, strerror(errno));
686
687             static_service_group_free(g);
688         } else if (st.st_mtime != g->mtime) {
689             avahi_log_info("Service group file %s changed, reloading.", g->filename);
690
691             if (static_service_group_load(g) < 0) {
692                 avahi_log_warn("Failed to load service group file %s, removing service.", g->filename);
693                 static_service_group_free(g);
694             }
695         }
696     }
697
698     memset(&globbuf, 0, sizeof(globbuf));
699
700     if ((globret = glob(in_chroot ? "/services/*.service" : AVAHI_SERVICE_DIR "/*.service", GLOB_ERR, NULL, &globbuf)) != 0)
701
702         switch (globret) {
703 #ifdef GLOB_NOSPACE
704             case GLOB_NOSPACE:
705                 avahi_log_error("Not enough memory to read service directory "AVAHI_SERVICE_DIR".");
706                 break;
707 #endif
708 #ifdef GLOB_NOMATCH
709             case GLOB_NOMATCH:
710                 avahi_log_info("No service file found in "AVAHI_SERVICE_DIR".");
711                 break;
712 #endif
713             default:
714                 avahi_log_error("Failed to read "AVAHI_SERVICE_DIR".");
715                 break;
716         }
717
718     else {
719         for (p = globbuf.gl_pathv; *p; p++)
720             load_file(*p);
721
722         globfree(&globbuf);
723     }
724 }
725
726 void static_service_free_all(void) {
727
728     while (groups)
729         static_service_group_free(groups);
730 }
731
732 void static_service_add_to_server(void) {
733     StaticServiceGroup *g;
734
735     for (g = groups; g; g = g->groups_next)
736         add_static_service_group_to_server(g);
737 }
738
739 void static_service_remove_from_server(void) {
740     StaticServiceGroup *g;
741
742     for (g = groups; g; g = g->groups_next)
743         remove_static_service_group_from_server(g);
744 }