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