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/avahi-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 
replacestr(const char * pattern,const char * a,const char * b)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 
static_service_new(StaticServiceGroup * group)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 
static_service_group_new(char * filename)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 
static_service_free(StaticService * s)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 
static_service_group_free(StaticServiceGroup * g)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 
entry_group_callback(AvahiServer * s,AVAHI_GCC_UNUSED AvahiSEntryGroup * eg,AvahiEntryGroupState state,void * userdata)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 
add_static_service_group_to_server(StaticServiceGroup * g)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 
remove_static_service_group_from_server(StaticServiceGroup * g)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 
xml_start(void * data,const char * el,const char * attr[])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 
xml_end(void * data,AVAHI_GCC_UNUSED const char * el)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 
append_cdata(char * t,const char * n,int length)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 
xml_cdata(void * data,const XML_Char * s,int len)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 
static_service_group_load(StaticServiceGroup * g)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 
load_file(char * n)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 
static_service_load(int in_chroot)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 
static_service_free_all(void)726 void static_service_free_all(void) {
727 
728     while (groups)
729         static_service_group_free(groups);
730 }
731 
static_service_add_to_server(void)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 
static_service_remove_from_server(void)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 }
745