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