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