]> git.ipfire.org Git - thirdparty/cups.git/blob - test/ippfind.c
Save work on ippfind program. Nearly there.
[thirdparty/cups.git] / test / ippfind.c
1 /*
2 * "$Id$"
3 *
4 * Utility to find IPP printers via Bonjour/DNS-SD and optionally run
5 * commands such as IPP and Bonjour conformance tests. This tool is
6 * inspired by the UNIX "find" command, thus its name.
7 *
8 * Copyright 2008-2013 by Apple Inc.
9 *
10 * These coded instructions, statements, and computer programs are the
11 * property of Apple Inc. and are protected by Federal copyright
12 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
13 * which should have been included with this file. If this file is
14 * file is missing or damaged, see the license at "http://www.cups.org/".
15 *
16 * This file is subject to the Apple OS-Developed Software exception.
17 *
18 * Usage:
19 *
20 * ./ippfind [options] regtype[,subtype][.domain.] ... [expression]
21 * ./ippfind [options] name[.regtype[.domain.]] ... [expression]
22 * ./ippfind --help
23 * ./ippfind --version
24 *
25 * Supported regtypes are:
26 *
27 * _http._tcp - HTTP (RFC 2616)
28 * _https._tcp - HTTPS (RFC 2818)
29 * _ipp._tcp - IPP (RFC 2911)
30 * _ipps._tcp - IPPS (pending)
31 * _printer._tcp - LPD (RFC 1179)
32 *
33 * Exit Codes:
34 *
35 * 0 if result for all processed expressions is true
36 * 1 if result of any processed expression is false
37 * 2 if browsing or any query or resolution failed
38 * 3 if an undefined option or invalid expression was specified
39 *
40 * Options:
41 *
42 * --help - Show program help
43 * --version - Show program version
44 * -4 - Use IPv4 when listing
45 * -6 - Use IPv6 when listing
46 * -T seconds - Specify browse timeout (default 10
47 * seconds)
48 * -V version - Specify IPP version (1.1, 2.0, 2.1, 2.2)
49 *
50 * "expression" is any of the following:
51 *
52 * -d regex
53 * --domain regex - True if the domain matches the given
54 * regular expression.
55 *
56 * -e utility [argument ...] ;
57 * --exec utility [argument ...] ; - Executes the specified program; "{}"
58 * does a substitution (see below)
59 *
60 * -l
61 * --ls - Lists attributes returned by
62 * Get-Printer-Attributes for IPP printers,
63 * ???? of HEAD request for HTTP URLs)
64 * True if resource is accessible, false
65 * otherwise.
66 *
67 * --local - True if the service is local to this
68 * computer.
69 *
70 * -n regex
71 * --name regex - True if the name matches the given
72 * regular expression.
73 *
74 * --path regex - True if the URI resource path matches
75 * the given regular expression.
76 *
77 * -p
78 * --print - Prints the URI of found printers (always
79 * true, default if -e, -l, -p, -q, or -s
80 * is not specified.
81 *
82 * -q
83 * --quiet - Quiet mode (just return exit code)
84 *
85 * -r
86 * --remote - True if the service is not local to this
87 * computer.
88 *
89 * -s
90 * --print-name - Prints the service name of found
91 * printers.
92 *
93 * -t key
94 * --txt key - True if the TXT record contains the
95 * named key
96 *
97 * --txt-* regex - True if the TXT record contains the
98 * named key and matches the given regular
99 * expression.
100 *
101 * -u regex
102 * --uri regex - True if the URI matches the given
103 * regular expression.
104 *
105 * Expressions may also contain modifiers:
106 *
107 * ( expression ) - Group the result of expressions.
108 *
109 * ! expression
110 * --not expression - Unary NOT
111 *
112 * --false - Always false
113 * --true - Always true
114 *
115 * expression expression
116 * expression --and expression - Logical AND.
117 *
118 * expression --or expression - Logical OR.
119 *
120 * The substitutions for {} are:
121 *
122 * {} - URI
123 * {service_domain} - Domain name
124 * {service_hostname} - FQDN
125 * {service_name} - Service name
126 * {service_port} - Port number
127 * {service_regtype} - DNS-SD registration type
128 * {service_scheme} - URI scheme for DNS-SD registration type
129 * {service_uri} - URI
130 * {txt_*} - Value of TXT record key
131 *
132 * These variables are also set in the environment for executed programs:
133 *
134 * IPPFIND_SERVICE_DOMAIN - Domain name
135 * IPPFIND_SERVICE_HOSTNAME - FQDN
136 * IPPFIND_SERVICE_NAME - Service name
137 * IPPFIND_SERVICE_PORT - Port number
138 * IPPFIND_SERVICE_REGTYPE - DNS-SD registration type
139 * IPPFIND_SERVICE_SCHEME - URI scheme for DNS-SD registration type
140 * IPPFIND_SERVICE_URI - URI
141 * IPPFIND_TXT_* - Values of TXT record key (uppercase)
142 *
143 * Contents:
144 *
145 */
146
147 /*
148 * Include necessary headers.
149 */
150
151 #include <cups/cups-private.h>
152 #include <regex.h>
153 #ifdef HAVE_DNSSD
154 # include <dns_sd.h>
155 #elif defined(HAVE_AVAHI)
156 # include <avahi-client/client.h>
157 # include <avahi-client/lookup.h>
158 # include <avahi-common/simple-watch.h>
159 # include <avahi-common/domain.h>
160 # include <avahi-common/error.h>
161 # include <avahi-common/malloc.h>
162 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
163 #endif /* HAVE_DNSSD */
164
165
166 /*
167 * Structures...
168 */
169
170 typedef enum ippfind_exit_e /* Exit codes */
171 {
172 IPPFIND_EXIT_TRUE = 0, /* OK and result is true */
173 IPPFIND_EXIT_FALSE, /* OK but result is false*/
174 IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */
175 IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */
176 IPPFIND_EXIT_MEMORY /* Out of memory */
177 } ippfind_exit_t;
178
179 typedef enum ippfind_op_e /* Operations for expressions */
180 {
181 IPPFIND_OP_NONE, /* No operation */
182 IPPFIND_OP_AND, /* Logical AND of all children */
183 IPPFIND_OP_OR, /* Logical OR of all children */
184 IPPFIND_OP_TRUE, /* Always true */
185 IPPFIND_OP_FALSE, /* Always false */
186 IPPFIND_OP_IS_LOCAL, /* Is a local service */
187 IPPFIND_OP_IS_REMOTE, /* Is a remote service */
188 IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */
189 IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */
190 IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */
191 IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */
192 IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */
193 IPPFIND_OP_URI_REGEX, /* URI matches regular expression */
194
195 IPPFIND_OP_OUTPUT = 100, /* Output operations */
196 IPPFIND_OP_EXEC, /* Execute when true */
197 IPPFIND_OP_LIST, /* List when true */
198 IPPFIND_OP_PRINT_NAME, /* Print URI when true */
199 IPPFIND_OP_PRINT_URI, /* Print name when true */
200 IPPFIND_OP_QUIET, /* No output when true */
201 } ippfind_op_t;
202
203 typedef struct ippfind_expr_s /* Expression */
204 {
205 struct ippfind_expr_s
206 *prev, /* Previous expression */
207 *next, /* Next expression */
208 *parent, /* Parent expressions */
209 *child; /* Child expressions */
210 ippfind_op_t op; /* Operation code (see above) */
211 int invert; /* Invert the result */
212 char *key; /* TXT record key */
213 regex_t re; /* Regular expression for matching */
214 int num_args; /* Number of arguments for exec */
215 char **args; /* Arguments for exec */
216 } ippfind_expr_t;
217
218 typedef struct ippfind_srv_s /* Service information */
219 {
220 #ifdef HAVE_DNSSD
221 DNSServiceRef ref; /* Service reference for query */
222 #elif defined(HAVE_AVAHI)
223 AvahiServiceResolver *ref; /* Resolver */
224 #endif /* HAVE_DNSSD */
225 char *name, /* Service name */
226 *domain, /* Domain name */
227 *regtype, /* Registration type */
228 *fullName, /* Full name */
229 *host, /* Hostname */
230 *uri; /* URI */
231 int num_txt; /* Number of TXT record keys */
232 cups_option_t *txt; /* TXT record keys */
233 int port, /* Port number */
234 is_local, /* Is a local service? */
235 is_processed, /* Did we process the service? */
236 is_resolved; /* Got the resolve data? */
237 } ippfind_srv_t;
238
239
240 /*
241 * Local globals...
242 */
243
244 #ifdef HAVE_DNSSD
245 static DNSServiceRef dnssd_ref; /* Master service reference */
246 #elif defined(HAVE_AVAHI)
247 static AvahiClient *avahi_client = NULL;/* Client information */
248 static int avahi_got_data = 0; /* Got data from poll? */
249 static AvahiSimplePoll *avahi_poll = NULL;
250 /* Poll information */
251 #endif /* HAVE_DNSSD */
252
253 static int address_family = AF_UNSPEC;
254 /* Address family for LIST */
255 static int bonjour_error = 0; /* Error browsing/resolving? */
256 static double bonjour_timeout = 1.0; /* Timeout in seconds */
257 static int ipp_version = 20; /* IPP version for LIST */
258
259
260 /*
261 * Local functions...
262 */
263
264 #ifdef HAVE_DNSSD
265 static void browse_callback(DNSServiceRef sdRef,
266 DNSServiceFlags flags,
267 uint32_t interfaceIndex,
268 DNSServiceErrorType errorCode,
269 const char *serviceName,
270 const char *regtype,
271 const char *replyDomain, void *context)
272 __attribute__((nonnull(1,5,6,7,8)));
273 static void browse_local_callback(DNSServiceRef sdRef,
274 DNSServiceFlags flags,
275 uint32_t interfaceIndex,
276 DNSServiceErrorType errorCode,
277 const char *serviceName,
278 const char *regtype,
279 const char *replyDomain,
280 void *context)
281 __attribute__((nonnull(1,5,6,7,8)));
282 #elif defined(HAVE_AVAHI)
283 static void browse_callback(AvahiServiceBrowser *browser,
284 AvahiIfIndex interface,
285 AvahiProtocol protocol,
286 AvahiBrowserEvent event,
287 const char *serviceName,
288 const char *regtype,
289 const char *replyDomain,
290 AvahiLookupResultFlags flags,
291 void *context);
292 static void client_callback(AvahiClient *client,
293 AvahiClientState state,
294 void *context);
295 #endif /* HAVE_AVAHI */
296
297 static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b);
298 static const char *dnssd_error_string(int error);
299 static int eval_expr(ippfind_srv_t *service,
300 ippfind_expr_t *expressions);
301 static ippfind_srv_t *get_service(cups_array_t *services,
302 const char *serviceName,
303 const char *regtype,
304 const char *replyDomain)
305 __attribute__((nonnull(1,2,3,4)));
306 static double get_time(void);
307 static ippfind_expr_t *new_expr(ippfind_op_t op, int invert, const char *key,
308 const char *regex, char **args);
309 #ifdef HAVE_DNSSD
310 static void resolve_callback(DNSServiceRef sdRef,
311 DNSServiceFlags flags,
312 uint32_t interfaceIndex,
313 DNSServiceErrorType errorCode,
314 const char *fullName,
315 const char *hostTarget, uint16_t port,
316 uint16_t txtLen,
317 const unsigned char *txtRecord,
318 void *context)
319 __attribute__((nonnull(1,5,6,9, 10)));
320 #elif defined(HAVE_AVAHI)
321 static int poll_callback(struct pollfd *pollfds,
322 unsigned int num_pollfds, int timeout,
323 void *context);
324 static void resolve_callback(AvahiServiceResolver *res,
325 AvahiIfIndex interface,
326 AvahiProtocol protocol,
327 AvahiBrowserEvent event,
328 const char *serviceName,
329 const char *regtype,
330 const char *replyDomain,
331 const char *host_name,
332 uint16_t port,
333 AvahiStringList *txt,
334 AvahiLookupResultFlags flags,
335 void *context);
336 #endif /* HAVE_DNSSD */
337 static void set_service_uri(ippfind_srv_t *service);
338 static void show_usage(void) __attribute__((noreturn));
339 static void show_version(void) __attribute__((noreturn));
340
341
342 /*
343 * 'main()' - Browse for printers.
344 */
345
346 int /* O - Exit status */
347 main(int argc, /* I - Number of command-line args */
348 char *argv[]) /* I - Command-line arguments */
349 {
350 int i, /* Looping var */
351 have_output = 0,/* Have output expression */
352 status = IPPFIND_EXIT_TRUE;
353 /* Exit status */
354 const char *opt, /* Option character */
355 *search; /* Current browse/resolve string */
356 cups_array_t *searches; /* Things to browse/resolve */
357 cups_array_t *services; /* Service array */
358 ippfind_srv_t *service; /* Current service */
359 ippfind_expr_t *expressions = NULL,
360 /* Expression tree */
361 *temp = NULL, /* New expression */
362 *parent = NULL, /* Parent expression */
363 *current = NULL;/* Current expression */
364 ippfind_op_t logic = IPPFIND_OP_AND;
365 /* Logic for next expression */
366 int invert = 0; /* Invert expression? */
367 int err; /* DNS-SD error */
368 #ifdef HAVE_DNSSD
369 fd_set sinput; /* Input set for select() */
370 struct timeval stimeout; /* Timeout for select() */
371 #endif /* HAVE_DNSSD */
372 double endtime; /* End time */
373
374
375 /*
376 * Initialize the locale...
377 */
378
379 _cupsSetLocale(argv);
380
381 /*
382 * Create arrays to track services and things we want to browse/resolve...
383 */
384
385 searches = cupsArrayNew(NULL, NULL);
386 services = cupsArrayNew((cups_array_func_t)compare_services, NULL);
387
388 /*
389 * Parse command-line...
390 */
391
392 for (i = 1; i < argc; i ++)
393 {
394 if (argv[i][0] == '-')
395 {
396 if (argv[i][1] == '-')
397 {
398 /*
399 * Parse --option options...
400 */
401
402 if (!strcmp(argv[i], "--and"))
403 {
404 if (logic != IPPFIND_OP_AND && current && current->prev)
405 {
406 /*
407 * OK, we have more than 1 rule in the current tree level.
408 * Make a new group tree and move the previous rule to it...
409 */
410
411 if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, NULL)) == NULL)
412 return (IPPFIND_EXIT_MEMORY);
413
414 temp->child = current;
415 temp->parent = current->parent;
416 current->prev->next = temp;
417 temp->prev = current->prev;
418
419 current->prev = NULL;
420 current->parent = temp;
421 parent = temp;
422 }
423 else if (parent)
424 parent->op = IPPFIND_OP_AND;
425
426 logic = IPPFIND_OP_AND;
427 temp = NULL;
428 }
429 else if (!strcmp(argv[i], "--domain"))
430 {
431 i ++;
432 if (i >= argc)
433 show_usage();
434
435 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i],
436 NULL)) == NULL)
437 return (IPPFIND_EXIT_MEMORY);
438 }
439 else if (!strcmp(argv[i], "--exec"))
440 {
441 i ++;
442 if (i >= argc)
443 show_usage();
444
445 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
446 argv + i)) == NULL)
447 return (IPPFIND_EXIT_MEMORY);
448
449 while (i < argc)
450 if (!strcmp(argv[i], ";"))
451 break;
452 else
453 i ++;
454
455 if (i >= argc)
456 show_usage();
457
458 have_output = 1;
459 }
460 else if (!strcmp(argv[i], "--false"))
461 {
462 i ++;
463 if (i >= argc)
464 show_usage();
465
466 if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL,
467 NULL)) == NULL)
468 return (IPPFIND_EXIT_MEMORY);
469 }
470 else if (!strcmp(argv[i], "--help"))
471 {
472 show_usage();
473 }
474 else if (!strcmp(argv[i], "--ls"))
475 {
476 i ++;
477 if (i >= argc)
478 show_usage();
479
480 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
481 NULL)) == NULL)
482 return (IPPFIND_EXIT_MEMORY);
483
484 have_output = 1;
485 }
486 else if (!strcmp(argv[i], "--local"))
487 {
488 i ++;
489 if (i >= argc)
490 show_usage();
491
492 if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL,
493 NULL)) == NULL)
494 return (IPPFIND_EXIT_MEMORY);
495 }
496 else if (!strcmp(argv[i], "--name"))
497 {
498 i ++;
499 if (i >= argc)
500 show_usage();
501
502 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i],
503 NULL)) == NULL)
504 return (IPPFIND_EXIT_MEMORY);
505 }
506 else if (!strcmp(argv[i], "--not"))
507 {
508 invert = 1;
509 }
510 else if (!strcmp(argv[i], "--or"))
511 {
512 if (logic != IPPFIND_OP_OR && current)
513 {
514 /*
515 * OK, we have two possibilities; either this is the top-level
516 * rule or we have a bunch of AND rules at this level.
517 */
518
519 if (!parent)
520 {
521 /*
522 * This is the top-level rule; we have to group *all* of the AND
523 * rules down a level, as AND has precedence over OR.
524 */
525
526 if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
527 NULL)) == NULL)
528 return (IPPFIND_EXIT_MEMORY);
529
530 while (current->prev)
531 {
532 current->parent = temp;
533 current = current->prev;
534 }
535
536 current->parent = temp;
537 temp->child = current;
538
539 expressions = current = temp;
540 }
541 else
542 {
543 /*
544 * This isn't the top rule, so go up one level...
545 */
546
547 current = parent;
548 parent = current->parent;
549 }
550 }
551
552 logic = IPPFIND_OP_OR;
553 temp = NULL;
554 }
555 else if (!strcmp(argv[i], "--path"))
556 {
557 i ++;
558 if (i >= argc)
559 show_usage();
560
561 if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i],
562 NULL)) == NULL)
563 return (IPPFIND_EXIT_MEMORY);
564 }
565 else if (!strcmp(argv[i], "--print"))
566 {
567 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
568 NULL)) == NULL)
569 return (IPPFIND_EXIT_MEMORY);
570
571 have_output = 1;
572 }
573 else if (!strcmp(argv[i], "--print-name"))
574 {
575 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
576 NULL)) == NULL)
577 return (IPPFIND_EXIT_MEMORY);
578
579 have_output = 1;
580 }
581 else if (!strcmp(argv[i], "--quiet"))
582 {
583 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
584 NULL)) == NULL)
585 return (IPPFIND_EXIT_MEMORY);
586
587 have_output = 1;
588 }
589 else if (!strcmp(argv[i], "--remote"))
590 {
591 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
592 NULL)) == NULL)
593 return (IPPFIND_EXIT_MEMORY);
594 }
595 else if (!strcmp(argv[i], "--true"))
596 {
597 if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i],
598 NULL)) == NULL)
599 return (IPPFIND_EXIT_MEMORY);
600 }
601 else if (!strcmp(argv[i], "--txt"))
602 {
603 i ++;
604 if (i >= argc)
605 show_usage();
606
607 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL,
608 NULL)) == NULL)
609 return (IPPFIND_EXIT_MEMORY);
610 }
611 else if (!strncmp(argv[i], "--txt-", 5))
612 {
613 const char *key = argv[i] + 5;/* TXT key */
614
615 i ++;
616 if (i >= argc)
617 show_usage();
618
619 if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i],
620 NULL)) == NULL)
621 return (IPPFIND_EXIT_MEMORY);
622 }
623 else if (!strcmp(argv[i], "--uri"))
624 {
625 i ++;
626 if (i >= argc)
627 show_usage();
628
629 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i],
630 NULL)) == NULL)
631 return (IPPFIND_EXIT_MEMORY);
632 }
633 else if (!strcmp(argv[i], "--version"))
634 {
635 show_version();
636 }
637 else
638 {
639 _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."),
640 "ippfind", argv[i]);
641 show_usage();
642 }
643
644 if (temp)
645 {
646 /*
647 * Add new expression...
648 */
649
650 if (current)
651 {
652 temp->parent = parent;
653 current->next = temp;
654 }
655 else if (parent)
656 parent->child = temp;
657 else
658 expressions = temp;
659
660 temp->prev = current;
661 current = temp;
662 invert = 0;
663 temp = NULL;
664 }
665 }
666 else
667 {
668 /*
669 * Parse -o options
670 */
671
672 for (opt = argv[i] + 1; *opt; opt ++)
673 {
674 switch (*opt)
675 {
676 case '4' :
677 address_family = AF_INET;
678 break;
679
680 case '6' :
681 address_family = AF_INET6;
682 break;
683
684 case 'T' :
685 i ++;
686 if (i >= argc)
687 show_usage();
688
689 bonjour_timeout = atof(argv[i]);
690 break;
691
692 case 'V' :
693 i ++;
694 if (i >= argc)
695 show_usage();
696
697 if (!strcmp(argv[i], "1.1"))
698 ipp_version = 11;
699 else if (!strcmp(argv[i], "2.0"))
700 ipp_version = 20;
701 else if (!strcmp(argv[i], "2.1"))
702 ipp_version = 21;
703 else if (!strcmp(argv[i], "2.2"))
704 ipp_version = 22;
705 else
706 show_usage();
707 break;
708
709 case 'd' :
710 i ++;
711 if (i >= argc)
712 show_usage();
713
714 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL,
715 argv[i], NULL)) == NULL)
716 return (IPPFIND_EXIT_MEMORY);
717 break;
718
719 case 'e' :
720 i ++;
721 if (i >= argc)
722 show_usage();
723
724 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
725 argv + i)) == NULL)
726 return (IPPFIND_EXIT_MEMORY);
727
728 while (i < argc)
729 if (!strcmp(argv[i], ";"))
730 break;
731 else
732 i ++;
733
734 if (i >= argc)
735 show_usage();
736
737 have_output = 1;
738 break;
739
740 case 'l' :
741 i ++;
742 if (i >= argc)
743 show_usage();
744
745 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
746 NULL)) == NULL)
747 return (IPPFIND_EXIT_MEMORY);
748
749 have_output = 1;
750 break;
751
752 case 'n' :
753 i ++;
754 if (i >= argc)
755 show_usage();
756
757 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL,
758 argv[i], NULL)) == NULL)
759 return (IPPFIND_EXIT_MEMORY);
760 break;
761
762 case 'p' :
763 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
764 NULL)) == NULL)
765 return (IPPFIND_EXIT_MEMORY);
766
767 have_output = 1;
768 break;
769
770 case 'q' :
771 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
772 NULL)) == NULL)
773 return (IPPFIND_EXIT_MEMORY);
774
775 have_output = 1;
776 break;
777
778 case 'r' :
779 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
780 NULL)) == NULL)
781 return (IPPFIND_EXIT_MEMORY);
782 break;
783
784 case 's' :
785 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
786 NULL)) == NULL)
787 return (IPPFIND_EXIT_MEMORY);
788
789 have_output = 1;
790 break;
791
792 case 't' :
793 i ++;
794 if (i >= argc)
795 show_usage();
796
797 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i],
798 NULL, NULL)) == NULL)
799 return (IPPFIND_EXIT_MEMORY);
800 break;
801
802 case 'u' :
803 i ++;
804 if (i >= argc)
805 show_usage();
806
807 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL,
808 argv[i], NULL)) == NULL)
809 return (IPPFIND_EXIT_MEMORY);
810 break;
811
812 default :
813 _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."),
814 "ippfind", *opt);
815 show_usage();
816 break;
817 }
818
819 if (temp)
820 {
821 /*
822 * Add new expression...
823 */
824
825 if (current)
826 {
827 temp->parent = parent;
828 current->next = temp;
829 }
830 else if (parent)
831 parent->child = temp;
832 else
833 expressions = temp;
834
835 temp->prev = current;
836 current = temp;
837 invert = 0;
838 temp = NULL;
839 }
840 }
841 }
842 }
843 else if (!strcmp(argv[i], "("))
844 {
845 if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
846 return (IPPFIND_EXIT_MEMORY);
847
848 if (current)
849 {
850 temp->parent = current->parent;
851 current->next = temp;
852 }
853 else
854 expressions = temp;
855
856 temp->prev = current;
857 parent = temp;
858 current = NULL;
859 invert = 0;
860 logic = IPPFIND_OP_AND;
861 }
862 else if (!strcmp(argv[i], ")"))
863 {
864 if (!parent)
865 {
866 _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis."));
867 show_usage();
868 }
869
870 current = parent;
871 parent = current->parent;
872
873 if (!parent)
874 logic = IPPFIND_OP_AND;
875 else
876 logic = parent->op;
877 }
878 else if (!strcmp(argv[i], "!"))
879 {
880 invert = 1;
881 }
882 else
883 {
884 /*
885 * _regtype._tcp[,subtype][.domain]
886 *
887 * OR
888 *
889 * service-name[._regtype._tcp[.domain]]
890 */
891
892 cupsArrayAdd(searches, argv[i]);
893 }
894 }
895
896 if (parent)
897 {
898 _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
899 show_usage();
900 }
901
902 if (cupsArrayCount(searches) == 0)
903 {
904 /*
905 * Add an implicit browse for IPP printers ("_ipp._tcp")...
906 */
907
908 cupsArrayAdd(searches, "_ipp._tcp");
909 }
910
911 if (!have_output)
912 {
913 /*
914 * Add an implicit --print-uri to the end...
915 */
916
917 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL)
918 return (IPPFIND_EXIT_MEMORY);
919
920 if (current)
921 {
922 temp->parent = parent;
923 current->next = temp;
924 }
925 else
926 expressions = temp;
927
928 temp->prev = current;
929 }
930
931 /*
932 * Start up browsing/resolving...
933 */
934
935 #ifdef HAVE_DNSSD
936 if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError)
937 {
938 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
939 dnssd_error_string(err));
940 return (IPPFIND_EXIT_BONJOUR);
941 }
942
943 #elif defined(HAVE_AVAHI)
944 if ((avahi_poll = avahi_simple_poll_new()) == NULL)
945 {
946 _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"),
947 strerror(errno));
948 return (IPPFIND_EXIT_BONJOUR);
949 }
950
951 avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);
952
953 avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll),
954 0, client_callback, avahi_poll, &err);
955 if (!client)
956 {
957 _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"),
958 dnssd_error_string(err));
959 return (IPPFIND_EXIT_BONJOUR);
960 }
961 #endif /* HAVE_DNSSD */
962
963 for (search = (const char *)cupsArrayFirst(searches);
964 search;
965 search = (const char *)cupsArrayNext(searches))
966 {
967 char buf[1024], /* Full name string */
968 *name = NULL, /* Service instance name */
969 *regtype, /* Registration type */
970 *domain; /* Domain, if any */
971
972 strlcpy(buf, search, sizeof(buf));
973 if (buf[0] == '_')
974 {
975 regtype = buf;
976 }
977 else if ((regtype = strstr(buf, "._")) != NULL)
978 {
979 name = buf;
980 *regtype++ = '\0';
981 }
982 else
983 {
984 name = buf;
985 regtype = "_ipp._tcp";
986 }
987
988 for (domain = regtype; *domain; domain ++)
989 if (*domain == '.' && domain[1] != '_')
990 {
991 *domain++ = '\0';
992 break;
993 }
994
995 if (!*domain)
996 domain = NULL;
997
998 if (name)
999 {
1000 /*
1001 * Resolve the given service instance name, regtype, and domain...
1002 */
1003
1004 if (!domain)
1005 domain = "local.";
1006
1007 service = get_service(services, name, regtype, domain);
1008
1009 #ifdef HAVE_DNSSD
1010 service->ref = dnssd_ref;
1011 err = DNSServiceResolve(&(service->ref),
1012 kDNSServiceFlagsShareConnection, 0, name,
1013 regtype, domain, resolve_callback,
1014 service);
1015
1016 #elif defined(HAVE_AVAHI)
1017 service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC,
1018 AVAHI_PROTO_UNSPEC, name,
1019 regtype, domain,
1020 AVAHI_PROTO_UNSPEC, 0,
1021 resolve_callback, service);
1022 if (service->ref)
1023 err = 0;
1024 else
1025 err = avahi_client_get_errno(avahi_client);
1026 #endif /* HAVE_DNSSD */
1027 }
1028 else
1029 {
1030 /*
1031 * Browse for services of the given type...
1032 */
1033
1034 #ifdef HAVE_DNSSD
1035 DNSServiceRef ref; /* Browse reference */
1036
1037 ref = dnssd_ref;
1038 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype,
1039 domain, browse_callback, services);
1040
1041 if (!err)
1042 {
1043 ref = dnssd_ref;
1044 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection,
1045 kDNSServiceInterfaceIndexLocalOnly, regtype,
1046 domain, browse_local_callback, services);
1047 }
1048
1049 #elif defined(HAVE_AVAHI)
1050 if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC,
1051 AVAHI_PROTO_UNSPEC, regtype, domain, 0,
1052 browse_callback, services))
1053 err = 0;
1054 else
1055 err = avahi_client_get_errno(avahi_client);
1056 #endif /* HAVE_DNSSD */
1057 }
1058
1059 if (err)
1060 {
1061 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
1062 dnssd_error_string(err));
1063
1064 if (name)
1065 printf("name=\"%s\"\n", name);
1066
1067 printf("regtype=\"%s\"\n", regtype);
1068
1069 if (domain)
1070 printf("domain=\"%s\"\n", domain);
1071
1072 return (IPPFIND_EXIT_BONJOUR);
1073 }
1074 }
1075
1076 /*
1077 * Process browse/resolve requests...
1078 */
1079
1080 if (bonjour_timeout > 1.0)
1081 endtime = get_time() + bonjour_timeout;
1082 else
1083 endtime = get_time() + 300.0;
1084
1085 while (get_time() < endtime)
1086 {
1087 int process = 0; /* Process services? */
1088
1089 #ifdef HAVE_DNSSD
1090 int fd = DNSServiceRefSockFD(dnssd_ref);
1091 /* File descriptor for DNS-SD */
1092
1093 FD_ZERO(&sinput);
1094 FD_SET(fd, &sinput);
1095
1096 stimeout.tv_sec = 0;
1097 stimeout.tv_usec = 500000;
1098
1099 if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0)
1100 continue;
1101
1102 if (FD_ISSET(fd, &sinput))
1103 {
1104 /*
1105 * Process responses...
1106 */
1107
1108 DNSServiceProcessResult(dnssd_ref);
1109 }
1110 else
1111 {
1112 /*
1113 * Time to process services...
1114 */
1115
1116 process = 1;
1117 }
1118
1119 #elif defined(HAVE_AVAHI)
1120 avahi_got_data = 0;
1121
1122 if (avahi_simple_poll_iterate(avahi_poll, 500) > 0)
1123 {
1124 /*
1125 * We've been told to exit the loop. Perhaps the connection to
1126 * Avahi failed.
1127 */
1128
1129 return (IPPFIND_EXIT_BONJOUR);
1130 }
1131
1132 if (!avahi_got_data)
1133 {
1134 /*
1135 * Time to process services...
1136 */
1137
1138 process = 1;
1139 }
1140 #endif /* HAVE_DNSSD */
1141
1142 if (process)
1143 {
1144 /*
1145 * Process any services that we have found...
1146 */
1147
1148 int active = 0, /* Number of active resolves */
1149 resolved = 0, /* Number of resolved services */
1150 processed = 0; /* Number of processed services */
1151
1152 for (service = (ippfind_srv_t *)cupsArrayFirst(services);
1153 service;
1154 service = (ippfind_srv_t *)cupsArrayNext(services))
1155 {
1156 if (service->is_processed)
1157 processed ++;
1158
1159 if (service->is_resolved)
1160 resolved ++;
1161
1162 if (!service->ref && !service->is_resolved)
1163 {
1164 /*
1165 * Found a service, now resolve it (but limit to 50 active resolves...)
1166 */
1167
1168 if (active < 50)
1169 {
1170 #ifdef HAVE_DNSSD
1171 service->ref = dnssd_ref;
1172 err = DNSServiceResolve(&(service->ref),
1173 kDNSServiceFlagsShareConnection, 0,
1174 service->name, service->regtype,
1175 service->domain, resolve_callback,
1176 service);
1177
1178 #elif defined(HAVE_AVAHI)
1179 service->ref = avahi_service_resolver_new(avahi_client,
1180 AVAHI_IF_UNSPEC,
1181 AVAHI_PROTO_UNSPEC,
1182 service->name,
1183 service->regtype,
1184 service->domain,
1185 AVAHI_PROTO_UNSPEC, 0,
1186 resolve_callback,
1187 service);
1188 if (service->ref)
1189 err = 0;
1190 else
1191 err = avahi_client_get_errno(avahi_client);
1192 #endif /* HAVE_DNSSD */
1193
1194 if (err)
1195 {
1196 _cupsLangPrintf(stderr,
1197 _("ippfind: Unable to browse or resolve: %s"),
1198 dnssd_error_string(err));
1199 return (IPPFIND_EXIT_BONJOUR);
1200 }
1201
1202 active ++;
1203 }
1204 }
1205 else if (service->is_resolved && !service->is_processed)
1206 {
1207 /*
1208 * Resolved, not process this service against the expressions...
1209 */
1210
1211 if (service->ref)
1212 {
1213 #ifdef HAVE_DNSSD
1214 DNSServiceRefDeallocate(service->ref);
1215 #else
1216 avahi_record_browser_free(service->ref);
1217 #endif /* HAVE_DNSSD */
1218
1219 service->ref = NULL;
1220 }
1221
1222 if (!eval_expr(service, expressions))
1223 status = IPPFIND_EXIT_FALSE;
1224
1225 service->is_processed = 1;
1226 }
1227 else if (service->ref)
1228 active ++;
1229 }
1230
1231 /*
1232 * If we have processed all services we have discovered, then we are done.
1233 */
1234
1235 if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0)
1236 break;
1237 }
1238 }
1239
1240 if (bonjour_error)
1241 return (IPPFIND_EXIT_BONJOUR);
1242 else
1243 return (status);
1244 }
1245
1246
1247 #ifdef HAVE_DNSSD
1248 /*
1249 * 'browse_callback()' - Browse devices.
1250 */
1251
1252 static void
1253 browse_callback(
1254 DNSServiceRef sdRef, /* I - Service reference */
1255 DNSServiceFlags flags, /* I - Option flags */
1256 uint32_t interfaceIndex, /* I - Interface number */
1257 DNSServiceErrorType errorCode, /* I - Error, if any */
1258 const char *serviceName, /* I - Name of service/device */
1259 const char *regtype, /* I - Type of service */
1260 const char *replyDomain, /* I - Service domain */
1261 void *context) /* I - Services array */
1262 {
1263 /*
1264 * Only process "add" data...
1265 */
1266
1267 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1268 return;
1269
1270 /*
1271 * Get the device...
1272 */
1273
1274 get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
1275 }
1276
1277
1278 /*
1279 * 'browse_local_callback()' - Browse local devices.
1280 */
1281
1282 static void
1283 browse_local_callback(
1284 DNSServiceRef sdRef, /* I - Service reference */
1285 DNSServiceFlags flags, /* I - Option flags */
1286 uint32_t interfaceIndex, /* I - Interface number */
1287 DNSServiceErrorType errorCode, /* I - Error, if any */
1288 const char *serviceName, /* I - Name of service/device */
1289 const char *regtype, /* I - Type of service */
1290 const char *replyDomain, /* I - Service domain */
1291 void *context) /* I - Services array */
1292 {
1293 ippfind_srv_t *service; /* Service */
1294
1295
1296 /*
1297 * Only process "add" data...
1298 */
1299
1300 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1301 return;
1302
1303 /*
1304 * Get the device...
1305 */
1306
1307 service = get_service((cups_array_t *)context, serviceName, regtype,
1308 replyDomain);
1309 service->is_local = 1;
1310 }
1311 #endif /* HAVE_DNSSD */
1312
1313
1314 #ifdef HAVE_AVAHI
1315 /*
1316 * 'browse_callback()' - Browse devices.
1317 */
1318
1319 static void
1320 browse_callback(
1321 AvahiServiceBrowser *browser, /* I - Browser */
1322 AvahiIfIndex interface, /* I - Interface index (unused) */
1323 AvahiProtocol protocol, /* I - Network protocol (unused) */
1324 AvahiBrowserEvent event, /* I - What happened */
1325 const char *name, /* I - Service name */
1326 const char *type, /* I - Registration type */
1327 const char *domain, /* I - Domain */
1328 AvahiLookupResultFlags flags, /* I - Flags */
1329 void *context) /* I - Services array */
1330 {
1331 AvahiClient *client = avahi_service_browser_get_client(browser);
1332 /* Client information */
1333 ippfind_srv_t *service; /* Service information */
1334
1335
1336 (void)interface;
1337 (void)protocol;
1338 (void)context;
1339
1340 switch (event)
1341 {
1342 case AVAHI_BROWSER_FAILURE:
1343 fprintf(stderr, "DEBUG: browse_callback: %s\n",
1344 avahi_strerror(avahi_client_errno(client)));
1345 bonjour_error = 1;
1346 avahi_simple_poll_quit(simple_poll);
1347 break;
1348
1349 case AVAHI_BROWSER_NEW:
1350 /*
1351 * This object is new on the network. Create a device entry for it if
1352 * it doesn't yet exist.
1353 */
1354
1355 service = get_service((cups_array_t *)context, name, type, domain);
1356
1357 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
1358 service->is_local = 1;
1359 break;
1360
1361 case AVAHI_BROWSER_REMOVE:
1362 case AVAHI_BROWSER_ALL_FOR_NOW:
1363 case AVAHI_BROWSER_CACHE_EXHAUSTED:
1364 break;
1365 }
1366 }
1367
1368
1369 /*
1370 * 'client_callback()' - Avahi client callback function.
1371 */
1372
1373 static void
1374 client_callback(
1375 AvahiClient *client, /* I - Client information (unused) */
1376 AvahiClientState state, /* I - Current state */
1377 void *context) /* I - User data (unused) */
1378 {
1379 (void)client;
1380 (void)context;
1381
1382 /*
1383 * If the connection drops, quit.
1384 */
1385
1386 if (state == AVAHI_CLIENT_FAILURE)
1387 {
1388 fputs("DEBUG: Avahi connection failed.\n", stderr);
1389 bonjour_error = 1;
1390 avahi_simple_poll_quit(avahi_poll);
1391 }
1392 }
1393 #endif /* HAVE_AVAHI */
1394
1395
1396 /*
1397 * 'compare_services()' - Compare two devices.
1398 */
1399
1400 static int /* O - Result of comparison */
1401 compare_services(ippfind_srv_t *a, /* I - First device */
1402 ippfind_srv_t *b) /* I - Second device */
1403 {
1404 return (strcmp(a->name, b->name));
1405 }
1406
1407
1408 /*
1409 * 'dnssd_error_string()' - Return an error string for an error code.
1410 */
1411
1412 static const char * /* O - Error message */
1413 dnssd_error_string(int error) /* I - Error number */
1414 {
1415 # ifdef HAVE_DNSSD
1416 switch (error)
1417 {
1418 case kDNSServiceErr_NoError :
1419 return ("OK.");
1420
1421 default :
1422 case kDNSServiceErr_Unknown :
1423 return ("Unknown error.");
1424
1425 case kDNSServiceErr_NoSuchName :
1426 return ("Service not found.");
1427
1428 case kDNSServiceErr_NoMemory :
1429 return ("Out of memory.");
1430
1431 case kDNSServiceErr_BadParam :
1432 return ("Bad parameter.");
1433
1434 case kDNSServiceErr_BadReference :
1435 return ("Bad service reference.");
1436
1437 case kDNSServiceErr_BadState :
1438 return ("Bad state.");
1439
1440 case kDNSServiceErr_BadFlags :
1441 return ("Bad flags.");
1442
1443 case kDNSServiceErr_Unsupported :
1444 return ("Unsupported.");
1445
1446 case kDNSServiceErr_NotInitialized :
1447 return ("Not initialized.");
1448
1449 case kDNSServiceErr_AlreadyRegistered :
1450 return ("Already registered.");
1451
1452 case kDNSServiceErr_NameConflict :
1453 return ("Name conflict.");
1454
1455 case kDNSServiceErr_Invalid :
1456 return ("Invalid name.");
1457
1458 case kDNSServiceErr_Firewall :
1459 return ("Firewall prevents registration.");
1460
1461 case kDNSServiceErr_Incompatible :
1462 return ("Client library incompatible.");
1463
1464 case kDNSServiceErr_BadInterfaceIndex :
1465 return ("Bad interface index.");
1466
1467 case kDNSServiceErr_Refused :
1468 return ("Server prevents registration.");
1469
1470 case kDNSServiceErr_NoSuchRecord :
1471 return ("Record not found.");
1472
1473 case kDNSServiceErr_NoAuth :
1474 return ("Authentication required.");
1475
1476 case kDNSServiceErr_NoSuchKey :
1477 return ("Encryption key not found.");
1478
1479 case kDNSServiceErr_NATTraversal :
1480 return ("Unable to traverse NAT boundary.");
1481
1482 case kDNSServiceErr_DoubleNAT :
1483 return ("Unable to traverse double-NAT boundary.");
1484
1485 case kDNSServiceErr_BadTime :
1486 return ("Bad system time.");
1487
1488 case kDNSServiceErr_BadSig :
1489 return ("Bad signature.");
1490
1491 case kDNSServiceErr_BadKey :
1492 return ("Bad encryption key.");
1493
1494 case kDNSServiceErr_Transient :
1495 return ("Transient error occurred - please try again.");
1496
1497 case kDNSServiceErr_ServiceNotRunning :
1498 return ("Server not running.");
1499
1500 case kDNSServiceErr_NATPortMappingUnsupported :
1501 return ("NAT doesn't support NAT-PMP or UPnP.");
1502
1503 case kDNSServiceErr_NATPortMappingDisabled :
1504 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1505
1506 case kDNSServiceErr_NoRouter :
1507 return ("No Internet/default router configured.");
1508
1509 case kDNSServiceErr_PollingMode :
1510 return ("Service polling mode error.");
1511
1512 case kDNSServiceErr_Timeout :
1513 return ("Service timeout.");
1514 }
1515
1516 # elif defined(HAVE_AVAHI)
1517 return (avahi_strerror(error));
1518 # endif /* HAVE_DNSSD */
1519 }
1520
1521
1522 /*
1523 * 'eval_expr()' - Evaluate the expressions against the specified service.
1524 *
1525 * Returns 1 for true and 0 for false.
1526 */
1527
1528 static int /* O - Result of evaluation */
1529 eval_expr(ippfind_srv_t *service, /* I - Service */
1530 ippfind_expr_t *expressions) /* I - Expressions */
1531 {
1532 (void)expressions;
1533
1534 puts(service->uri);
1535 return (1);
1536 }
1537
1538
1539 /*
1540 * 'get_service()' - Create or update a device.
1541 */
1542
1543 static ippfind_srv_t * /* O - Service */
1544 get_service(cups_array_t *services, /* I - Service array */
1545 const char *serviceName, /* I - Name of service/device */
1546 const char *regtype, /* I - Type of service */
1547 const char *replyDomain) /* I - Service domain */
1548 {
1549 ippfind_srv_t key, /* Search key */
1550 *service; /* Service */
1551 char fullName[kDNSServiceMaxDomainName];
1552 /* Full name for query */
1553
1554
1555 /*
1556 * See if this is a new device...
1557 */
1558
1559 key.name = (char *)serviceName;
1560 key.regtype = (char *)regtype;
1561
1562 for (service = cupsArrayFind(services, &key);
1563 service;
1564 service = cupsArrayNext(services))
1565 if (_cups_strcasecmp(service->name, key.name))
1566 break;
1567 else if (!strcmp(service->regtype, key.regtype))
1568 return (service);
1569
1570 /*
1571 * Yes, add the service...
1572 */
1573
1574 service = calloc(sizeof(ippfind_srv_t), 1);
1575 service->name = strdup(serviceName);
1576 service->domain = strdup(replyDomain);
1577 service->regtype = strdup(regtype);
1578
1579 cupsArrayAdd(services, service);
1580
1581 /*
1582 * Set the "full name" of this service, which is used for queries and
1583 * resolves...
1584 */
1585
1586 #ifdef HAVE_DNSSD
1587 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
1588 #else /* HAVE_AVAHI */
1589 avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName,
1590 regtype, replyDomain);
1591 #endif /* HAVE_DNSSD */
1592
1593 service->fullName = strdup(fullName);
1594
1595 return (service);
1596 }
1597
1598
1599 /*
1600 * 'get_time()' - Get the current time-of-day in seconds.
1601 */
1602
1603 static double
1604 get_time(void)
1605 {
1606 #ifdef WIN32
1607 struct _timeb curtime; /* Current Windows time */
1608
1609 _ftime(&curtime);
1610
1611 return (curtime.time + 0.001 * curtime.millitm);
1612
1613 #else
1614 struct timeval curtime; /* Current UNIX time */
1615
1616 if (gettimeofday(&curtime, NULL))
1617 return (0.0);
1618 else
1619 return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
1620 #endif /* WIN32 */
1621 }
1622
1623
1624 /*
1625 * 'new_expr()' - Create a new expression.
1626 */
1627
1628 static ippfind_expr_t * /* O - New expression */
1629 new_expr(ippfind_op_t op, /* I - Operation */
1630 int invert, /* I - Invert result? */
1631 const char *key, /* I - TXT key */
1632 const char *regex, /* I - Regular expression */
1633 char **args) /* I - Pointer to argument strings */
1634 {
1635 ippfind_expr_t *temp; /* New expression */
1636
1637
1638 if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL)
1639 return (NULL);
1640
1641 temp->op = op;
1642 temp->invert = invert;
1643 temp->key = (char *)key;
1644
1645 if (regex)
1646 {
1647 int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_EXTENDED);
1648
1649 if (err)
1650 {
1651 char message[256]; /* Error message */
1652
1653 regerror(err, &(temp->re), message, sizeof(message));
1654 _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"),
1655 message);
1656 exit(IPPFIND_EXIT_SYNTAX);
1657 }
1658 }
1659
1660 if (args)
1661 {
1662 int num_args; /* Number of arguments */
1663
1664 for (num_args = 1; args[num_args]; num_args ++)
1665 if (!strcmp(args[num_args], ";"))
1666 break;
1667
1668 temp->args = malloc(num_args * sizeof(char *));
1669 memcpy(temp->args, args, num_args * sizeof(char *));
1670 }
1671
1672 return (temp);
1673 }
1674
1675
1676 #ifdef HAVE_AVAHI
1677 /*
1678 * 'poll_callback()' - Wait for input on the specified file descriptors.
1679 *
1680 * Note: This function is needed because avahi_simple_poll_iterate is broken
1681 * and always uses a timeout of 0 (!) milliseconds.
1682 * (Avahi Ticket #364)
1683 */
1684
1685 static int /* O - Number of file descriptors matching */
1686 poll_callback(
1687 struct pollfd *pollfds, /* I - File descriptors */
1688 unsigned int num_pollfds, /* I - Number of file descriptors */
1689 int timeout, /* I - Timeout in milliseconds (unused) */
1690 void *context) /* I - User data (unused) */
1691 {
1692 int val; /* Return value */
1693
1694
1695 (void)timeout;
1696 (void)context;
1697
1698 val = poll(pollfds, num_pollfds, 500);
1699
1700 if (val < 0)
1701 fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno));
1702 else if (val > 0)
1703 got_data = 1;
1704
1705 return (val);
1706 }
1707 #endif /* HAVE_AVAHI */
1708
1709
1710 /*
1711 * 'resolve_callback()' - Process resolve data.
1712 */
1713
1714 #ifdef HAVE_DNSSD
1715 static void
1716 resolve_callback(
1717 DNSServiceRef sdRef, /* I - Service reference */
1718 DNSServiceFlags flags, /* I - Data flags */
1719 uint32_t interfaceIndex, /* I - Interface */
1720 DNSServiceErrorType errorCode, /* I - Error, if any */
1721 const char *fullName, /* I - Full service name */
1722 const char *hostTarget, /* I - Hostname */
1723 uint16_t port, /* I - Port number (network byte order) */
1724 uint16_t txtLen, /* I - Length of TXT record data */
1725 const unsigned char *txtRecord, /* I - TXT record data */
1726 void *context) /* I - Service */
1727 {
1728 char key[256], /* TXT key value */
1729 *value; /* Value from TXT record */
1730 const unsigned char *txtEnd; /* End of TXT record */
1731 uint8_t valueLen; /* Length of value */
1732 ippfind_srv_t *service = (ippfind_srv_t *)context;
1733 /* Service */
1734
1735
1736 /*
1737 * Only process "add" data...
1738 */
1739
1740 if (errorCode != kDNSServiceErr_NoError)
1741 {
1742 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
1743 dnssd_error_string(errorCode));
1744 bonjour_error = 1;
1745 return;
1746 }
1747
1748 service->is_resolved = 1;
1749 service->host = strdup(hostTarget);
1750 service->port = ntohs(port);
1751
1752 /*
1753 * Loop through the TXT key/value pairs and add them to an array...
1754 */
1755
1756 for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen)
1757 {
1758 /*
1759 * Ignore bogus strings...
1760 */
1761
1762 valueLen = *txtRecord++;
1763
1764 memcpy(key, txtRecord, valueLen);
1765 key[valueLen] = '\0';
1766
1767 if ((value = strchr(key, '=')) == NULL)
1768 continue;
1769
1770 *value++ = '\0';
1771
1772 /*
1773 * Add to array of TXT values...
1774 */
1775
1776 service->num_txt = cupsAddOption(key, value, service->num_txt,
1777 &(service->txt));
1778 }
1779
1780 set_service_uri(service);
1781 }
1782
1783
1784 #elif defined(HAVE_AVAHI)
1785 static void
1786 resolve_callback(
1787 AvahiServiceResolver *resolver, /* I - Resolver */
1788 AvahiIfIndex interface, /* I - Interface */
1789 AvahiProtocol protocol, /* I - Address protocol */
1790 AvahiBrowserEvent event, /* I - Event */
1791 const char *serviceName,/* I - Service name */
1792 const char *regtype, /* I - Registration type */
1793 const char *replyDomain,/* I - Domain name */
1794 const char *hostTarget, /* I - FQDN */
1795 uint16_t port, /* I - Port number */
1796 AvahiStringList *txt, /* I - TXT records */
1797 AvahiLookupResultFlags flags, /* I - Lookup flags */
1798 void *context) /* I - Service */
1799 {
1800 char uri[1024]; /* URI */
1801 key[256], /* TXT key */
1802 *value; /* TXT value */
1803 ippfind_srv_t *service = (ippfind_srv_t *)context;
1804 /* Service */
1805 AvahiStringList *current; /* Current TXT key/value pair */
1806
1807
1808 if (event != AVAHI_RESOLVER_FOUND)
1809 {
1810 bonjour_error = 1;
1811
1812 avahi_service_resolver_free(resolver);
1813 avahi_simple_poll_quit(uribuf->poll);
1814 return;
1815 }
1816
1817 service->is_resolved = 1;
1818 service->host = strdup(hostTarget);
1819 service->port = ntohs(port);
1820
1821 /*
1822 * Loop through the TXT key/value pairs and add them to an array...
1823 */
1824
1825 for (current = txt; current; current = current->next)
1826 {
1827 /*
1828 * Ignore bogus strings...
1829 */
1830
1831 if (current->size > (sizeof(key) - 1))
1832 continue;
1833
1834 memcpy(key, current->text, current->size);
1835 key[current->size] = '\0';
1836
1837 if ((value = strchr(key, '=')) == NULL)
1838 continue;
1839
1840 *value++ = '\0';
1841
1842 /*
1843 * Add to array of TXT values...
1844 */
1845
1846 service->num_txt = cupsAddOption(key, value, service->num_txt,
1847 &(service->txt));
1848 }
1849
1850 set_service_uri(service);
1851 }
1852 #endif /* HAVE_DNSSD */
1853
1854
1855 /*
1856 * 'set_service_uri()' - Set the URI of the service.
1857 */
1858
1859 static void
1860 set_service_uri(ippfind_srv_t *service) /* I - Service */
1861 {
1862 char uri[1024]; /* URI */
1863 const char *path, /* Resource path */
1864 *scheme; /* URI scheme */
1865
1866
1867 if (!strncmp(service->regtype, "_http.", 6))
1868 {
1869 scheme = "http";
1870 path = cupsGetOption("path", service->num_txt, service->txt);
1871 }
1872 else if (!strncmp(service->regtype, "_https.", 7))
1873 {
1874 scheme = "https";
1875 path = cupsGetOption("path", service->num_txt, service->txt);
1876 }
1877 else if (!strncmp(service->regtype, "_ipp.", 5))
1878 {
1879 scheme = "ipp";
1880 path = cupsGetOption("rp", service->num_txt, service->txt);
1881 }
1882 else if (!strncmp(service->regtype, "_ipps.", 6))
1883 {
1884 scheme = "ipps";
1885 path = cupsGetOption("rp", service->num_txt, service->txt);
1886 }
1887 else if (!strncmp(service->regtype, "_printer.", 9))
1888 {
1889 scheme = "lpd";
1890 path = cupsGetOption("rp", service->num_txt, service->txt);
1891 }
1892 else
1893 return;
1894
1895 if (!path || !*path)
1896 path = "/";
1897
1898 if (*path == '/')
1899 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL,
1900 service->host, service->port, path);
1901 else
1902 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL,
1903 service->host, service->port, "/%s", path);
1904
1905 service->uri = strdup(uri);
1906 }
1907
1908
1909 /*
1910 * 'show_usage()' - Show program usage.
1911 */
1912
1913 static void
1914 show_usage(void)
1915 {
1916 _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
1917 "[.domain.] ... [expression]\n"
1918 " ippfind [options] name[.regtype[.domain.]] "
1919 "... [expression]\n"
1920 " ippfind --help\n"
1921 " ippfind --version"));
1922 _cupsLangPuts(stderr, "");
1923 _cupsLangPuts(stderr, _("Options:"));
1924 _cupsLangPuts(stderr, _(" -4 Connect using IPv4."));
1925 _cupsLangPuts(stderr, _(" -6 Connect using IPv6."));
1926 _cupsLangPuts(stderr, _(" -T seconds Set the browse timeout in "
1927 "seconds."));
1928 _cupsLangPuts(stderr, _(" -V version Set default IPP "
1929 "version."));
1930 _cupsLangPuts(stderr, _(" --help Show this help."));
1931 _cupsLangPuts(stderr, _(" --version Show program version."));
1932 _cupsLangPuts(stderr, "");
1933 _cupsLangPuts(stderr, _("Expressions:"));
1934 _cupsLangPuts(stderr, _(" -d regex Match domain to regular expression."));
1935 _cupsLangPuts(stderr, _(" -e utility [argument ...] ;\n"
1936 " Execute program if true."));
1937 _cupsLangPuts(stderr, _(" -l List attributes."));
1938 _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression."));
1939 _cupsLangPuts(stderr, _(" -p Print URI if true."));
1940 _cupsLangPuts(stderr, _(" -q Quietly report match via exit code."));
1941 _cupsLangPuts(stderr, _(" -r True if service is remote."));
1942 _cupsLangPuts(stderr, _(" -s Print service name if true."));
1943 _cupsLangPuts(stderr, _(" -t key True if the TXT record contains the key."));
1944 _cupsLangPuts(stderr, _(" -u regex Match URI to regular expression."));
1945 _cupsLangPuts(stderr, _(" --domain regex Match domain to regular expression."));
1946 _cupsLangPuts(stderr, _(" --exec utility [argument ...] ;\n"
1947 " Execute program if true."));
1948 _cupsLangPuts(stderr, _(" --ls List attributes."));
1949 _cupsLangPuts(stderr, _(" --local True if service is local."));
1950 _cupsLangPuts(stderr, _(" --name regex Match service name to regular expression."));
1951 _cupsLangPuts(stderr, _(" --path regex Match resource path to regular expression."));
1952 _cupsLangPuts(stderr, _(" --print Print URI if true."));
1953 _cupsLangPuts(stderr, _(" --print-name Print service name if true."));
1954 _cupsLangPuts(stderr, _(" --quiet Quietly report match via exit code."));
1955 _cupsLangPuts(stderr, _(" --remote True if service is remote."));
1956 _cupsLangPuts(stderr, _(" --txt key True if the TXT record contains the key."));
1957 _cupsLangPuts(stderr, _(" --txt-* regex Match TXT record key to regular expression."));
1958 _cupsLangPuts(stderr, _(" --uri regex Match URI to regular expression."));
1959 _cupsLangPuts(stderr, "");
1960 _cupsLangPuts(stderr, _("Modifiers:"));
1961 _cupsLangPuts(stderr, _(" ( expressions ) Group expressions."));
1962 _cupsLangPuts(stderr, _(" ! expression Unary NOT of expression."));
1963 _cupsLangPuts(stderr, _(" --not expression Unary NOT of expression."));
1964 _cupsLangPuts(stderr, _(" --false Always false."));
1965 _cupsLangPuts(stderr, _(" --true Always true."));
1966 _cupsLangPuts(stderr, _(" expression expression Logical AND."));
1967 _cupsLangPuts(stderr, _(" expression --and expression\n"
1968 " Logical AND."));
1969 _cupsLangPuts(stderr, _(" expression --or expression\n"
1970 " Logical OR."));
1971 _cupsLangPuts(stderr, "");
1972 _cupsLangPuts(stderr, _("Substitutions:"));
1973 _cupsLangPuts(stderr, _(" {} URI"));
1974 _cupsLangPuts(stderr, _(" {service_domain} Domain name"));
1975 _cupsLangPuts(stderr, _(" {service_hostname} Fully-qualified domain name"));
1976 _cupsLangPuts(stderr, _(" {service_name} Service instance name"));
1977 _cupsLangPuts(stderr, _(" {service_port} Port number"));
1978 _cupsLangPuts(stderr, _(" {service_regtype} DNS-SD registration type"));
1979 _cupsLangPuts(stderr, _(" {service_scheme} URI scheme"));
1980 _cupsLangPuts(stderr, _(" {service_uri} URI"));
1981 _cupsLangPuts(stderr, _(" {txt_*} Value of TXT record key"));
1982 _cupsLangPuts(stderr, "");
1983 _cupsLangPuts(stderr, _("Environment Variables:"));
1984 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_DOMAIN Domain name"));
1985 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_HOSTNAME\n"
1986 " Fully-qualified domain name"));
1987 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_NAME Service instance name"));
1988 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_PORT Port number"));
1989 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
1990 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_SCHEME URI scheme"));
1991 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_URI URI"));
1992 _cupsLangPuts(stderr, _(" IPPFIND_TXT_* Value of TXT record key"));
1993
1994 exit(IPPFIND_EXIT_TRUE);
1995 }
1996
1997
1998 /*
1999 * 'show_version()' - Show program version.
2000 */
2001
2002 static void
2003 show_version(void)
2004 {
2005 _cupsLangPuts(stderr, CUPS_SVERSION);
2006
2007 exit(IPPFIND_EXIT_TRUE);
2008 }
2009
2010
2011 /*
2012 * End of "$Id$".
2013 */