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