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