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