/*** This file is part of avahi. avahi is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. avahi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with avahi; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <stdio.h> #include <getopt.h> #include <assert.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <net/if.h> #include <locale.h> #include <ctype.h> #include <avahi-common/simple-watch.h> #include <avahi-common/error.h> #include "avahi-common/avahi-malloc.h" #include <avahi-common/domain.h> #include <avahi-common/llist.h> #include <avahi-common/i18n.h> #include <avahi-client/client.h> #include <avahi-client/lookup.h> #include "sigint.h" #if defined(HAVE_GDBM) || defined(HAVE_DBM) #include "stdb.h" #endif typedef enum { COMMAND_HELP, COMMAND_VERSION, COMMAND_BROWSE_SERVICES, COMMAND_BROWSE_ALL_SERVICES, COMMAND_BROWSE_DOMAINS #if defined(HAVE_GDBM) || defined(HAVE_DBM) , COMMAND_DUMP_STDB #endif } Command; typedef struct Config { int verbose; int terminate_on_all_for_now; int terminate_on_cache_exhausted; char *domain; char *stype; int ignore_local; Command command; int resolve; int no_fail; int parsable; #if defined(HAVE_GDBM) || defined(HAVE_DBM) int no_db_lookup; #endif } Config; typedef struct ServiceInfo ServiceInfo; struct ServiceInfo { AvahiIfIndex interface; AvahiProtocol protocol; char *name, *type, *domain; AvahiServiceResolver *resolver; Config *config; AVAHI_LLIST_FIELDS(ServiceInfo, info); }; static AvahiSimplePoll *simple_poll = NULL; static AvahiClient *client = NULL; static int n_all_for_now = 0, n_cache_exhausted = 0, n_resolving = 0; static AvahiStringList *browsed_types = NULL; static ServiceInfo *services = NULL; static int n_columns = 80; static int browsing = 0; static void check_terminate(Config *c) { assert(n_all_for_now >= 0); assert(n_cache_exhausted >= 0); assert(n_resolving >= 0); if (n_all_for_now <= 0 && n_resolving <= 0) { if (c->verbose && !c->parsable) { printf(_(": All for now\n")); n_all_for_now++; /* Make sure that this event is not repeated */ } if (c->terminate_on_all_for_now) avahi_simple_poll_quit(simple_poll); } if (n_cache_exhausted <= 0 && n_resolving <= 0) { if (c->verbose && !c->parsable) { printf(_(": Cache exhausted\n")); n_cache_exhausted++; /* Make sure that this event is not repeated */ } if (c->terminate_on_cache_exhausted) avahi_simple_poll_quit(simple_poll); } } static ServiceInfo *find_service(AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) { ServiceInfo *i; for (i = services; i; i = i->info_next) if (i->interface == interface && i->protocol == protocol && strcasecmp(i->name, name) == 0 && avahi_domain_equal(i->type, type) && avahi_domain_equal(i->domain, domain)) return i; return NULL; } static char *make_printable(const char *from, char *to) { const char *f; char *t; for (f = from, t = to; *f; f++, t++) *t = isprint(*f) ? *f : '_'; *t = 0; return to; } static void print_service_line(Config *config, char c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain, int nl) { char ifname[IF_NAMESIZE]; #if defined(HAVE_GDBM) || defined(HAVE_DBM) if (!config->no_db_lookup) type = stdb_lookup(type); #endif if (config->parsable) { char sn[AVAHI_DOMAIN_NAME_MAX], *e = sn; size_t l = sizeof(sn); printf("%c;%s;%s;%s;%s;%s%s", c, interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : _("n/a"), protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : _("n/a"), avahi_escape_label(name, strlen(name), &e, &l), type, domain, nl ? "\n" : ""); } else { char label[AVAHI_LABEL_MAX]; make_printable(name, label); printf("%c %6s %4s %-*s %-20s %s\n", c, interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : _("n/a"), protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : _("n/a"), n_columns-35, label, type, domain); } fflush(stdout); } static void service_resolver_callback( AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { ServiceInfo *i = userdata; assert(r); assert(i); switch (event) { case AVAHI_RESOLVER_FOUND: { char address[AVAHI_ADDRESS_STR_MAX], *t; avahi_address_snprint(address, sizeof(address), a); t = avahi_string_list_to_string(txt); print_service_line(i->config, '=', interface, protocol, name, type, domain, 0); if (i->config->parsable) printf(";%s;%s;%u;%s\n", host_name, address, port, t); else printf(" hostname = [%s]\n" " address = [%s]\n" " port = [%u]\n" " txt = [%s]\n", host_name, address, port, t); avahi_free(t); break; } case AVAHI_RESOLVER_FAILURE: fprintf(stderr, _("Failed to resolve service '%s' of type '%s' in domain '%s': %s\n"), name, type, domain, avahi_strerror(avahi_client_errno(client))); break; } avahi_service_resolver_free(i->resolver); i->resolver = NULL; assert(n_resolving > 0); n_resolving--; check_terminate(i->config); fflush(stdout); } static ServiceInfo *add_service(Config *c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) { ServiceInfo *i; i = avahi_new(ServiceInfo, 1); if (c->resolve) { if (!(i->resolver = avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, i))) { avahi_free(i); fprintf(stderr, _("Failed to resolve service '%s' of type '%s' in domain '%s': %s\n"), name, type, domain, avahi_strerror(avahi_client_errno(client))); return NULL; } n_resolving++; } else i->resolver = NULL; i->interface = interface; i->protocol = protocol; i->name = avahi_strdup(name); i->type = avahi_strdup(type); i->domain = avahi_strdup(domain); i->config = c; AVAHI_LLIST_PREPEND(ServiceInfo, info, services, i); return i; } static void remove_service(Config *c, ServiceInfo *i) { assert(c); assert(i); AVAHI_LLIST_REMOVE(ServiceInfo, info, services, i); if (i->resolver) avahi_service_resolver_free(i->resolver); avahi_free(i->name); avahi_free(i->type); avahi_free(i->domain); avahi_free(i); } static void service_browser_callback( AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void *userdata) { Config *c = userdata; assert(b); assert(c); switch (event) { case AVAHI_BROWSER_NEW: { if (c->ignore_local && (flags & AVAHI_LOOKUP_RESULT_LOCAL)) break; if (find_service(interface, protocol, name, type, domain)) return; add_service(c, interface, protocol, name, type, domain); print_service_line(c, '+', interface, protocol, name, type, domain, 1); break; } case AVAHI_BROWSER_REMOVE: { ServiceInfo *info; if (!(info = find_service(interface, protocol, name, type, domain))) return; remove_service(c, info); print_service_line(c, '-', interface, protocol, name, type, domain, 1); break; } case AVAHI_BROWSER_FAILURE: fprintf(stderr, _("service_browser failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: n_cache_exhausted --; check_terminate(c); break; case AVAHI_BROWSER_ALL_FOR_NOW: n_all_for_now --; check_terminate(c); break; } } static void browse_service_type(Config *c, const char *stype, const char *domain) { AvahiServiceBrowser *b; AvahiStringList *i; assert(c); assert(client); assert(stype); for (i = browsed_types; i; i = i->next) if (avahi_domain_equal(stype, (char*) i->text)) return; if (!(b = avahi_service_browser_new( client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, stype, domain, 0, service_browser_callback, c))) { fprintf(stderr, _("avahi_service_browser_new() failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); } browsed_types = avahi_string_list_add(browsed_types, stype); n_all_for_now++; n_cache_exhausted++; } static void service_type_browser_callback( AvahiServiceTypeBrowser *b, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiBrowserEvent event, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { Config *c = userdata; assert(b); assert(c); switch (event) { case AVAHI_BROWSER_NEW: browse_service_type(c, type, domain); break; case AVAHI_BROWSER_REMOVE: /* We're dirty and never remove the browser again */ break; case AVAHI_BROWSER_FAILURE: fprintf(stderr, _("service_type_browser failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: n_cache_exhausted --; check_terminate(c); break; case AVAHI_BROWSER_ALL_FOR_NOW: n_all_for_now --; check_terminate(c); break; } } static void browse_all(Config *c) { AvahiServiceTypeBrowser *b; assert(c); if (!(b = avahi_service_type_browser_new( client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, c->domain, 0, service_type_browser_callback, c))) { fprintf(stderr, _("avahi_service_type_browser_new() failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); } n_cache_exhausted++; n_all_for_now++; } static void domain_browser_callback( AvahiDomainBrowser *b, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiBrowserEvent event, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void *userdata) { Config *c = userdata; assert(b); assert(c); switch (event) { case AVAHI_BROWSER_NEW: case AVAHI_BROWSER_REMOVE: { char ifname[IF_NAMESIZE]; if (c->parsable) printf("%c;%s;%s;%s\n", event == AVAHI_BROWSER_NEW ? '+' : '-', interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "", protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "", domain); else printf("%c %4s %4s %s\n", event == AVAHI_BROWSER_NEW ? '+' : '-', interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "n/a", protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "n/a", domain); break; } case AVAHI_BROWSER_FAILURE: fprintf(stderr, ("domain_browser failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); break; case AVAHI_BROWSER_CACHE_EXHAUSTED: n_cache_exhausted --; check_terminate(c); break; case AVAHI_BROWSER_ALL_FOR_NOW: n_all_for_now --; check_terminate(c); break; } } static void browse_domains(Config *c) { AvahiDomainBrowser *b; assert(c); if (!(b = avahi_domain_browser_new( client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, c->domain, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browser_callback, c))) { fprintf(stderr, _("avahi_domain_browser_new() failed: %s\n"), avahi_strerror(avahi_client_errno(client))); avahi_simple_poll_quit(simple_poll); } n_cache_exhausted++; n_all_for_now++; } static int start(Config *config) { assert(!browsing); if (config->verbose && !config->parsable) { const char *version, *hn; if (!(version = avahi_client_get_version_string(client))) { fprintf(stderr, _("Failed to query version string: %s\n"), avahi_strerror(avahi_client_errno(client))); return -1; } if (!(hn = avahi_client_get_host_name_fqdn(client))) { fprintf(stderr, _("Failed to query host name: %s\n"), avahi_strerror(avahi_client_errno(client))); return -1; } fprintf(stderr, _("Server version: %s; Host name: %s\n"), version, hn); if (config->command == COMMAND_BROWSE_DOMAINS) { /* Translators: This is a column heading with abbreviations for * Event (+/-), Network Interface, Protocol (IPv4/v6), Domain */ fprintf(stderr, _("E Ifce Prot Domain\n")); } else { /* Translators: This is a column heading with abbreviations for * Event (+/-), Network Interface, Protocol (IPv4/v6), Domain */ fprintf(stderr, _("E Ifce Prot %-*s %-20s Domain\n"), n_columns-35, _("Name"), _("Type")); } } if (config->command == COMMAND_BROWSE_SERVICES) browse_service_type(config, config->stype, config->domain); else if (config->command == COMMAND_BROWSE_ALL_SERVICES) browse_all(config); else { assert(config->command == COMMAND_BROWSE_DOMAINS); browse_domains(config); } browsing = 1; return 0; } static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { Config *config = userdata; /* This function might be called when avahi_client_new() has not * returned yet.*/ client = c; switch (state) { case AVAHI_CLIENT_FAILURE: if (config->no_fail && avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { int error; /* We have been disconnected, so let reconnect */ fprintf(stderr, _("Disconnected, reconnecting ...\n")); avahi_client_free(client); client = NULL; avahi_string_list_free(browsed_types); browsed_types = NULL; while (services) remove_service(config, services); browsing = 0; if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_NO_FAIL, client_callback, config, &error))) { fprintf(stderr, _("Failed to create client object: %s\n"), avahi_strerror(error)); avahi_simple_poll_quit(simple_poll); } } else { fprintf(stderr, _("Client failure, exiting: %s\n"), avahi_strerror(avahi_client_errno(c))); avahi_simple_poll_quit(simple_poll); } break; case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_COLLISION: if (!browsing) if (start(config) < 0) avahi_simple_poll_quit(simple_poll); break; case AVAHI_CLIENT_CONNECTING: if (config->verbose && !config->parsable) fprintf(stderr, _("Waiting for daemon ...\n")); break; } } static void help(FILE *f, const char *argv0) { if (strstr(argv0, "domain")) fprintf(f, "%s [options] \n\n", argv0); else fprintf(f, "%s [options] <service type>\n" "%s [options] -a\n" "%s [options] -D\n" #if defined(HAVE_GDBM) || defined(HAVE_DBM) "%s [options] -b\n" #endif "\n", #if defined(HAVE_GDBM) || defined(HAVE_DBM) argv0, #endif argv0, argv0, argv0); fprintf(f, "%s%s", _(" -h --help Show this help\n" " -V --version Show version\n" " -D --browse-domains Browse for browsing domains instead of services\n" " -a --all Show all services, regardless of the type\n" " -d --domain=DOMAIN The domain to browse in\n" " -v --verbose Enable verbose mode\n" " -t --terminate Terminate after dumping a more or less complete list\n" " -c --cache Terminate after dumping all entries from the cache\n" " -l --ignore-local Ignore local services\n" " -r --resolve Resolve services found\n" " -f --no-fail Don't fail if the daemon is not available\n" " -p --parsable Output in parsable format\n"), #if defined(HAVE_GDBM) || defined(HAVE_DBM) _(" -k --no-db-lookup Don't lookup service types\n" " -b --dump-db Dump service type database\n") #else "" #endif ); } static int parse_command_line(Config *c, const char *argv0, int argc, char *argv[]) { int o; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "browse-domains", no_argument, NULL, 'D' }, { "domain", required_argument, NULL, 'd' }, { "all", no_argument, NULL, 'a' }, { "verbose", no_argument, NULL, 'v' }, { "terminate", no_argument, NULL, 't' }, { "cache", no_argument, NULL, 'c' }, { "ignore-local", no_argument, NULL, 'l' }, { "resolve", no_argument, NULL, 'r' }, { "no-fail", no_argument, NULL, 'f' }, { "parsable", no_argument, NULL, 'p' }, #if defined(HAVE_GDBM) || defined(HAVE_DBM) { "no-db-lookup", no_argument, NULL, 'k' }, { "dump-db", no_argument, NULL, 'b' }, #endif { NULL, 0, NULL, 0 } }; assert(c); c->command = strstr(argv0, "domain") ? COMMAND_BROWSE_DOMAINS : COMMAND_BROWSE_SERVICES; c->verbose = c->terminate_on_cache_exhausted = c->terminate_on_all_for_now = c->ignore_local = c->resolve = c->no_fail = c->parsable = 0; c->domain = c->stype = NULL; #if defined(HAVE_GDBM) || defined(HAVE_DBM) c->no_db_lookup = 0; #endif while ((o = getopt_long(argc, argv, "hVd:avtclrDfp" #if defined(HAVE_GDBM) || defined(HAVE_DBM) "kb" #endif , long_options, NULL)) >= 0) { switch(o) { case 'h': c->command = COMMAND_HELP; break; case 'V': c->command = COMMAND_VERSION; break; case 'a': c->command = COMMAND_BROWSE_ALL_SERVICES; break; case 'D': c->command = COMMAND_BROWSE_DOMAINS; break; case 'd': avahi_free(c->domain); c->domain = avahi_strdup(optarg); break; case 'v': c->verbose = 1; break; case 't': c->terminate_on_all_for_now = 1; break; case 'c': c->terminate_on_cache_exhausted = 1; break; case 'l': c->ignore_local = 1; break; case 'r': c->resolve = 1; break; case 'f': c->no_fail = 1; break; case 'p': c->parsable = 1; break; #if defined(HAVE_GDBM) || defined(HAVE_DBM) case 'k': c->no_db_lookup = 1; break; case 'b': c->command = COMMAND_DUMP_STDB; break; #endif default: return -1; } } if (c->command == COMMAND_BROWSE_SERVICES) { if (optind >= argc) { fprintf(stderr, _("Too few arguments\n")); return -1; } c->stype = avahi_strdup(argv[optind]); optind++; } if (optind < argc) { fprintf(stderr, _("Too many arguments\n")); return -1; } return 0; } int main(int argc, char *argv[]) { int ret = 1, error; Config config; const char *argv0; char *ec; avahi_init_i18n(); setlocale(LC_ALL, ""); if ((argv0 = strrchr(argv[0], '/'))) argv0++; else argv0 = argv[0]; if ((ec = getenv("COLUMNS"))) n_columns = atoi(ec); if (n_columns < 40) n_columns = 40; if (parse_command_line(&config, argv0, argc, argv) < 0) goto fail; switch (config.command) { case COMMAND_HELP: help(stdout, argv0); ret = 0; break; case COMMAND_VERSION: printf("%s "PACKAGE_VERSION"\n", argv0); ret = 0; break; case COMMAND_BROWSE_SERVICES: case COMMAND_BROWSE_ALL_SERVICES: case COMMAND_BROWSE_DOMAINS: if (!(simple_poll = avahi_simple_poll_new())) { fprintf(stderr, _("Failed to create simple poll object.\n")); goto fail; } if (sigint_install(simple_poll) < 0) goto fail; if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), config.no_fail ? AVAHI_CLIENT_NO_FAIL : 0, client_callback, &config, &error))) { fprintf(stderr, _("Failed to create client object: %s\n"), avahi_strerror(error)); goto fail; } avahi_simple_poll_loop(simple_poll); ret = 0; break; #if defined(HAVE_GDBM) || defined(HAVE_DBM) case COMMAND_DUMP_STDB: { char *t; stdb_setent(); while ((t = stdb_getent())) { if (config.no_db_lookup) printf("%s\n", t); else printf("%s\n", stdb_lookup(t)); } ret = 0; break; } #endif } fail: while (services) remove_service(&config, services); if (client) avahi_client_free(client); sigint_uninstall(); if (simple_poll) avahi_simple_poll_free(simple_poll); avahi_free(config.domain); avahi_free(config.stype); avahi_string_list_free(browsed_types); #if defined(HAVE_GDBM) || defined(HAVE_DBM) stdb_shutdown(); #endif return ret; }