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 <assert.h> 25 #include <string.h> 26 #include <sys/types.h> 27 #include <sys/socket.h> 28 #include <stdio.h> 29 #include <unistd.h> 30 #include <sys/un.h> 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <sys/stat.h> 34 35 #include <avahi-common/llist.h> 36 #include "avahi-common/avahi-malloc.h" 37 #include <avahi-common/error.h> 38 39 #include <avahi-core/log.h> 40 #include <avahi-core/lookup.h> 41 #include <avahi-core/dns-srv-rr.h> 42 43 #include "simple-protocol.h" 44 #include "main.h" 45 #include "sd-daemon.h" 46 47 #ifdef ENABLE_CHROOT 48 #include "chroot.h" 49 #endif 50 51 #ifndef AF_LOCAL 52 #define AF_LOCAL AF_UNIX 53 #endif 54 #ifndef PF_LOCAL 55 #define PF_LOCAL PF_UNIX 56 #endif 57 58 #define BUFFER_SIZE (20*1024) 59 60 #define CLIENTS_MAX 50 61 62 typedef struct Client Client; 63 typedef struct Server Server; 64 65 typedef enum { 66 CLIENT_IDLE, 67 CLIENT_RESOLVE_HOSTNAME, 68 CLIENT_RESOLVE_ADDRESS, 69 CLIENT_BROWSE_DNS_SERVERS, 70 CLIENT_DEAD 71 } ClientState; 72 73 struct Client { 74 Server *server; 75 76 ClientState state; 77 78 int fd; 79 AvahiWatch *watch; 80 81 char inbuf[BUFFER_SIZE], outbuf[BUFFER_SIZE]; 82 size_t inbuf_length, outbuf_length; 83 84 AvahiSHostNameResolver *host_name_resolver; 85 AvahiSAddressResolver *address_resolver; 86 AvahiSDNSServerBrowser *dns_server_browser; 87 88 AvahiProtocol afquery; 89 90 AVAHI_LLIST_FIELDS(Client, clients); 91 }; 92 93 struct Server { 94 const AvahiPoll *poll_api; 95 int fd; 96 AvahiWatch *watch; 97 AVAHI_LLIST_HEAD(Client, clients); 98 99 unsigned n_clients; 100 int remove_socket; 101 }; 102 103 static Server *server = NULL; 104 105 static void client_work(AvahiWatch *watch, int fd, AvahiWatchEvent events, void *userdata); 106 107 static void client_free(Client *c) { 108 assert(c); 109 110 assert(c->server->n_clients >= 1); 111 c->server->n_clients--; 112 113 if (c->host_name_resolver) 114 avahi_s_host_name_resolver_free(c->host_name_resolver); 115 116 if (c->address_resolver) 117 avahi_s_address_resolver_free(c->address_resolver); 118 119 if (c->dns_server_browser) 120 avahi_s_dns_server_browser_free(c->dns_server_browser); 121 122 c->server->poll_api->watch_free(c->watch); 123 close(c->fd); 124 125 AVAHI_LLIST_REMOVE(Client, clients, c->server->clients, c); 126 avahi_free(c); 127 } 128 129 static void client_new(Server *s, int fd) { 130 Client *c; 131 132 assert(fd >= 0); 133 134 c = avahi_new(Client, 1); 135 c->server = s; 136 c->fd = fd; 137 c->state = CLIENT_IDLE; 138 139 c->inbuf_length = c->outbuf_length = 0; 140 141 c->host_name_resolver = NULL; 142 c->address_resolver = NULL; 143 c->dns_server_browser = NULL; 144 145 c->watch = s->poll_api->watch_new(s->poll_api, fd, AVAHI_WATCH_IN, client_work, c); 146 147 AVAHI_LLIST_PREPEND(Client, clients, s->clients, c); 148 s->n_clients++; 149 } 150 151 static void client_output(Client *c, const uint8_t*data, size_t size) { 152 size_t k, m; 153 154 assert(c); 155 assert(data); 156 157 if (!size) 158 return; 159 160 k = sizeof(c->outbuf) - c->outbuf_length; 161 m = size > k ? k : size; 162 163 memcpy(c->outbuf + c->outbuf_length, data, m); 164 c->outbuf_length += m; 165 166 server->poll_api->watch_update(c->watch, AVAHI_WATCH_OUT); 167 } 168 169 static void client_output_printf(Client *c, const char *format, ...) { 170 char *t; 171 va_list ap; 172 173 va_start(ap, format); 174 t = avahi_strdup_vprintf(format, ap); 175 va_end(ap); 176 177 client_output(c, (uint8_t*) t, strlen(t)); 178 avahi_free(t); 179 } 180 181 static void host_name_resolver_callback( 182 AVAHI_GCC_UNUSED AvahiSHostNameResolver *r, 183 AvahiIfIndex iface, 184 AvahiProtocol protocol, 185 AvahiResolverEvent event, 186 const char *hostname, 187 const AvahiAddress *a, 188 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, 189 void* userdata) { 190 191 Client *c = userdata; 192 193 assert(c); 194 195 if (event == AVAHI_RESOLVER_FAILURE) 196 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server))); 197 else if (event == AVAHI_RESOLVER_FOUND) { 198 char t[AVAHI_ADDRESS_STR_MAX]; 199 avahi_address_snprint(t, sizeof(t), a); 200 client_output_printf(c, "+ %i %u %s %s\n", iface, protocol, hostname, t); 201 } 202 203 c->state = CLIENT_DEAD; 204 } 205 206 static void address_resolver_callback( 207 AVAHI_GCC_UNUSED AvahiSAddressResolver *r, 208 AvahiIfIndex iface, 209 AvahiProtocol protocol, 210 AvahiResolverEvent event, 211 AVAHI_GCC_UNUSED const AvahiAddress *a, 212 const char *hostname, 213 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, 214 void* userdata) { 215 216 Client *c = userdata; 217 218 assert(c); 219 220 if (event == AVAHI_RESOLVER_FAILURE) 221 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server))); 222 else if (event == AVAHI_RESOLVER_FOUND) 223 client_output_printf(c, "+ %i %u %s\n", iface, protocol, hostname); 224 225 c->state = CLIENT_DEAD; 226 } 227 228 static void dns_server_browser_callback( 229 AVAHI_GCC_UNUSED AvahiSDNSServerBrowser *b, 230 AvahiIfIndex interface, 231 AvahiProtocol protocol, 232 AvahiBrowserEvent event, 233 AVAHI_GCC_UNUSED const char *host_name, 234 const AvahiAddress *a, 235 uint16_t port, 236 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, 237 void* userdata) { 238 239 Client *c = userdata; 240 char t[AVAHI_ADDRESS_STR_MAX]; 241 242 assert(c); 243 244 if (!a) 245 return; 246 247 switch (event) { 248 case AVAHI_BROWSER_FAILURE: 249 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server))); 250 c->state = CLIENT_DEAD; 251 break; 252 253 case AVAHI_BROWSER_ALL_FOR_NOW: 254 case AVAHI_BROWSER_CACHE_EXHAUSTED: 255 break; 256 257 case AVAHI_BROWSER_NEW: 258 case AVAHI_BROWSER_REMOVE: 259 260 avahi_address_snprint(t, sizeof(t), a); 261 client_output_printf(c, "%c %i %u %s %u\n", event == AVAHI_BROWSER_NEW ? '>' : '<', interface, protocol, t, port); 262 break; 263 } 264 } 265 266 static void handle_line(Client *c, const char *s) { 267 char cmd[64], arg[64]; 268 int n_args; 269 270 assert(c); 271 assert(s); 272 273 if (c->state != CLIENT_IDLE) 274 return; 275 276 if ((n_args = sscanf(s, "%63s %63s", cmd, arg)) < 1 ) { 277 client_output_printf(c, "%+i Failed to parse command, try \"HELP\".\n", AVAHI_ERR_INVALID_OPERATION); 278 c->state = CLIENT_DEAD; 279 return; 280 } 281 282 if (strcmp(cmd, "HELP") == 0) { 283 client_output_printf(c, 284 "+ Available commands are:\n" 285 "+ RESOLVE-HOSTNAME <hostname>\n" 286 "+ RESOLVE-HOSTNAME-IPV6 <hostname>\n" 287 "+ RESOLVE-HOSTNAME-IPV4 <hostname>\n" 288 "+ RESOLVE-ADDRESS <address>\n" 289 "+ BROWSE-DNS-SERVERS\n" 290 "+ BROWSE-DNS-SERVERS-IPV4\n" 291 "+ BROWSE-DNS-SERVERS-IPV6\n"); 292 c->state = CLIENT_DEAD; } 293 else if (strcmp(cmd, "FUCK") == 0 && n_args == 1) { 294 client_output_printf(c, "+ FUCK: Go fuck yourself!\n"); 295 c->state = CLIENT_DEAD; 296 } else if (strcmp(cmd, "RESOLVE-HOSTNAME-IPV4") == 0 && n_args == 2) { 297 c->state = CLIENT_RESOLVE_HOSTNAME; 298 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_INET, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c))) 299 goto fail; 300 301 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg); 302 } else if (strcmp(cmd, "RESOLVE-HOSTNAME-IPV6") == 0 && n_args == 2) { 303 c->state = CLIENT_RESOLVE_HOSTNAME; 304 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_INET6, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c))) 305 goto fail; 306 307 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg); 308 } else if (strcmp(cmd, "RESOLVE-HOSTNAME") == 0 && n_args == 2) { 309 c->state = CLIENT_RESOLVE_HOSTNAME; 310 if (!(c->host_name_resolver = avahi_s_host_name_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, arg, c->afquery = AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST, host_name_resolver_callback, c))) 311 goto fail; 312 313 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg); 314 } else if (strcmp(cmd, "RESOLVE-ADDRESS") == 0 && n_args == 2) { 315 AvahiAddress addr; 316 317 if (!(avahi_address_parse(arg, AVAHI_PROTO_UNSPEC, &addr))) { 318 client_output_printf(c, "%+i Failed to parse address \"%s\".\n", AVAHI_ERR_INVALID_ADDRESS, arg); 319 c->state = CLIENT_DEAD; 320 } else { 321 c->state = CLIENT_RESOLVE_ADDRESS; 322 if (!(c->address_resolver = avahi_s_address_resolver_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, &addr, AVAHI_LOOKUP_USE_MULTICAST, address_resolver_callback, c))) 323 goto fail; 324 } 325 326 avahi_log_debug(__FILE__": Got %s request for '%s'.", cmd, arg); 327 328 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS-IPV4") == 0 && n_args == 1) { 329 c->state = CLIENT_BROWSE_DNS_SERVERS; 330 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_INET, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c))) 331 goto fail; 332 client_output_printf(c, "+ Browsing ...\n"); 333 334 avahi_log_debug(__FILE__": Got %s request.", cmd); 335 336 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS-IPV6") == 0 && n_args == 1) { 337 c->state = CLIENT_BROWSE_DNS_SERVERS; 338 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_INET6, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c))) 339 goto fail; 340 client_output_printf(c, "+ Browsing ...\n"); 341 342 avahi_log_debug(__FILE__": Got %s request.", cmd); 343 344 } else if (strcmp(cmd, "BROWSE-DNS-SERVERS") == 0 && n_args == 1) { 345 c->state = CLIENT_BROWSE_DNS_SERVERS; 346 if (!(c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery = AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c))) 347 goto fail; 348 client_output_printf(c, "+ Browsing ...\n"); 349 350 avahi_log_debug(__FILE__": Got %s request.", cmd); 351 352 } else { 353 client_output_printf(c, "%+i Invalid command \"%s\", try \"HELP\".\n", AVAHI_ERR_INVALID_OPERATION, cmd); 354 c->state = CLIENT_DEAD; 355 356 avahi_log_debug(__FILE__": Got invalid request '%s'.", cmd); 357 } 358 359 return; 360 361 fail: 362 client_output_printf(c, "%+i %s\n", avahi_server_errno(avahi_server), avahi_strerror(avahi_server_errno(avahi_server))); 363 c->state = CLIENT_DEAD; 364 } 365 366 static void handle_input(Client *c) { 367 assert(c); 368 369 for (;;) { 370 char *e; 371 size_t k; 372 373 if (!(e = memchr(c->inbuf, '\n', c->inbuf_length))) 374 break; 375 376 k = e - (char*) c->inbuf; 377 *e = 0; 378 379 handle_line(c, c->inbuf); 380 c->inbuf_length -= k + 1; 381 memmove(c->inbuf, e+1, c->inbuf_length); 382 } 383 } 384 385 static void client_work(AvahiWatch *watch, AVAHI_GCC_UNUSED int fd, AvahiWatchEvent events, void *userdata) { 386 Client *c = userdata; 387 388 assert(c); 389 390 if ((events & AVAHI_WATCH_IN) && c->inbuf_length < sizeof(c->inbuf)) { 391 ssize_t r; 392 393 if ((r = read(c->fd, c->inbuf + c->inbuf_length, sizeof(c->inbuf) - c->inbuf_length)) <= 0) { 394 if (r < 0) 395 avahi_log_warn("read(): %s", strerror(errno)); 396 client_free(c); 397 return; 398 } 399 400 c->inbuf_length += r; 401 assert(c->inbuf_length <= sizeof(c->inbuf)); 402 403 handle_input(c); 404 } 405 406 if ((events & AVAHI_WATCH_OUT) && c->outbuf_length > 0) { 407 ssize_t r; 408 409 if ((r = write(c->fd, c->outbuf, c->outbuf_length)) < 0) { 410 avahi_log_warn("write(): %s", strerror(errno)); 411 client_free(c); 412 return; 413 } 414 415 assert((size_t) r <= c->outbuf_length); 416 c->outbuf_length -= r; 417 418 if (c->outbuf_length) 419 memmove(c->outbuf, c->outbuf + r, c->outbuf_length - r); 420 421 if (c->outbuf_length == 0 && c->state == CLIENT_DEAD) { 422 client_free(c); 423 return; 424 } 425 } 426 427 c->server->poll_api->watch_update( 428 watch, 429 (c->outbuf_length > 0 ? AVAHI_WATCH_OUT : 0) | 430 (c->inbuf_length < sizeof(c->inbuf) ? AVAHI_WATCH_IN : 0)); 431 } 432 433 static void server_work(AVAHI_GCC_UNUSED AvahiWatch *watch, int fd, AvahiWatchEvent events, void *userdata) { 434 Server *s = userdata; 435 436 assert(s); 437 438 if (events & AVAHI_WATCH_IN) { 439 int cfd; 440 441 if ((cfd = accept(fd, NULL, NULL)) < 0) 442 avahi_log_error("accept(): %s", strerror(errno)); 443 else 444 client_new(s, cfd); 445 } 446 } 447 448 int simple_protocol_setup(const AvahiPoll *poll_api) { 449 struct sockaddr_un sa; 450 mode_t u; 451 int n; 452 453 assert(!server); 454 455 server = avahi_new(Server, 1); 456 server->poll_api = poll_api; 457 server->remove_socket = 0; 458 server->fd = -1; 459 server->n_clients = 0; 460 AVAHI_LLIST_HEAD_INIT(Client, server->clients); 461 server->watch = NULL; 462 463 u = umask(0000); 464 465 if ((n = sd_listen_fds(1)) < 0) { 466 avahi_log_warn("Failed to acquire systemd file descriptors: %s", strerror(-n)); 467 goto fail; 468 } 469 470 if (n > 1) { 471 avahi_log_warn("Too many systemd file descriptors passed."); 472 goto fail; 473 } 474 475 if (n == 1) { 476 int r; 477 478 if ((r = sd_is_socket(SD_LISTEN_FDS_START, AF_LOCAL, SOCK_STREAM, 1)) < 0) { 479 avahi_log_warn("Passed systemd file descriptor is of wrong type: %s", strerror(-r)); 480 goto fail; 481 } 482 483 server->fd = SD_LISTEN_FDS_START; 484 485 } else { 486 487 if ((server->fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { 488 avahi_log_warn("socket(AF_LOCAL, SOCK_STREAM, 0): %s", strerror(errno)); 489 goto fail; 490 } 491 492 memset(&sa, 0, sizeof(sa)); 493 sa.sun_family = AF_LOCAL; 494 strncpy(sa.sun_path, AVAHI_SOCKET, sizeof(sa.sun_path)-1); 495 496 /* We simply remove existing UNIX sockets under this name. The 497 Avahi daemon makes sure that it runs only once on a host, 498 therefore sockets that already exist are stale and may be 499 removed without any ill effects */ 500 501 unlink(AVAHI_SOCKET); 502 503 if (bind(server->fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) { 504 avahi_log_warn("bind(): %s", strerror(errno)); 505 goto fail; 506 } 507 508 server->remove_socket = 1; 509 510 if (listen(server->fd, SOMAXCONN) < 0) { 511 avahi_log_warn("listen(): %s", strerror(errno)); 512 goto fail; 513 } 514 } 515 516 umask(u); 517 518 server->watch = poll_api->watch_new(poll_api, server->fd, AVAHI_WATCH_IN, server_work, server); 519 520 return 0; 521 522 fail: 523 524 umask(u); 525 simple_protocol_shutdown(); 526 527 return -1; 528 } 529 530 void simple_protocol_shutdown(void) { 531 532 if (server) { 533 534 if (server->remove_socket) 535 #ifdef ENABLE_CHROOT 536 avahi_chroot_helper_unlink(AVAHI_SOCKET); 537 #else 538 unlink(AVAHI_SOCKET); 539 #endif 540 541 while (server->clients) 542 client_free(server->clients); 543 544 if (server->watch) 545 server->poll_api->watch_free(server->watch); 546 547 if (server->fd >= 0) 548 close(server->fd); 549 550 avahi_free(server); 551 552 server = NULL; 553 } 554 } 555 556 void simple_protocol_restart_queries(void) { 557 Client *c; 558 559 /* Restart queries in case of local domain name changes */ 560 561 assert(server); 562 563 for (c = server->clients; c; c = c->clients_next) 564 if (c->state == CLIENT_BROWSE_DNS_SERVERS && c->dns_server_browser) { 565 avahi_s_dns_server_browser_free(c->dns_server_browser); 566 c->dns_server_browser = avahi_s_dns_server_browser_new(avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DNS_SERVER_RESOLVE, c->afquery, AVAHI_LOOKUP_USE_MULTICAST, dns_server_browser_callback, c); 567 } 568 } 569