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
53typedef struct StaticService StaticService;
54typedef struct StaticServiceGroup StaticServiceGroup;
55
56struct 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
72struct 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
84static AVAHI_LLIST_HEAD(StaticServiceGroup, groups) = NULL;
85
86static 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
114static void add_static_service_group_to_server(StaticServiceGroup *g);
115static void remove_static_service_group_from_server(StaticServiceGroup *g);
116
117static 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
136static 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
153static 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
168static 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
185static 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
223static 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
286static 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
293typedef 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
306struct 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
318static 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
412invalid_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
418static 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
501static 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
520static 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
561static 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
634finish:
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
647static 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
664void 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
721void static_service_free_all(void) {
722
723    while (groups)
724        static_service_group_free(groups);
725}
726
727void 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
734void 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}
740