]> git.meshlink.io Git - catta/blob - avahi-daemon/static-services.c
Allow compilation against bsdxml instead of Expat. Patch from zml. (Closes #159)
[catta] / avahi-daemon / static-services.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 <sys/stat.h>
27 #include <glob.h>
28 #include <limits.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33
34 #ifdef USE_EXPAT_H
35 #include <expat.h>
36 #endif /* USE_EXPAT_H */
37
38 #ifdef USE_BSDXML_H
39 #include <bsdxml.h>
40 #endif /* USE_BSDXML_H */
41
42 #include <avahi-common/llist.h>
43 #include <avahi-common/malloc.h>
44 #include <avahi-common/alternative.h>
45 #include <avahi-common/error.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             g->chosen_name = replacestr(g->name, "%h", avahi_server_get_host_name(avahi_server));
237         else
238             g->chosen_name = avahi_strdup(g->name);
239
240     }
241         
242     if (!g->entry_group)
243         g->entry_group = avahi_s_entry_group_new(avahi_server, entry_group_callback, g);
244
245     assert(avahi_s_entry_group_is_empty(g->entry_group));
246     
247     for (s = g->services; s; s = s->services_next) {
248         AvahiStringList *i;
249
250         if (avahi_server_add_service_strlst(
251                 avahi_server,
252                 g->entry_group,
253                 AVAHI_IF_UNSPEC, s->protocol,
254                 0, 
255                 g->chosen_name, s->type, s->domain_name,
256                 s->host_name, s->port,
257                 s->txt_records) < 0) {
258             avahi_log_error("Failed to add service '%s' of type '%s', ignoring service group (%s): %s",
259                             g->chosen_name, s->type, g->filename,
260                             avahi_strerror(avahi_server_errno(avahi_server)));
261             remove_static_service_group_from_server(g);
262             return;
263         }
264
265         for (i = s->subtypes; i; i = i->next) {
266
267             if (avahi_server_add_service_subtype(
268                     avahi_server,
269                     g->entry_group,
270                     AVAHI_IF_UNSPEC, s->protocol,
271                     0,
272                     g->chosen_name, s->type, s->domain_name,
273                     (char*) i->text) < 0) {
274                 
275                 avahi_log_error("Failed to add subtype '%s' for service '%s' of type '%s', ignoring subtype (%s): %s",
276                                 i->text, g->chosen_name, s->type, g->filename,
277                                 avahi_strerror(avahi_server_errno(avahi_server)));
278             }
279         }
280     }
281
282     avahi_s_entry_group_commit(g->entry_group);
283 }
284
285 static void remove_static_service_group_from_server(StaticServiceGroup *g) {
286     assert(g);
287
288     if (g->entry_group)
289         avahi_s_entry_group_reset(g->entry_group);
290 }
291
292 typedef enum {
293     XML_TAG_INVALID,
294     XML_TAG_SERVICE_GROUP,
295     XML_TAG_NAME,
296     XML_TAG_SERVICE,
297     XML_TAG_TYPE,
298     XML_TAG_SUBTYPE,
299     XML_TAG_DOMAIN_NAME,
300     XML_TAG_HOST_NAME,
301     XML_TAG_PORT,
302     XML_TAG_TXT_RECORD
303 } xml_tag_name;
304
305 struct xml_userdata {
306     StaticServiceGroup *group;
307     StaticService *service;
308     xml_tag_name current_tag;
309     int failed;
310     char *buf;
311 };
312
313 #ifndef XMLCALL
314 #define XMLCALL
315 #endif
316
317 static void XMLCALL xml_start(void *data, const char *el, const char *attr[]) {
318     struct xml_userdata *u = data;
319     
320     assert(u);
321
322     if (u->failed)
323         return;
324
325     if (u->current_tag == XML_TAG_INVALID && strcmp(el, "service-group") == 0) {
326
327         if (attr[0])
328             goto invalid_attr;
329
330         u->current_tag = XML_TAG_SERVICE_GROUP;
331     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "name") == 0) {
332         u->current_tag = XML_TAG_NAME;
333
334         if (attr[0]) {
335             if (strcmp(attr[0], "replace-wildcards") == 0) 
336                 u->group->replace_wildcards = strcmp(attr[1], "yes") == 0;
337             else
338                 goto invalid_attr;
339
340             if (attr[2])
341                 goto invalid_attr;
342         }
343         
344     } else if (u->current_tag == XML_TAG_SERVICE_GROUP && strcmp(el, "service") == 0) {
345         u->current_tag = XML_TAG_SERVICE;
346
347         assert(!u->service);
348         u->service = static_service_new(u->group);
349
350         if (attr[0]) {
351             if (strcmp(attr[0], "protocol") == 0) {
352                 AvahiProtocol protocol;
353                 
354                 if (strcmp(attr[1], "ipv4") == 0) {
355                     protocol = AVAHI_PROTO_INET;
356                 } else if (strcmp(attr[1], "ipv6") == 0) {
357                     protocol = AVAHI_PROTO_INET6;
358                 } else if (strcmp(attr[1], "any") == 0) {
359                     protocol = AVAHI_PROTO_UNSPEC;
360                 } else {
361                     avahi_log_error("%s: parse failure: invalid protocol specification \"%s\".", u->group->filename, attr[1]);
362                     u->failed = 1;
363                     return;
364                 }
365
366                 u->service->protocol = protocol;
367             } else
368                 goto invalid_attr;
369
370             if (attr[2])
371                 goto invalid_attr;
372         }
373
374     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "type") == 0) {
375         if (attr[0])
376             goto invalid_attr;
377
378         u->current_tag = XML_TAG_TYPE;
379     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "subtype") == 0) {
380         if (attr[0])
381             goto invalid_attr;
382
383         u->current_tag = XML_TAG_SUBTYPE;
384     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "domain-name") == 0) {
385         if (attr[0])
386             goto invalid_attr;
387         
388         u->current_tag = XML_TAG_DOMAIN_NAME;
389     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "host-name") == 0) {
390         if (attr[0])
391             goto invalid_attr;
392         
393         u->current_tag = XML_TAG_HOST_NAME;
394     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "port") == 0) {
395         if (attr[0])
396             goto invalid_attr;
397         
398         u->current_tag = XML_TAG_PORT;
399     } else if (u->current_tag == XML_TAG_SERVICE && strcmp(el, "txt-record") == 0) {
400         if (attr[0])
401             goto invalid_attr;
402         
403         u->current_tag = XML_TAG_TXT_RECORD;
404     } else {
405         avahi_log_error("%s: parse failure: didn't expect element <%s>.", u->group->filename, el);
406         u->failed = 1;
407     }
408
409     return;
410
411 invalid_attr:
412     avahi_log_error("%s: parse failure: invalid attribute for element <%s>.", u->group->filename, el);
413     u->failed = 1;
414     return;
415 }
416     
417 static void XMLCALL xml_end(void *data, AVAHI_GCC_UNUSED const char *el) {
418     struct xml_userdata *u = data;
419     assert(u);
420
421     if (u->failed)
422         return;
423     
424     switch (u->current_tag) {
425         case XML_TAG_SERVICE_GROUP:
426
427             if (!u->group->name || !u->group->services) {
428                 avahi_log_error("%s: parse failure: service group incomplete.", u->group->filename);
429                 u->failed = 1;
430                 return;
431             }
432             
433             u->current_tag = XML_TAG_INVALID;
434             break;
435
436         case XML_TAG_SERVICE:
437
438             if (!u->service->type) {
439                 avahi_log_error("%s: parse failure: service incomplete.", u->group->filename);
440                 u->failed = 1;
441                 return;
442             }
443             
444             u->service = NULL;
445             u->current_tag = XML_TAG_SERVICE_GROUP;
446             break;
447
448         case XML_TAG_NAME:
449             u->current_tag = XML_TAG_SERVICE_GROUP;
450             break;
451
452         case XML_TAG_PORT: {
453             int p;
454             assert(u->service);
455             
456             p = u->buf ? atoi(u->buf) : 0;
457
458             if (p < 0 || p > 0xFFFF) {
459                 avahi_log_error("%s: parse failure: invalid port specification \"%s\".", u->group->filename, u->buf);
460                 u->failed = 1;
461                 return;
462             }
463
464             u->service->port = (uint16_t) p;
465
466             u->current_tag = XML_TAG_SERVICE;
467             break;
468         }
469
470         case XML_TAG_TXT_RECORD: {
471             assert(u->service);
472             
473             u->service->txt_records = avahi_string_list_add(u->service->txt_records, u->buf ? u->buf : "");
474             u->current_tag = XML_TAG_SERVICE;
475             break;
476         }
477
478         case XML_TAG_SUBTYPE: {
479             assert(u->service);
480             
481             u->service->subtypes = avahi_string_list_add(u->service->subtypes, u->buf ? u->buf : "");
482             u->current_tag = XML_TAG_SERVICE;
483             break;
484         }
485             
486         case XML_TAG_TYPE:
487         case XML_TAG_DOMAIN_NAME:
488         case XML_TAG_HOST_NAME:
489             u->current_tag = XML_TAG_SERVICE;
490             break;
491
492         case XML_TAG_INVALID:
493             ;
494     }
495
496     avahi_free(u->buf);
497     u->buf = NULL;
498 }
499
500 static char *append_cdata(char *t, const char *n, int length) {
501     char *r, *k;
502     
503     if (!length)
504         return t;
505
506     
507     k = avahi_strndup(n, length);
508
509     if (t) {
510         r = avahi_strdup_printf("%s%s", t, k);
511         avahi_free(k);
512         avahi_free(t);
513     } else
514         r = k;
515     
516     return r;
517 }
518
519 static void XMLCALL xml_cdata(void *data, const XML_Char *s, int len) {
520     struct xml_userdata *u = data;
521     assert(u);
522
523     if (u->failed)
524         return;
525
526     switch (u->current_tag) {
527         case XML_TAG_NAME:
528             u->group->name = append_cdata(u->group->name, s, len);
529             break;
530             
531         case XML_TAG_TYPE:
532             assert(u->service);
533             u->service->type = append_cdata(u->service->type, s, len);
534             break;
535
536         case XML_TAG_DOMAIN_NAME:
537             assert(u->service);
538             u->service->domain_name = append_cdata(u->service->domain_name, s, len);
539             break;
540
541         case XML_TAG_HOST_NAME:
542             assert(u->service);
543             u->service->host_name = append_cdata(u->service->host_name, s, len);
544             break;
545
546         case XML_TAG_PORT:
547         case XML_TAG_TXT_RECORD:
548         case XML_TAG_SUBTYPE:
549             assert(u->service);
550             u->buf = append_cdata(u->buf, s, len);
551             break;
552
553         case XML_TAG_SERVICE_GROUP:
554         case XML_TAG_SERVICE:
555         case XML_TAG_INVALID:
556             ;
557     }
558 }
559
560 static int static_service_group_load(StaticServiceGroup *g) {
561     XML_Parser parser = NULL;
562     int fd = -1;
563     struct xml_userdata u;
564     int r = -1;
565     struct stat st;
566     ssize_t n;
567
568     assert(g);
569
570     u.buf = NULL;
571     u.group = g;
572     u.service = NULL;
573     u.current_tag = XML_TAG_INVALID;
574     u.failed = 0;
575
576     /* Cleanup old data in this service group, if available */
577     remove_static_service_group_from_server(g);
578     while (g->services)
579         static_service_free(g->services);
580
581     avahi_free(g->name);
582     avahi_free(g->chosen_name);
583     g->name = g->chosen_name = NULL;
584     g->replace_wildcards = 0;
585     
586     if (!(parser = XML_ParserCreate(NULL))) {
587         avahi_log_error("XML_ParserCreate() failed.");
588         goto finish;
589     }
590
591     if ((fd = open(g->filename, O_RDONLY)) < 0) {
592         avahi_log_error("open(\"%s\", O_RDONLY): %s", g->filename, strerror(errno));
593         goto finish;
594     }
595
596     if (fstat(fd, &st) < 0) {
597         avahi_log_error("fstat(): %s", strerror(errno));
598         goto finish;
599     }
600
601     g->mtime = st.st_mtime;
602     
603     XML_SetUserData(parser, &u);
604
605     XML_SetElementHandler(parser, xml_start, xml_end);
606     XML_SetCharacterDataHandler(parser, xml_cdata);
607
608     do {
609         void *buffer;
610
611 #define BUFSIZE (10*1024)
612
613         if (!(buffer = XML_GetBuffer(parser, BUFSIZE))) {
614             avahi_log_error("XML_GetBuffer() failed.");
615             goto finish;
616         }
617
618         if ((n = read(fd, buffer, BUFSIZE)) < 0) {
619             avahi_log_error("read(): %s\n", strerror(errno));
620             goto finish;
621         }
622
623         if (!XML_ParseBuffer(parser, n, n == 0)) {
624             avahi_log_error("XML_ParseBuffer() failed at line %d: %s.\n", (int) XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser)));
625             goto finish;
626         }
627
628     } while (n != 0);
629
630     if (!u.failed)
631         r = 0;
632
633 finish:
634
635     if (fd >= 0)
636         close(fd);
637
638     if (parser)
639         XML_ParserFree(parser);
640
641     avahi_free(u.buf);
642
643     return r;
644 }
645
646 static void load_file(char *n) {
647     StaticServiceGroup *g;
648     assert(n);
649
650     for (g = groups; g; g = g->groups_next)
651         if (strcmp(g->filename, n) == 0)
652             return;
653
654     avahi_log_info("Loading service file %s.", n);
655     
656     g = static_service_group_new(n);
657     if (static_service_group_load(g) < 0) {
658         avahi_log_error("Failed to load service group file %s, ignoring.", g->filename);
659         static_service_group_free(g);
660     }
661 }
662
663 void static_service_load(int in_chroot) {
664     StaticServiceGroup *g, *n;
665     glob_t globbuf;
666     int globret;
667     char **p;
668
669     for (g = groups; g; g = n) {
670         struct stat st;
671
672         n = g->groups_next;
673
674         if (stat(g->filename, &st) < 0) {
675
676             if (errno == ENOENT)
677                 avahi_log_info("Service group file %s vanished, removing services.", g->filename);
678             else
679                 avahi_log_warn("Failed to stat() file %s, ignoring: %s", g->filename, strerror(errno));
680             
681             static_service_group_free(g);
682         } else if (st.st_mtime != g->mtime) {
683             avahi_log_info("Service group file %s changed, reloading.", g->filename);
684             
685             if (static_service_group_load(g) < 0) {
686                 avahi_log_warn("Failed to load service group file %s, removing service.", g->filename);
687                 static_service_group_free(g);
688             }
689         }
690     }
691
692     memset(&globbuf, 0, sizeof(globbuf));
693     
694     if ((globret = glob(in_chroot ? "/services/*.service" : AVAHI_SERVICE_DIR "/*.service", GLOB_ERR, NULL, &globbuf)) != 0)
695         
696         switch (globret) {
697 #ifdef GLOB_NOSPACE
698             case GLOB_NOSPACE:
699                 avahi_log_error("Not enough memory to read service directory "AVAHI_SERVICE_DIR".");
700                 break;
701 #endif
702 #ifdef GLOB_NOMATCH
703             case GLOB_NOMATCH:
704                 avahi_log_info("No service file found in "AVAHI_SERVICE_DIR".");
705                 break;
706 #endif
707             default:
708                 avahi_log_error("Failed to read "AVAHI_SERVICE_DIR".");
709                 break;
710         }
711     
712     else {
713         for (p = globbuf.gl_pathv; *p; p++)
714             load_file(*p);
715         
716         globfree(&globbuf);
717     }
718 }
719
720 void static_service_free_all(void) {
721     
722     while (groups)
723         static_service_group_free(groups);
724 }
725
726 void static_service_add_to_server(void) {
727     StaticServiceGroup *g;
728
729     for (g = groups; g; g = g->groups_next)
730         add_static_service_group_to_server(g);
731 }
732
733 void static_service_remove_from_server(void) {
734     StaticServiceGroup *g;
735
736     for (g = groups; g; g = g->groups_next)
737         remove_static_service_group_from_server(g);
738 }