]> git.meshlink.io Git - catta/blob - avahi-utils/avahi-browse.c
b8910fcb720981266e66023a609b9d395bd789ca
[catta] / avahi-utils / avahi-browse.c
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 <stdlib.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <assert.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <net/if.h>
34 #include <locale.h>
35
36 #include <avahi-common/simple-watch.h>
37 #include <avahi-common/error.h>
38 #include <avahi-common/malloc.h>
39 #include <avahi-common/domain.h>
40 #include <avahi-common/llist.h>
41 #include <avahi-client/client.h>
42 #include <avahi-client/lookup.h>
43
44 #include "sigint.h"
45
46 #ifdef HAVE_GDBM
47 #include "stdb.h"
48 #endif
49
50 typedef enum {
51     COMMAND_HELP,
52     COMMAND_VERSION,
53     COMMAND_BROWSE_SERVICES,
54     COMMAND_BROWSE_ALL_SERVICES,
55     COMMAND_BROWSE_DOMAINS
56 } Command;
57
58 typedef struct Config {
59     int verbose;
60     int terminate_on_all_for_now;
61     int terminate_on_cache_exhausted;
62     char *domain;
63     char *stype;
64     int ignore_local;
65     Command command;
66     int resolve;
67 #ifdef HAVE_GDBM
68     int no_db_lookup;
69 #endif
70 } Config;
71
72 typedef struct ServiceInfo ServiceInfo;
73
74 struct ServiceInfo {
75     AvahiIfIndex interface;
76     AvahiProtocol protocol;
77     char *name, *type, *domain;
78
79     AvahiServiceResolver *resolver;
80     Config *config;
81
82     AVAHI_LLIST_FIELDS(ServiceInfo, info);
83 };
84
85 static AvahiSimplePoll *simple_poll = NULL;
86 static AvahiClient *client = NULL;
87 static int n_all_for_now = 0, n_cache_exhausted = 0, n_resolving = 0;
88 static AvahiStringList *browsed_types = NULL;
89 static ServiceInfo *services = NULL;
90 static int n_columns = 80;
91
92 static void check_terminate(Config *c) {
93
94     assert(n_all_for_now >= 0);
95     assert(n_cache_exhausted >= 0);
96     assert(n_resolving >= 0);
97     
98     if (n_all_for_now <= 0 && n_resolving <= 0) {
99
100         if (c->verbose) {
101             printf(": All for now\n");
102             n_all_for_now++; /* Make sure that this event is not repeated */
103         }
104         
105         if (c->terminate_on_all_for_now)
106             avahi_simple_poll_quit(simple_poll);
107     }
108     
109     if (n_cache_exhausted <= 0 && n_resolving <= 0) {
110
111         if (c->verbose) {
112             printf(": Cache exhausted\n");
113             n_cache_exhausted++; /* Make sure that this event is not repeated */
114         }
115         
116         if (c->terminate_on_cache_exhausted)
117             avahi_simple_poll_quit(simple_poll);
118     }
119 }
120
121 static ServiceInfo *find_service(AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
122     ServiceInfo *i;
123
124     for (i = services; i; i = i->info_next)
125         if (i->interface == interface &&
126             i->protocol == protocol &&
127             strcasecmp(i->name, name) == 0 &&
128             avahi_domain_equal(i->type, type) == 0 &&
129             avahi_domain_equal(i->domain, domain) == 0)
130
131             return i;
132
133     return NULL;
134 }
135
136 static void print_service_line(Config *config, char c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
137     char ifname[IF_NAMESIZE];
138
139 #ifdef HAVE_GDBM
140     if (!config->no_db_lookup)
141         type = stdb_lookup(type);
142 #endif
143     
144     printf("%c %4s %4s %-*s %-20s %s\n",
145            c,
146            interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "n/a",
147            protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "n/a", 
148            n_columns-35, name, type, domain);
149 }
150
151 static void service_resolver_callback(
152     AvahiServiceResolver *r,
153     AvahiIfIndex interface,
154     AvahiProtocol protocol,
155     AvahiResolverEvent event,
156     const char *name,
157     const char *type,
158     const char *domain,
159     const char *host_name,
160     const AvahiAddress *a,
161     uint16_t port,
162     AvahiStringList *txt,
163     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
164     void *userdata) {
165     
166     ServiceInfo *i = userdata;
167     
168     assert(r);
169     assert(i);
170
171     switch (event) {
172         case AVAHI_RESOLVER_FOUND: {
173             char address[AVAHI_ADDRESS_STR_MAX], *t;
174
175             avahi_address_snprint(address, sizeof(address), a);
176
177             t = avahi_string_list_to_string(txt);
178
179             print_service_line(i->config, '=', interface, protocol, name, type, domain);
180             
181             printf("   hostname = [%s]\n"
182                    "   address = [%s]\n"
183                    "   port = [%i]\n"
184                    "   txt = [%s]\n",
185                    host_name,
186                    address,
187                    port,
188                    t);
189             avahi_free(t);
190
191             break;
192         }
193
194         case AVAHI_RESOLVER_FAILURE:
195             
196             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
197             break;
198     }
199
200     
201     avahi_service_resolver_free(i->resolver);
202     i->resolver = NULL;
203
204     assert(n_resolving > 0);
205     n_resolving--;
206     check_terminate(i->config);
207 }
208
209 static ServiceInfo *add_service(Config *c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
210     ServiceInfo *i;
211
212     i = avahi_new(ServiceInfo, 1);
213
214     if (c->resolve) {
215         if (!(i->resolver = avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, i))) {
216             avahi_free(i);
217             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
218             return NULL;
219         }
220
221         n_resolving++;
222     } else
223         i->resolver = NULL;
224
225     i->interface = interface;
226     i->protocol = protocol;
227     i->name = avahi_strdup(name);
228     i->type = avahi_strdup(type);
229     i->domain = avahi_strdup(domain);
230     i->config = c;
231
232     AVAHI_LLIST_PREPEND(ServiceInfo, info, services, i);
233
234     return i;
235 }
236
237 static void remove_service(Config *c, ServiceInfo *i) {
238     assert(c);
239     assert(i);
240
241     AVAHI_LLIST_REMOVE(ServiceInfo, info, services, i);
242
243     if (i->resolver)
244         avahi_service_resolver_free(i->resolver);
245     
246     avahi_free(i->name);
247     avahi_free(i->type);
248     avahi_free(i->domain);
249     avahi_free(i);
250 }
251
252 static void service_browser_callback(
253     AvahiServiceBrowser *b,
254     AvahiIfIndex interface,
255     AvahiProtocol protocol,
256     AvahiBrowserEvent event,
257     const char *name,
258     const char *type,
259     const char *domain,
260     AvahiLookupResultFlags flags,
261     void *userdata) {
262
263     Config *c = userdata;
264     
265     assert(b);
266     assert(c);
267
268     switch (event) {
269         case AVAHI_BROWSER_NEW: {
270             if (c->ignore_local && (flags & AVAHI_LOOKUP_RESULT_LOCAL))
271                 break;
272
273             if (find_service(interface, protocol, name, type, domain))
274                 return;
275
276             add_service(c, interface, protocol, name, type, domain);
277
278             print_service_line(c, '+', interface, protocol, name, type, domain);
279             break;
280
281         }
282
283         case AVAHI_BROWSER_REMOVE: {
284             ServiceInfo *info;
285             
286             if (!(info = find_service(interface, protocol, name, type, domain)))
287                 return;
288
289             remove_service(c, info);
290             
291             print_service_line(c, '-', interface, protocol, name, type, domain);
292             break;
293         }
294             
295         case AVAHI_BROWSER_FAILURE:
296             fprintf(stderr, "service_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
297             avahi_simple_poll_quit(simple_poll);
298             break;
299
300         case AVAHI_BROWSER_CACHE_EXHAUSTED:
301             n_cache_exhausted --;
302             check_terminate(c);
303             break;
304             
305         case AVAHI_BROWSER_ALL_FOR_NOW:
306             n_all_for_now --;
307             check_terminate(c);
308             break;
309     }
310 }
311
312 static void browse_service_type(Config *c, const char *stype, const char *domain) {
313     AvahiServiceBrowser *b;
314     AvahiStringList *i;
315     
316     assert(c);
317     assert(client);
318     assert(stype);
319
320     for (i = browsed_types; i; i = i->next)
321         if (avahi_domain_equal(stype, (char*) i->text))
322             return;
323
324     if (!(b = avahi_service_browser_new(
325               client,
326               AVAHI_IF_UNSPEC,
327               AVAHI_PROTO_UNSPEC,
328               stype,
329               domain,
330               0,
331               service_browser_callback,
332               c))) {
333
334         fprintf(stderr, "avahi_service_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
335         avahi_simple_poll_quit(simple_poll);
336     }
337
338     browsed_types = avahi_string_list_add(browsed_types, stype);
339
340     n_all_for_now++;
341     n_cache_exhausted++;
342 }
343
344 static void service_type_browser_callback(
345     AvahiServiceTypeBrowser *b,
346     AVAHI_GCC_UNUSED AvahiIfIndex interface,
347     AVAHI_GCC_UNUSED AvahiProtocol protocol,
348     AvahiBrowserEvent event,
349     const char *type,
350     const char *domain,
351     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
352     void *userdata) {
353
354     Config *c = userdata;
355
356     assert(b);
357     assert(c);
358     
359     switch (event) {
360         
361         case AVAHI_BROWSER_NEW:
362             browse_service_type(c, type, domain);
363             break;
364
365         case AVAHI_BROWSER_REMOVE:
366             /* We're dirty and never remove the browser again */
367             break;
368
369         case AVAHI_BROWSER_FAILURE:
370             fprintf(stderr, "service_type_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
371             avahi_simple_poll_quit(simple_poll);
372             break;
373             
374         case AVAHI_BROWSER_CACHE_EXHAUSTED:
375             n_cache_exhausted --;
376             check_terminate(c);
377             break;
378             
379         case AVAHI_BROWSER_ALL_FOR_NOW:
380             n_all_for_now --;
381             check_terminate(c);
382             break;
383     }
384 }
385
386 static void browse_all(Config *c) {
387     AvahiServiceTypeBrowser *b;
388     
389     assert(c);
390
391     if (!(b = avahi_service_type_browser_new(
392               client,
393               AVAHI_IF_UNSPEC,
394               AVAHI_PROTO_UNSPEC,
395               c->domain,
396               0,
397               service_type_browser_callback,
398               c))) {
399         
400         fprintf(stderr, "avahi_service_type_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
401         avahi_simple_poll_quit(simple_poll);
402     }
403
404     n_cache_exhausted++;
405     n_all_for_now++;
406 }
407
408 static void domain_browser_callback(
409     AvahiDomainBrowser *b,
410     AVAHI_GCC_UNUSED AvahiIfIndex interface,
411     AVAHI_GCC_UNUSED AvahiProtocol protocol,
412     AvahiBrowserEvent event,
413     const char *domain,
414     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
415     void *userdata) {
416
417     Config *c = userdata;
418
419     assert(b);
420     assert(c);
421     
422     switch (event) {
423         
424         case AVAHI_BROWSER_NEW:
425         case AVAHI_BROWSER_REMOVE: {
426             char ifname[IF_NAMESIZE];
427             
428             printf("%c %4s %4s %s\n",
429                    event == AVAHI_BROWSER_NEW ? '+' : '-',
430                    interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "n/a",
431                    protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "n/a", 
432                    domain);
433             break;
434         }
435
436         case AVAHI_BROWSER_FAILURE:
437             fprintf(stderr, "domain_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
438             avahi_simple_poll_quit(simple_poll);
439             break;
440             
441         case AVAHI_BROWSER_CACHE_EXHAUSTED:
442             n_cache_exhausted --;
443             check_terminate(c);
444             break;
445             
446         case AVAHI_BROWSER_ALL_FOR_NOW:
447             n_all_for_now --;
448             check_terminate(c);
449             break;
450     }
451 }
452
453 static void browse_domains(Config *c) {
454     AvahiDomainBrowser *b;
455
456     assert(c);
457
458     if (!(b = avahi_domain_browser_new(
459               client,
460               AVAHI_IF_UNSPEC,
461               AVAHI_PROTO_UNSPEC,
462               c->domain,
463               AVAHI_DOMAIN_BROWSER_BROWSE,
464               0,
465               domain_browser_callback,
466               c))) {
467
468         fprintf(stderr, "avahi_domain_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
469         avahi_simple_poll_quit(simple_poll);
470     }
471
472     n_cache_exhausted++;
473     n_all_for_now++;
474 }
475
476 static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
477     switch (state) {
478         case AVAHI_CLIENT_FAILURE:
479             fprintf(stderr, "Client failure, exiting: %s\n", avahi_strerror(avahi_client_errno(c)));
480             avahi_simple_poll_quit(simple_poll);
481             break;
482             
483         case AVAHI_CLIENT_DISCONNECTED:
484             fprintf(stderr, "Client disconnected, exiting.\n");
485             avahi_simple_poll_quit(simple_poll);
486             break;
487
488         case AVAHI_CLIENT_S_REGISTERING:
489         case AVAHI_CLIENT_S_RUNNING:
490         case AVAHI_CLIENT_S_COLLISION:
491             ;
492     }
493 }
494
495 static void help(FILE *f, const char *argv0) {
496     fprintf(f,
497             "%s [options] <service type>\n"
498             "%s [options] -a\n"
499             "%s [options] -D\n\n"
500             "    -h --help            Show this help\n"
501             "    -V --version         Show version\n"
502             "    -D --browse-domains  Browse for browsing domains instead of services\n"
503             "    -a --all             Show all services, regardless of the type\n"
504             "    -d --domain=DOMAIN   The domain to browse in\n"
505             "    -v --verbose         Enable verbose mode\n"
506             "    -t --terminate       Terminate after dumping a more or less complete list\n"
507             "    -c --cache           Terminate after dumping all entries from the cache\n"
508             "    -l --ignore-local    Ignore local services\n"
509             "    -r --resolve         Resolve services found\n"
510 #ifdef HAVE_GDBM
511             "    -k --no-db-lookup    Don't lookup service types\n"
512 #endif
513             , argv0, argv0, argv0);
514 }
515
516 static int parse_command_line(Config *c, int argc, char *argv[]) {
517     int o;
518
519     static const struct option long_options[] = {
520         { "help",           no_argument,       NULL, 'h' },
521         { "version",        no_argument,       NULL, 'V' },
522         { "browse-domains", no_argument,       NULL, 'D' },
523         { "domain",         required_argument, NULL, 'd' },
524         { "all",            no_argument,       NULL, 'a' },
525         { "verbose",        no_argument,       NULL, 'v' },
526         { "terminate",      no_argument,       NULL, 't' },
527         { "cache",          no_argument,       NULL, 'c' },
528         { "ignore-local",   no_argument,       NULL, 'l' },
529         { "resolve",        no_argument,       NULL, 'r' },
530 #ifdef HAVE_GDBM
531         { "no-db-lookup",   no_argument,       NULL, 'k' },
532 #endif
533         { NULL, 0, NULL, 0 }
534     };
535
536     assert(c);
537
538     c->command = COMMAND_BROWSE_SERVICES;
539     c->verbose =
540         c->terminate_on_cache_exhausted =
541         c->terminate_on_all_for_now =
542         c->ignore_local =
543         c->resolve = 0;
544     c->domain = c->stype = NULL;
545
546 #ifdef HAVE_GDBM
547     c->no_db_lookup = 0;
548 #endif
549     
550     opterr = 0;
551     while ((o = getopt_long(argc, argv, "hVd:avtclrD"
552 #ifdef HAVE_GDBM
553                             "k"
554 #endif
555                             , long_options, NULL)) >= 0) {
556
557         switch(o) {
558             case 'h':
559                 c->command = COMMAND_HELP;
560                 break;
561             case 'V':
562                 c->command = COMMAND_VERSION;
563                 break;
564             case 'a':
565                 c->command = COMMAND_BROWSE_ALL_SERVICES;
566                 break;
567             case 'D':
568                 c->command = COMMAND_BROWSE_DOMAINS;
569                 break;
570             case 'd':
571                 c->domain = avahi_strdup(optarg);
572                 break;
573             case 'v':
574                 c->verbose = 1;
575                 break;
576             case 't':
577                 c->terminate_on_all_for_now = 1;
578                 break;
579             case 'c':
580                 c->terminate_on_cache_exhausted = 1;
581                 break;
582             case 'l':
583                 c->ignore_local = 1;
584                 break;
585             case 'r':
586                 c->resolve = 1;
587                 break;
588 #ifdef HAVE_GDBM
589             case 'k':
590                 c->no_db_lookup = 1;
591                 break;
592 #endif
593             default:
594                 fprintf(stderr, "Invalid command line argument: %c\n", o);
595                 return -1;
596         }
597     }
598
599     if (c->command == COMMAND_BROWSE_SERVICES) {
600         if (optind >= argc) {
601             fprintf(stderr, "Too few arguments\n");
602             return -1;
603         }
604
605         c->stype = avahi_strdup(argv[optind]);
606         optind++;
607     }
608     
609     if (optind < argc) {
610         fprintf(stderr, "Too many arguments\n");
611         return -1;
612     }
613         
614     return 0;
615 }
616
617 int main(int argc, char *argv[]) {
618     int ret = 1, error;
619     Config config;
620     const char *argv0;
621     char *ec;
622
623     setlocale(LC_ALL, "");
624
625     if ((argv0 = strrchr(argv[0], '/')))
626         argv0++;
627     else
628         argv0 = argv[0];
629
630     if ((ec = getenv("COLUMNS")))
631         n_columns = atoi(ec);
632
633     if (n_columns < 40)
634         n_columns = 40;
635     
636     if (parse_command_line(&config, argc, argv) < 0)
637         goto fail;
638
639     switch (config.command) {
640         case COMMAND_HELP:
641             help(stdout, argv0);
642             ret = 0;
643             break;
644             
645         case COMMAND_VERSION:
646             printf("%s "PACKAGE_VERSION"\n", argv0);
647             ret = 0;
648             break;
649
650         case COMMAND_BROWSE_SERVICES:
651         case COMMAND_BROWSE_ALL_SERVICES:
652         case COMMAND_BROWSE_DOMAINS:
653             
654             if (!(simple_poll = avahi_simple_poll_new())) {
655                 fprintf(stderr, "Failed to create simple poll object.\n");
656                 goto fail;
657             }
658             
659             if (sigint_install(simple_poll) < 0)
660                 goto fail;
661             
662             if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), client_callback, NULL, &error))) {
663                 fprintf(stderr, "Failed to create client object: %s\n", avahi_strerror(error));
664                 goto fail;
665             }
666
667             if (config.verbose) {
668                 const char *version, *hn;
669
670                 if (!(version = avahi_client_get_version_string(client))) {
671                     fprintf(stderr, "Failed to query version string: %s\n", avahi_strerror(avahi_client_errno(client)));
672                     goto fail;
673                 }
674
675                 if (!(hn = avahi_client_get_host_name_fqdn(client))) {
676                     fprintf(stderr, "Failed to query host name: %s\n", avahi_strerror(avahi_client_errno(client)));
677                     goto fail;
678                 }
679                 
680                 fprintf(stderr, "Server version: %s; Host name: %s\n", version, hn);
681
682                 if (config.command == COMMAND_BROWSE_DOMAINS)
683                     fprintf(stderr, "E Ifce Prot Domain\n");
684                 else
685                     fprintf(stderr, "E Ifce Prot %-*s %-20s Domain\n", n_columns-35, "Name", "Type");
686             }
687
688             if (config.command == COMMAND_BROWSE_SERVICES)
689                 browse_service_type(&config, config.stype, config.domain);
690             else if (config.command == COMMAND_BROWSE_ALL_SERVICES)
691                 browse_all(&config);
692             else {
693                 assert(config.command == COMMAND_BROWSE_DOMAINS);
694                 browse_domains(&config);
695             }
696             
697             avahi_simple_poll_loop(simple_poll);
698             ret = 0;
699             break;
700     }
701     
702     
703 fail:
704
705     while (services)
706         remove_service(&config, services);
707
708     if (client)
709         avahi_client_free(client);
710
711     sigint_uninstall();
712     
713     if (simple_poll)
714         avahi_simple_poll_free(simple_poll);
715
716     avahi_free(config.domain);
717     avahi_free(config.stype);
718
719     avahi_string_list_free(browsed_types);
720
721 #ifdef HAVE_GDBM
722     stdb_shutdown();
723 #endif    
724
725     return ret;
726 }