Home | History | Annotate | Download | only in avahi-daemon
      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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    726 void static_service_free_all(void) {
    727 
    728     while (groups)
    729         static_service_group_free(groups);
    730 }
    731 
    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 
    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