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