]> git.ipfire.org Git - thirdparty/cups.git/blob - test/ippfind.c
Fix unused store - actually use the count we saved.
[thirdparty/cups.git] / test / ippfind.c
1 /*
2 * Utility to find IPP printers via Bonjour/DNS-SD and optionally run
3 * commands such as IPP and Bonjour conformance tests. This tool is
4 * inspired by the UNIX "find" command, thus its name.
5 *
6 * Copyright 2008-2017 by Apple Inc.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 */
11
12 /*
13 * Include necessary headers.
14 */
15
16 #define _CUPS_NO_DEPRECATED
17 #include <cups/cups-private.h>
18 #ifdef WIN32
19 # include <process.h>
20 # include <sys/timeb.h>
21 #else
22 # include <sys/wait.h>
23 #endif /* WIN32 */
24 #include <regex.h>
25 #ifdef HAVE_DNSSD
26 # include <dns_sd.h>
27 #elif defined(HAVE_AVAHI)
28 # include <avahi-client/client.h>
29 # include <avahi-client/lookup.h>
30 # include <avahi-common/simple-watch.h>
31 # include <avahi-common/domain.h>
32 # include <avahi-common/error.h>
33 # include <avahi-common/malloc.h>
34 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
35 #endif /* HAVE_DNSSD */
36
37 #ifndef WIN32
38 extern char **environ; /* Process environment variables */
39 #endif /* !WIN32 */
40
41
42 /*
43 * Structures...
44 */
45
46 typedef enum ippfind_exit_e /* Exit codes */
47 {
48 IPPFIND_EXIT_TRUE = 0, /* OK and result is true */
49 IPPFIND_EXIT_FALSE, /* OK but result is false*/
50 IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */
51 IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */
52 IPPFIND_EXIT_MEMORY /* Out of memory */
53 } ippfind_exit_t;
54
55 typedef enum ippfind_op_e /* Operations for expressions */
56 {
57 /* "Evaluation" operations */
58 IPPFIND_OP_NONE, /* No operation */
59 IPPFIND_OP_AND, /* Logical AND of all children */
60 IPPFIND_OP_OR, /* Logical OR of all children */
61 IPPFIND_OP_TRUE, /* Always true */
62 IPPFIND_OP_FALSE, /* Always false */
63 IPPFIND_OP_IS_LOCAL, /* Is a local service */
64 IPPFIND_OP_IS_REMOTE, /* Is a remote service */
65 IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */
66 IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */
67 IPPFIND_OP_HOST_REGEX, /* Hostname matches regular expression */
68 IPPFIND_OP_PORT_RANGE, /* Port matches range */
69 IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */
70 IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */
71 IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */
72 IPPFIND_OP_URI_REGEX, /* URI matches regular expression */
73
74 /* "Output" operations */
75 IPPFIND_OP_EXEC, /* Execute when true */
76 IPPFIND_OP_LIST, /* List when true */
77 IPPFIND_OP_PRINT_NAME, /* Print URI when true */
78 IPPFIND_OP_PRINT_URI, /* Print name when true */
79 IPPFIND_OP_QUIET /* No output when true */
80 } ippfind_op_t;
81
82 typedef struct ippfind_expr_s /* Expression */
83 {
84 struct ippfind_expr_s
85 *prev, /* Previous expression */
86 *next, /* Next expression */
87 *parent, /* Parent expressions */
88 *child; /* Child expressions */
89 ippfind_op_t op; /* Operation code (see above) */
90 int invert; /* Invert the result */
91 char *key; /* TXT record key */
92 regex_t re; /* Regular expression for matching */
93 int range[2]; /* Port number range */
94 int num_args; /* Number of arguments for exec */
95 char **args; /* Arguments for exec */
96 } ippfind_expr_t;
97
98 typedef struct ippfind_srv_s /* Service information */
99 {
100 #ifdef HAVE_DNSSD
101 DNSServiceRef ref; /* Service reference for query */
102 #elif defined(HAVE_AVAHI)
103 AvahiServiceResolver *ref; /* Resolver */
104 #endif /* HAVE_DNSSD */
105 char *name, /* Service name */
106 *domain, /* Domain name */
107 *regtype, /* Registration type */
108 *fullName, /* Full name */
109 *host, /* Hostname */
110 *resource, /* Resource path */
111 *uri; /* URI */
112 int num_txt; /* Number of TXT record keys */
113 cups_option_t *txt; /* TXT record keys */
114 int port, /* Port number */
115 is_local, /* Is a local service? */
116 is_processed, /* Did we process the service? */
117 is_resolved; /* Got the resolve data? */
118 } ippfind_srv_t;
119
120
121 /*
122 * Local globals...
123 */
124
125 #ifdef HAVE_DNSSD
126 static DNSServiceRef dnssd_ref; /* Master service reference */
127 #elif defined(HAVE_AVAHI)
128 static AvahiClient *avahi_client = NULL;/* Client information */
129 static int avahi_got_data = 0; /* Got data from poll? */
130 static AvahiSimplePoll *avahi_poll = NULL;
131 /* Poll information */
132 #endif /* HAVE_DNSSD */
133
134 static int address_family = AF_UNSPEC;
135 /* Address family for LIST */
136 static int bonjour_error = 0; /* Error browsing/resolving? */
137 static double bonjour_timeout = 1.0; /* Timeout in seconds */
138 static int ipp_version = 20; /* IPP version for LIST */
139
140
141 /*
142 * Local functions...
143 */
144
145 #ifdef HAVE_DNSSD
146 static void DNSSD_API browse_callback(DNSServiceRef sdRef,
147 DNSServiceFlags flags,
148 uint32_t interfaceIndex,
149 DNSServiceErrorType errorCode,
150 const char *serviceName,
151 const char *regtype,
152 const char *replyDomain, void *context)
153 __attribute__((nonnull(1,5,6,7,8)));
154 static void DNSSD_API browse_local_callback(DNSServiceRef sdRef,
155 DNSServiceFlags flags,
156 uint32_t interfaceIndex,
157 DNSServiceErrorType errorCode,
158 const char *serviceName,
159 const char *regtype,
160 const char *replyDomain,
161 void *context)
162 __attribute__((nonnull(1,5,6,7,8)));
163 #elif defined(HAVE_AVAHI)
164 static void browse_callback(AvahiServiceBrowser *browser,
165 AvahiIfIndex interface,
166 AvahiProtocol protocol,
167 AvahiBrowserEvent event,
168 const char *serviceName,
169 const char *regtype,
170 const char *replyDomain,
171 AvahiLookupResultFlags flags,
172 void *context);
173 static void client_callback(AvahiClient *client,
174 AvahiClientState state,
175 void *context);
176 #endif /* HAVE_AVAHI */
177
178 static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b);
179 static const char *dnssd_error_string(int error);
180 static int eval_expr(ippfind_srv_t *service,
181 ippfind_expr_t *expressions);
182 static int exec_program(ippfind_srv_t *service, int num_args,
183 char **args);
184 static ippfind_srv_t *get_service(cups_array_t *services,
185 const char *serviceName,
186 const char *regtype,
187 const char *replyDomain)
188 __attribute__((nonnull(1,2,3,4)));
189 static double get_time(void);
190 static int list_service(ippfind_srv_t *service);
191 static ippfind_expr_t *new_expr(ippfind_op_t op, int invert,
192 const char *value, const char *regex,
193 char **args);
194 #ifdef HAVE_DNSSD
195 static void DNSSD_API resolve_callback(DNSServiceRef sdRef,
196 DNSServiceFlags flags,
197 uint32_t interfaceIndex,
198 DNSServiceErrorType errorCode,
199 const char *fullName,
200 const char *hostTarget, uint16_t port,
201 uint16_t txtLen,
202 const unsigned char *txtRecord,
203 void *context)
204 __attribute__((nonnull(1,5,6,9, 10)));
205 #elif defined(HAVE_AVAHI)
206 static int poll_callback(struct pollfd *pollfds,
207 unsigned int num_pollfds, int timeout,
208 void *context);
209 static void resolve_callback(AvahiServiceResolver *res,
210 AvahiIfIndex interface,
211 AvahiProtocol protocol,
212 AvahiResolverEvent event,
213 const char *serviceName,
214 const char *regtype,
215 const char *replyDomain,
216 const char *host_name,
217 const AvahiAddress *address,
218 uint16_t port,
219 AvahiStringList *txt,
220 AvahiLookupResultFlags flags,
221 void *context);
222 #endif /* HAVE_DNSSD */
223 static void set_service_uri(ippfind_srv_t *service);
224 static void show_usage(void) __attribute__((noreturn));
225 static void show_version(void) __attribute__((noreturn));
226
227
228 /*
229 * 'main()' - Browse for printers.
230 */
231
232 int /* O - Exit status */
233 main(int argc, /* I - Number of command-line args */
234 char *argv[]) /* I - Command-line arguments */
235 {
236 int i, /* Looping var */
237 have_output = 0,/* Have output expression */
238 status = IPPFIND_EXIT_FALSE;
239 /* Exit status */
240 const char *opt, /* Option character */
241 *search; /* Current browse/resolve string */
242 cups_array_t *searches; /* Things to browse/resolve */
243 cups_array_t *services; /* Service array */
244 ippfind_srv_t *service; /* Current service */
245 ippfind_expr_t *expressions = NULL,
246 /* Expression tree */
247 *temp = NULL, /* New expression */
248 *parent = NULL, /* Parent expression */
249 *current = NULL,/* Current expression */
250 *parens[100]; /* Markers for parenthesis */
251 int num_parens = 0; /* Number of parenthesis */
252 ippfind_op_t logic = IPPFIND_OP_AND;
253 /* Logic for next expression */
254 int invert = 0; /* Invert expression? */
255 int err; /* DNS-SD error */
256 #ifdef HAVE_DNSSD
257 fd_set sinput; /* Input set for select() */
258 struct timeval stimeout; /* Timeout for select() */
259 #endif /* HAVE_DNSSD */
260 double endtime; /* End time */
261 static const char * const ops[] = /* Node operation names */
262 {
263 "NONE",
264 "AND",
265 "OR",
266 "TRUE",
267 "FALSE",
268 "IS_LOCAL",
269 "IS_REMOTE",
270 "DOMAIN_REGEX",
271 "NAME_REGEX",
272 "HOST_REGEX",
273 "PORT_RANGE",
274 "PATH_REGEX",
275 "TXT_EXISTS",
276 "TXT_REGEX",
277 "URI_REGEX",
278 "EXEC",
279 "LIST",
280 "PRINT_NAME",
281 "PRINT_URI",
282 "QUIET"
283 };
284
285
286 /*
287 * Initialize the locale...
288 */
289
290 _cupsSetLocale(argv);
291
292 /*
293 * Create arrays to track services and things we want to browse/resolve...
294 */
295
296 searches = cupsArrayNew(NULL, NULL);
297 services = cupsArrayNew((cups_array_func_t)compare_services, NULL);
298
299 /*
300 * Parse command-line...
301 */
302
303 if (getenv("IPPFIND_DEBUG"))
304 for (i = 1; i < argc; i ++)
305 fprintf(stderr, "argv[%d]=\"%s\"\n", i, argv[i]);
306
307 for (i = 1; i < argc; i ++)
308 {
309 if (argv[i][0] == '-')
310 {
311 if (argv[i][1] == '-')
312 {
313 /*
314 * Parse --option options...
315 */
316
317 if (!strcmp(argv[i], "--and"))
318 {
319 if (logic == IPPFIND_OP_OR)
320 {
321 _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or."));
322 show_usage();
323 }
324
325 if (!current)
326 {
327 _cupsLangPuts(stderr,
328 _("ippfind: Missing expression before \"--and\"."));
329 show_usage();
330 }
331
332 temp = NULL;
333 }
334 else if (!strcmp(argv[i], "--domain"))
335 {
336 i ++;
337 if (i >= argc)
338 {
339 _cupsLangPrintf(stderr,
340 _("ippfind: Missing regular expression after %s."),
341 "--domain");
342 show_usage();
343 }
344
345 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i],
346 NULL)) == NULL)
347 return (IPPFIND_EXIT_MEMORY);
348 }
349 else if (!strcmp(argv[i], "--exec"))
350 {
351 i ++;
352 if (i >= argc)
353 {
354 _cupsLangPrintf(stderr, _("ippfind: Expected program after %s."),
355 "--exec");
356 show_usage();
357 }
358
359 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
360 argv + i)) == NULL)
361 return (IPPFIND_EXIT_MEMORY);
362
363 while (i < argc)
364 if (!strcmp(argv[i], ";"))
365 break;
366 else
367 i ++;
368
369 if (i >= argc)
370 {
371 _cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."),
372 "--exec");
373 show_usage();
374 }
375
376 have_output = 1;
377 }
378 else if (!strcmp(argv[i], "--false"))
379 {
380 if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL,
381 NULL)) == NULL)
382 return (IPPFIND_EXIT_MEMORY);
383 }
384 else if (!strcmp(argv[i], "--help"))
385 {
386 show_usage();
387 }
388 else if (!strcmp(argv[i], "--host"))
389 {
390 i ++;
391 if (i >= argc)
392 {
393 _cupsLangPrintf(stderr,
394 _("ippfind: Missing regular expression after %s."),
395 "--host");
396 show_usage();
397 }
398
399 if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i],
400 NULL)) == NULL)
401 return (IPPFIND_EXIT_MEMORY);
402 }
403 else if (!strcmp(argv[i], "--ls"))
404 {
405 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
406 NULL)) == NULL)
407 return (IPPFIND_EXIT_MEMORY);
408
409 have_output = 1;
410 }
411 else if (!strcmp(argv[i], "--local"))
412 {
413 if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL,
414 NULL)) == NULL)
415 return (IPPFIND_EXIT_MEMORY);
416 }
417 else if (!strcmp(argv[i], "--name"))
418 {
419 i ++;
420 if (i >= argc)
421 {
422 _cupsLangPrintf(stderr,
423 _("ippfind: Missing regular expression after %s."),
424 "--name");
425 show_usage();
426 }
427
428 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i],
429 NULL)) == NULL)
430 return (IPPFIND_EXIT_MEMORY);
431 }
432 else if (!strcmp(argv[i], "--not"))
433 {
434 invert = 1;
435 }
436 else if (!strcmp(argv[i], "--or"))
437 {
438 if (!current)
439 {
440 _cupsLangPuts(stderr,
441 _("ippfind: Missing expression before \"--or\"."));
442 show_usage();
443 }
444
445 logic = IPPFIND_OP_OR;
446
447 if (parent && parent->op == IPPFIND_OP_OR)
448 {
449 /*
450 * Already setup to do "foo --or bar --or baz"...
451 */
452
453 temp = NULL;
454 }
455 else if (!current->prev && parent)
456 {
457 /*
458 * Change parent node into an OR node...
459 */
460
461 parent->op = IPPFIND_OP_OR;
462 temp = NULL;
463 }
464 else if (!current->prev)
465 {
466 /*
467 * Need to group "current" in a new OR node...
468 */
469
470 if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
471 NULL)) == NULL)
472 return (IPPFIND_EXIT_MEMORY);
473
474 temp->parent = parent;
475 temp->child = current;
476 current->parent = temp;
477
478 if (parent)
479 parent->child = temp;
480 else
481 expressions = temp;
482
483 parent = temp;
484 temp = NULL;
485 }
486 else
487 {
488 /*
489 * Need to group previous expressions in an AND node, and then
490 * put that in an OR node...
491 */
492
493 if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
494 NULL)) == NULL)
495 return (IPPFIND_EXIT_MEMORY);
496
497 while (current->prev)
498 {
499 current->parent = temp;
500 current = current->prev;
501 }
502
503 current->parent = temp;
504 temp->child = current;
505 current = temp;
506
507 if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
508 NULL)) == NULL)
509 return (IPPFIND_EXIT_MEMORY);
510
511 temp->parent = parent;
512 current->parent = temp;
513
514 if (parent)
515 parent->child = temp;
516 else
517 expressions = temp;
518
519 parent = temp;
520 temp = NULL;
521 }
522 }
523 else if (!strcmp(argv[i], "--path"))
524 {
525 i ++;
526 if (i >= argc)
527 {
528 _cupsLangPrintf(stderr,
529 _("ippfind: Missing regular expression after %s."),
530 "--path");
531 show_usage();
532 }
533
534 if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i],
535 NULL)) == NULL)
536 return (IPPFIND_EXIT_MEMORY);
537 }
538 else if (!strcmp(argv[i], "--port"))
539 {
540 i ++;
541 if (i >= argc)
542 {
543 _cupsLangPrintf(stderr,
544 _("ippfind: Expected port range after %s."),
545 "--port");
546 show_usage();
547 }
548
549 if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL,
550 NULL)) == NULL)
551 return (IPPFIND_EXIT_MEMORY);
552 }
553 else if (!strcmp(argv[i], "--print"))
554 {
555 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
556 NULL)) == NULL)
557 return (IPPFIND_EXIT_MEMORY);
558
559 have_output = 1;
560 }
561 else if (!strcmp(argv[i], "--print-name"))
562 {
563 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
564 NULL)) == NULL)
565 return (IPPFIND_EXIT_MEMORY);
566
567 have_output = 1;
568 }
569 else if (!strcmp(argv[i], "--quiet"))
570 {
571 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
572 NULL)) == NULL)
573 return (IPPFIND_EXIT_MEMORY);
574
575 have_output = 1;
576 }
577 else if (!strcmp(argv[i], "--remote"))
578 {
579 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
580 NULL)) == NULL)
581 return (IPPFIND_EXIT_MEMORY);
582 }
583 else if (!strcmp(argv[i], "--true"))
584 {
585 if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i],
586 NULL)) == NULL)
587 return (IPPFIND_EXIT_MEMORY);
588 }
589 else if (!strcmp(argv[i], "--txt"))
590 {
591 i ++;
592 if (i >= argc)
593 {
594 _cupsLangPrintf(stderr, _("ippfind: Expected key name after %s."),
595 "--txt");
596 show_usage();
597 }
598
599 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL,
600 NULL)) == NULL)
601 return (IPPFIND_EXIT_MEMORY);
602 }
603 else if (!strncmp(argv[i], "--txt-", 6))
604 {
605 const char *key = argv[i] + 6;/* TXT key */
606
607 i ++;
608 if (i >= argc)
609 {
610 _cupsLangPrintf(stderr,
611 _("ippfind: Missing regular expression after %s."),
612 argv[i - 1]);
613 show_usage();
614 }
615
616 if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i],
617 NULL)) == NULL)
618 return (IPPFIND_EXIT_MEMORY);
619 }
620 else if (!strcmp(argv[i], "--uri"))
621 {
622 i ++;
623 if (i >= argc)
624 {
625 _cupsLangPrintf(stderr,
626 _("ippfind: Missing regular expression after %s."),
627 "--uri");
628 show_usage();
629 }
630
631 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i],
632 NULL)) == NULL)
633 return (IPPFIND_EXIT_MEMORY);
634 }
635 else if (!strcmp(argv[i], "--version"))
636 {
637 show_version();
638 }
639 else
640 {
641 _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."),
642 "ippfind", argv[i]);
643 show_usage();
644 }
645
646 if (temp)
647 {
648 /*
649 * Add new expression...
650 */
651
652 if (logic == IPPFIND_OP_AND &&
653 current && current->prev &&
654 parent && parent->op != IPPFIND_OP_AND)
655 {
656 /*
657 * Need to re-group "current" in a new AND node...
658 */
659
660 ippfind_expr_t *tempand; /* Temporary AND node */
661
662 if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
663 NULL)) == NULL)
664 return (IPPFIND_EXIT_MEMORY);
665
666 /*
667 * Replace "current" with new AND node at the end of this list...
668 */
669
670 current->prev->next = tempand;
671 tempand->prev = current->prev;
672 tempand->parent = parent;
673
674 /*
675 * Add "current to the new AND node...
676 */
677
678 tempand->child = current;
679 current->parent = tempand;
680 current->prev = NULL;
681 parent = tempand;
682 }
683
684 /*
685 * Add the new node at current level...
686 */
687
688 temp->parent = parent;
689 temp->prev = current;
690
691 if (current)
692 current->next = temp;
693 else if (parent)
694 parent->child = temp;
695 else
696 expressions = temp;
697
698 current = temp;
699 invert = 0;
700 logic = IPPFIND_OP_AND;
701 temp = NULL;
702 }
703 }
704 else
705 {
706 /*
707 * Parse -o options
708 */
709
710 for (opt = argv[i] + 1; *opt; opt ++)
711 {
712 switch (*opt)
713 {
714 case '4' :
715 address_family = AF_INET;
716 break;
717
718 case '6' :
719 address_family = AF_INET6;
720 break;
721
722 case 'P' :
723 i ++;
724 if (i >= argc)
725 {
726 _cupsLangPrintf(stderr,
727 _("ippfind: Expected port range after %s."),
728 "-P");
729 show_usage();
730 }
731
732 if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i],
733 NULL, NULL)) == NULL)
734 return (IPPFIND_EXIT_MEMORY);
735 break;
736
737 case 'T' :
738 i ++;
739 if (i >= argc)
740 {
741 _cupsLangPrintf(stderr,
742 _("%s: Missing timeout for \"-T\"."),
743 "ippfind");
744 show_usage();
745 }
746
747 bonjour_timeout = atof(argv[i]);
748 break;
749
750 case 'V' :
751 i ++;
752 if (i >= argc)
753 {
754 _cupsLangPrintf(stderr,
755 _("%s: Missing version for \"-V\"."),
756 "ippfind");
757 show_usage();
758 }
759
760 if (!strcmp(argv[i], "1.1"))
761 ipp_version = 11;
762 else if (!strcmp(argv[i], "2.0"))
763 ipp_version = 20;
764 else if (!strcmp(argv[i], "2.1"))
765 ipp_version = 21;
766 else if (!strcmp(argv[i], "2.2"))
767 ipp_version = 22;
768 else
769 {
770 _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."),
771 "ippfind", argv[i]);
772 show_usage();
773 }
774 break;
775
776 case 'd' :
777 i ++;
778 if (i >= argc)
779 {
780 _cupsLangPrintf(stderr,
781 _("ippfind: Missing regular expression after "
782 "%s."), "-d");
783 show_usage();
784 }
785
786 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL,
787 argv[i], NULL)) == NULL)
788 return (IPPFIND_EXIT_MEMORY);
789 break;
790
791 case 'h' :
792 i ++;
793 if (i >= argc)
794 {
795 _cupsLangPrintf(stderr,
796 _("ippfind: Missing regular expression after "
797 "%s."), "-h");
798 show_usage();
799 }
800
801 if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL,
802 argv[i], NULL)) == NULL)
803 return (IPPFIND_EXIT_MEMORY);
804 break;
805
806 case 'l' :
807 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
808 NULL)) == NULL)
809 return (IPPFIND_EXIT_MEMORY);
810
811 have_output = 1;
812 break;
813
814 case 'n' :
815 i ++;
816 if (i >= argc)
817 {
818 _cupsLangPrintf(stderr,
819 _("ippfind: Missing regular expression after "
820 "%s."), "-n");
821 show_usage();
822 }
823
824 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL,
825 argv[i], NULL)) == NULL)
826 return (IPPFIND_EXIT_MEMORY);
827 break;
828
829 case 'p' :
830 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
831 NULL)) == NULL)
832 return (IPPFIND_EXIT_MEMORY);
833
834 have_output = 1;
835 break;
836
837 case 'q' :
838 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
839 NULL)) == NULL)
840 return (IPPFIND_EXIT_MEMORY);
841
842 have_output = 1;
843 break;
844
845 case 'r' :
846 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
847 NULL)) == NULL)
848 return (IPPFIND_EXIT_MEMORY);
849 break;
850
851 case 's' :
852 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
853 NULL)) == NULL)
854 return (IPPFIND_EXIT_MEMORY);
855
856 have_output = 1;
857 break;
858
859 case 't' :
860 i ++;
861 if (i >= argc)
862 {
863 _cupsLangPrintf(stderr,
864 _("ippfind: Missing key name after %s."),
865 "-t");
866 show_usage();
867 }
868
869 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i],
870 NULL, NULL)) == NULL)
871 return (IPPFIND_EXIT_MEMORY);
872 break;
873
874 case 'u' :
875 i ++;
876 if (i >= argc)
877 {
878 _cupsLangPrintf(stderr,
879 _("ippfind: Missing regular expression after "
880 "%s."), "-u");
881 show_usage();
882 }
883
884 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL,
885 argv[i], NULL)) == NULL)
886 return (IPPFIND_EXIT_MEMORY);
887 break;
888
889 case 'x' :
890 i ++;
891 if (i >= argc)
892 {
893 _cupsLangPrintf(stderr,
894 _("ippfind: Missing program after %s."),
895 "-x");
896 show_usage();
897 }
898
899 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
900 argv + i)) == NULL)
901 return (IPPFIND_EXIT_MEMORY);
902
903 while (i < argc)
904 if (!strcmp(argv[i], ";"))
905 break;
906 else
907 i ++;
908
909 if (i >= argc)
910 {
911 _cupsLangPrintf(stderr,
912 _("ippfind: Missing semi-colon after %s."),
913 "-x");
914 show_usage();
915 }
916
917 have_output = 1;
918 break;
919
920 default :
921 _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."),
922 "ippfind", *opt);
923 show_usage();
924 }
925
926 if (temp)
927 {
928 /*
929 * Add new expression...
930 */
931
932 if (logic == IPPFIND_OP_AND &&
933 current && current->prev &&
934 parent && parent->op != IPPFIND_OP_AND)
935 {
936 /*
937 * Need to re-group "current" in a new AND node...
938 */
939
940 ippfind_expr_t *tempand; /* Temporary AND node */
941
942 if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
943 NULL)) == NULL)
944 return (IPPFIND_EXIT_MEMORY);
945
946 /*
947 * Replace "current" with new AND node at the end of this list...
948 */
949
950 current->prev->next = tempand;
951 tempand->prev = current->prev;
952 tempand->parent = parent;
953
954 /*
955 * Add "current to the new AND node...
956 */
957
958 tempand->child = current;
959 current->parent = tempand;
960 current->prev = NULL;
961 parent = tempand;
962 }
963
964 /*
965 * Add the new node at current level...
966 */
967
968 temp->parent = parent;
969 temp->prev = current;
970
971 if (current)
972 current->next = temp;
973 else if (parent)
974 parent->child = temp;
975 else
976 expressions = temp;
977
978 current = temp;
979 invert = 0;
980 logic = IPPFIND_OP_AND;
981 temp = NULL;
982 }
983 }
984 }
985 }
986 else if (!strcmp(argv[i], "("))
987 {
988 if (num_parens >= 100)
989 {
990 _cupsLangPuts(stderr, _("ippfind: Too many parenthesis."));
991 show_usage();
992 }
993
994 if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
995 return (IPPFIND_EXIT_MEMORY);
996
997 parens[num_parens++] = temp;
998
999 if (current)
1000 {
1001 temp->parent = current->parent;
1002 current->next = temp;
1003 temp->prev = current;
1004 }
1005 else
1006 expressions = temp;
1007
1008 parent = temp;
1009 current = NULL;
1010 invert = 0;
1011 logic = IPPFIND_OP_AND;
1012 }
1013 else if (!strcmp(argv[i], ")"))
1014 {
1015 if (num_parens <= 0)
1016 {
1017 _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis."));
1018 show_usage();
1019 }
1020
1021 current = parens[--num_parens];
1022 parent = current->parent;
1023 invert = 0;
1024 logic = IPPFIND_OP_AND;
1025 }
1026 else if (!strcmp(argv[i], "!"))
1027 {
1028 invert = 1;
1029 }
1030 else
1031 {
1032 /*
1033 * _regtype._tcp[,subtype][.domain]
1034 *
1035 * OR
1036 *
1037 * service-name[._regtype._tcp[.domain]]
1038 */
1039
1040 cupsArrayAdd(searches, argv[i]);
1041 }
1042 }
1043
1044 if (num_parens > 0)
1045 {
1046 _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
1047 show_usage();
1048 }
1049
1050 if (!have_output)
1051 {
1052 /*
1053 * Add an implicit --print-uri to the end...
1054 */
1055
1056 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL)
1057 return (IPPFIND_EXIT_MEMORY);
1058
1059 if (current)
1060 {
1061 while (current->parent)
1062 current = current->parent;
1063
1064 current->next = temp;
1065 temp->prev = current;
1066 }
1067 else
1068 expressions = temp;
1069 }
1070
1071 if (cupsArrayCount(searches) == 0)
1072 {
1073 /*
1074 * Add an implicit browse for IPP printers ("_ipp._tcp")...
1075 */
1076
1077 cupsArrayAdd(searches, "_ipp._tcp");
1078 }
1079
1080 if (getenv("IPPFIND_DEBUG"))
1081 {
1082 int indent = 4; /* Indentation */
1083
1084 puts("Expression tree:");
1085 current = expressions;
1086 while (current)
1087 {
1088 /*
1089 * Print the current node...
1090 */
1091
1092 printf("%*s%s%s\n", indent, "", current->invert ? "!" : "",
1093 ops[current->op]);
1094
1095 /*
1096 * Advance to the next node...
1097 */
1098
1099 if (current->child)
1100 {
1101 current = current->child;
1102 indent += 4;
1103 }
1104 else if (current->next)
1105 current = current->next;
1106 else if (current->parent)
1107 {
1108 while (current->parent)
1109 {
1110 indent -= 4;
1111 current = current->parent;
1112 if (current->next)
1113 break;
1114 }
1115
1116 current = current->next;
1117 }
1118 else
1119 current = NULL;
1120 }
1121
1122 puts("\nSearch items:");
1123 for (search = (const char *)cupsArrayFirst(searches);
1124 search;
1125 search = (const char *)cupsArrayNext(searches))
1126 printf(" %s\n", search);
1127 }
1128
1129 /*
1130 * Start up browsing/resolving...
1131 */
1132
1133 #ifdef HAVE_DNSSD
1134 if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError)
1135 {
1136 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
1137 dnssd_error_string(err));
1138 return (IPPFIND_EXIT_BONJOUR);
1139 }
1140
1141 #elif defined(HAVE_AVAHI)
1142 if ((avahi_poll = avahi_simple_poll_new()) == NULL)
1143 {
1144 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
1145 strerror(errno));
1146 return (IPPFIND_EXIT_BONJOUR);
1147 }
1148
1149 avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);
1150
1151 avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll),
1152 0, client_callback, avahi_poll, &err);
1153 if (!avahi_client)
1154 {
1155 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
1156 dnssd_error_string(err));
1157 return (IPPFIND_EXIT_BONJOUR);
1158 }
1159 #endif /* HAVE_DNSSD */
1160
1161 for (search = (const char *)cupsArrayFirst(searches);
1162 search;
1163 search = (const char *)cupsArrayNext(searches))
1164 {
1165 char buf[1024], /* Full name string */
1166 *name = NULL, /* Service instance name */
1167 *regtype, /* Registration type */
1168 *domain; /* Domain, if any */
1169
1170 strlcpy(buf, search, sizeof(buf));
1171 if ((regtype = strstr(buf, "._")) != NULL)
1172 {
1173 if (strcmp(regtype, "._tcp"))
1174 {
1175 /*
1176 * "something._protocol._tcp" -> search for something with the given
1177 * protocol...
1178 */
1179
1180 name = buf;
1181 *regtype++ = '\0';
1182 }
1183 else
1184 {
1185 /*
1186 * "_protocol._tcp" -> search for everything with the given protocol...
1187 */
1188
1189 /* name = NULL; */
1190 regtype = buf;
1191 }
1192 }
1193 else
1194 {
1195 /*
1196 * "something" -> search for something with IPP protocol...
1197 */
1198
1199 name = buf;
1200 regtype = "_ipp._tcp";
1201 }
1202
1203 for (domain = regtype; *domain; domain ++)
1204 {
1205 if (*domain == '.' && domain[1] != '_')
1206 {
1207 *domain++ = '\0';
1208 break;
1209 }
1210 }
1211
1212 if (!*domain)
1213 domain = NULL;
1214
1215 if (name)
1216 {
1217 /*
1218 * Resolve the given service instance name, regtype, and domain...
1219 */
1220
1221 if (!domain)
1222 domain = "local.";
1223
1224 service = get_service(services, name, regtype, domain);
1225
1226 #ifdef HAVE_DNSSD
1227 service->ref = dnssd_ref;
1228 err = DNSServiceResolve(&(service->ref),
1229 kDNSServiceFlagsShareConnection, 0, name,
1230 regtype, domain, resolve_callback,
1231 service);
1232
1233 #elif defined(HAVE_AVAHI)
1234 service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC,
1235 AVAHI_PROTO_UNSPEC, name,
1236 regtype, domain,
1237 AVAHI_PROTO_UNSPEC, 0,
1238 resolve_callback, service);
1239 if (service->ref)
1240 err = 0;
1241 else
1242 err = avahi_client_errno(avahi_client);
1243 #endif /* HAVE_DNSSD */
1244 }
1245 else
1246 {
1247 /*
1248 * Browse for services of the given type...
1249 */
1250
1251 #ifdef HAVE_DNSSD
1252 DNSServiceRef ref; /* Browse reference */
1253
1254 ref = dnssd_ref;
1255 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype,
1256 domain, browse_callback, services);
1257
1258 if (!err)
1259 {
1260 ref = dnssd_ref;
1261 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection,
1262 kDNSServiceInterfaceIndexLocalOnly, regtype,
1263 domain, browse_local_callback, services);
1264 }
1265
1266 #elif defined(HAVE_AVAHI)
1267 if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC,
1268 AVAHI_PROTO_UNSPEC, regtype, domain, 0,
1269 browse_callback, services))
1270 err = 0;
1271 else
1272 err = avahi_client_errno(avahi_client);
1273 #endif /* HAVE_DNSSD */
1274 }
1275
1276 if (err)
1277 {
1278 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
1279 dnssd_error_string(err));
1280
1281 return (IPPFIND_EXIT_BONJOUR);
1282 }
1283 }
1284
1285 /*
1286 * Process browse/resolve requests...
1287 */
1288
1289 if (bonjour_timeout > 1.0)
1290 endtime = get_time() + bonjour_timeout;
1291 else
1292 endtime = get_time() + 300.0;
1293
1294 while (get_time() < endtime)
1295 {
1296 int process = 0; /* Process services? */
1297
1298 #ifdef HAVE_DNSSD
1299 int fd = DNSServiceRefSockFD(dnssd_ref);
1300 /* File descriptor for DNS-SD */
1301
1302 FD_ZERO(&sinput);
1303 FD_SET(fd, &sinput);
1304
1305 stimeout.tv_sec = 0;
1306 stimeout.tv_usec = 500000;
1307
1308 if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0)
1309 continue;
1310
1311 if (FD_ISSET(fd, &sinput))
1312 {
1313 /*
1314 * Process responses...
1315 */
1316
1317 DNSServiceProcessResult(dnssd_ref);
1318 }
1319 else
1320 {
1321 /*
1322 * Time to process services...
1323 */
1324
1325 process = 1;
1326 }
1327
1328 #elif defined(HAVE_AVAHI)
1329 avahi_got_data = 0;
1330
1331 if (avahi_simple_poll_iterate(avahi_poll, 500) > 0)
1332 {
1333 /*
1334 * We've been told to exit the loop. Perhaps the connection to
1335 * Avahi failed.
1336 */
1337
1338 return (IPPFIND_EXIT_BONJOUR);
1339 }
1340
1341 if (!avahi_got_data)
1342 {
1343 /*
1344 * Time to process services...
1345 */
1346
1347 process = 1;
1348 }
1349 #endif /* HAVE_DNSSD */
1350
1351 if (process)
1352 {
1353 /*
1354 * Process any services that we have found...
1355 */
1356
1357 int active = 0, /* Number of active resolves */
1358 resolved = 0, /* Number of resolved services */
1359 processed = 0; /* Number of processed services */
1360
1361 for (service = (ippfind_srv_t *)cupsArrayFirst(services);
1362 service;
1363 service = (ippfind_srv_t *)cupsArrayNext(services))
1364 {
1365 if (service->is_processed)
1366 processed ++;
1367
1368 if (service->is_resolved)
1369 resolved ++;
1370
1371 if (!service->ref && !service->is_resolved)
1372 {
1373 /*
1374 * Found a service, now resolve it (but limit to 50 active resolves...)
1375 */
1376
1377 if (active < 50)
1378 {
1379 #ifdef HAVE_DNSSD
1380 service->ref = dnssd_ref;
1381 err = DNSServiceResolve(&(service->ref),
1382 kDNSServiceFlagsShareConnection, 0,
1383 service->name, service->regtype,
1384 service->domain, resolve_callback,
1385 service);
1386
1387 #elif defined(HAVE_AVAHI)
1388 service->ref = avahi_service_resolver_new(avahi_client,
1389 AVAHI_IF_UNSPEC,
1390 AVAHI_PROTO_UNSPEC,
1391 service->name,
1392 service->regtype,
1393 service->domain,
1394 AVAHI_PROTO_UNSPEC, 0,
1395 resolve_callback,
1396 service);
1397 if (service->ref)
1398 err = 0;
1399 else
1400 err = avahi_client_errno(avahi_client);
1401 #endif /* HAVE_DNSSD */
1402
1403 if (err)
1404 {
1405 _cupsLangPrintf(stderr,
1406 _("ippfind: Unable to browse or resolve: %s"),
1407 dnssd_error_string(err));
1408 return (IPPFIND_EXIT_BONJOUR);
1409 }
1410
1411 active ++;
1412 }
1413 }
1414 else if (service->is_resolved && !service->is_processed)
1415 {
1416 /*
1417 * Resolved, not process this service against the expressions...
1418 */
1419
1420 if (service->ref)
1421 {
1422 #ifdef HAVE_DNSSD
1423 DNSServiceRefDeallocate(service->ref);
1424 #else
1425 avahi_service_resolver_free(service->ref);
1426 #endif /* HAVE_DNSSD */
1427
1428 service->ref = NULL;
1429 }
1430
1431 if (eval_expr(service, expressions))
1432 status = IPPFIND_EXIT_TRUE;
1433
1434 service->is_processed = 1;
1435 }
1436 else if (service->ref)
1437 active ++;
1438 }
1439
1440 /*
1441 * If we have processed all services we have discovered, then we are done.
1442 */
1443
1444 if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0)
1445 break;
1446 }
1447 }
1448
1449 if (bonjour_error)
1450 return (IPPFIND_EXIT_BONJOUR);
1451 else
1452 return (status);
1453 }
1454
1455
1456 #ifdef HAVE_DNSSD
1457 /*
1458 * 'browse_callback()' - Browse devices.
1459 */
1460
1461 static void DNSSD_API
1462 browse_callback(
1463 DNSServiceRef sdRef, /* I - Service reference */
1464 DNSServiceFlags flags, /* I - Option flags */
1465 uint32_t interfaceIndex, /* I - Interface number */
1466 DNSServiceErrorType errorCode, /* I - Error, if any */
1467 const char *serviceName, /* I - Name of service/device */
1468 const char *regtype, /* I - Type of service */
1469 const char *replyDomain, /* I - Service domain */
1470 void *context) /* I - Services array */
1471 {
1472 /*
1473 * Only process "add" data...
1474 */
1475
1476 (void)sdRef;
1477 (void)interfaceIndex;
1478
1479 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1480 return;
1481
1482 /*
1483 * Get the device...
1484 */
1485
1486 get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
1487 }
1488
1489
1490 /*
1491 * 'browse_local_callback()' - Browse local devices.
1492 */
1493
1494 static void DNSSD_API
1495 browse_local_callback(
1496 DNSServiceRef sdRef, /* I - Service reference */
1497 DNSServiceFlags flags, /* I - Option flags */
1498 uint32_t interfaceIndex, /* I - Interface number */
1499 DNSServiceErrorType errorCode, /* I - Error, if any */
1500 const char *serviceName, /* I - Name of service/device */
1501 const char *regtype, /* I - Type of service */
1502 const char *replyDomain, /* I - Service domain */
1503 void *context) /* I - Services array */
1504 {
1505 ippfind_srv_t *service; /* Service */
1506
1507
1508 /*
1509 * Only process "add" data...
1510 */
1511
1512 (void)sdRef;
1513 (void)interfaceIndex;
1514
1515 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1516 return;
1517
1518 /*
1519 * Get the device...
1520 */
1521
1522 service = get_service((cups_array_t *)context, serviceName, regtype,
1523 replyDomain);
1524 service->is_local = 1;
1525 }
1526 #endif /* HAVE_DNSSD */
1527
1528
1529 #ifdef HAVE_AVAHI
1530 /*
1531 * 'browse_callback()' - Browse devices.
1532 */
1533
1534 static void
1535 browse_callback(
1536 AvahiServiceBrowser *browser, /* I - Browser */
1537 AvahiIfIndex interface, /* I - Interface index (unused) */
1538 AvahiProtocol protocol, /* I - Network protocol (unused) */
1539 AvahiBrowserEvent event, /* I - What happened */
1540 const char *name, /* I - Service name */
1541 const char *type, /* I - Registration type */
1542 const char *domain, /* I - Domain */
1543 AvahiLookupResultFlags flags, /* I - Flags */
1544 void *context) /* I - Services array */
1545 {
1546 AvahiClient *client = avahi_service_browser_get_client(browser);
1547 /* Client information */
1548 ippfind_srv_t *service; /* Service information */
1549
1550
1551 (void)interface;
1552 (void)protocol;
1553 (void)context;
1554
1555 switch (event)
1556 {
1557 case AVAHI_BROWSER_FAILURE:
1558 fprintf(stderr, "DEBUG: browse_callback: %s\n",
1559 avahi_strerror(avahi_client_errno(client)));
1560 bonjour_error = 1;
1561 avahi_simple_poll_quit(avahi_poll);
1562 break;
1563
1564 case AVAHI_BROWSER_NEW:
1565 /*
1566 * This object is new on the network. Create a device entry for it if
1567 * it doesn't yet exist.
1568 */
1569
1570 service = get_service((cups_array_t *)context, name, type, domain);
1571
1572 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
1573 service->is_local = 1;
1574 break;
1575
1576 case AVAHI_BROWSER_REMOVE:
1577 case AVAHI_BROWSER_ALL_FOR_NOW:
1578 case AVAHI_BROWSER_CACHE_EXHAUSTED:
1579 break;
1580 }
1581 }
1582
1583
1584 /*
1585 * 'client_callback()' - Avahi client callback function.
1586 */
1587
1588 static void
1589 client_callback(
1590 AvahiClient *client, /* I - Client information (unused) */
1591 AvahiClientState state, /* I - Current state */
1592 void *context) /* I - User data (unused) */
1593 {
1594 (void)client;
1595 (void)context;
1596
1597 /*
1598 * If the connection drops, quit.
1599 */
1600
1601 if (state == AVAHI_CLIENT_FAILURE)
1602 {
1603 fputs("DEBUG: Avahi connection failed.\n", stderr);
1604 bonjour_error = 1;
1605 avahi_simple_poll_quit(avahi_poll);
1606 }
1607 }
1608 #endif /* HAVE_AVAHI */
1609
1610
1611 /*
1612 * 'compare_services()' - Compare two devices.
1613 */
1614
1615 static int /* O - Result of comparison */
1616 compare_services(ippfind_srv_t *a, /* I - First device */
1617 ippfind_srv_t *b) /* I - Second device */
1618 {
1619 return (strcmp(a->name, b->name));
1620 }
1621
1622
1623 /*
1624 * 'dnssd_error_string()' - Return an error string for an error code.
1625 */
1626
1627 static const char * /* O - Error message */
1628 dnssd_error_string(int error) /* I - Error number */
1629 {
1630 # ifdef HAVE_DNSSD
1631 switch (error)
1632 {
1633 case kDNSServiceErr_NoError :
1634 return ("OK.");
1635
1636 default :
1637 case kDNSServiceErr_Unknown :
1638 return ("Unknown error.");
1639
1640 case kDNSServiceErr_NoSuchName :
1641 return ("Service not found.");
1642
1643 case kDNSServiceErr_NoMemory :
1644 return ("Out of memory.");
1645
1646 case kDNSServiceErr_BadParam :
1647 return ("Bad parameter.");
1648
1649 case kDNSServiceErr_BadReference :
1650 return ("Bad service reference.");
1651
1652 case kDNSServiceErr_BadState :
1653 return ("Bad state.");
1654
1655 case kDNSServiceErr_BadFlags :
1656 return ("Bad flags.");
1657
1658 case kDNSServiceErr_Unsupported :
1659 return ("Unsupported.");
1660
1661 case kDNSServiceErr_NotInitialized :
1662 return ("Not initialized.");
1663
1664 case kDNSServiceErr_AlreadyRegistered :
1665 return ("Already registered.");
1666
1667 case kDNSServiceErr_NameConflict :
1668 return ("Name conflict.");
1669
1670 case kDNSServiceErr_Invalid :
1671 return ("Invalid name.");
1672
1673 case kDNSServiceErr_Firewall :
1674 return ("Firewall prevents registration.");
1675
1676 case kDNSServiceErr_Incompatible :
1677 return ("Client library incompatible.");
1678
1679 case kDNSServiceErr_BadInterfaceIndex :
1680 return ("Bad interface index.");
1681
1682 case kDNSServiceErr_Refused :
1683 return ("Server prevents registration.");
1684
1685 case kDNSServiceErr_NoSuchRecord :
1686 return ("Record not found.");
1687
1688 case kDNSServiceErr_NoAuth :
1689 return ("Authentication required.");
1690
1691 case kDNSServiceErr_NoSuchKey :
1692 return ("Encryption key not found.");
1693
1694 case kDNSServiceErr_NATTraversal :
1695 return ("Unable to traverse NAT boundary.");
1696
1697 case kDNSServiceErr_DoubleNAT :
1698 return ("Unable to traverse double-NAT boundary.");
1699
1700 case kDNSServiceErr_BadTime :
1701 return ("Bad system time.");
1702
1703 case kDNSServiceErr_BadSig :
1704 return ("Bad signature.");
1705
1706 case kDNSServiceErr_BadKey :
1707 return ("Bad encryption key.");
1708
1709 case kDNSServiceErr_Transient :
1710 return ("Transient error occurred - please try again.");
1711
1712 case kDNSServiceErr_ServiceNotRunning :
1713 return ("Server not running.");
1714
1715 case kDNSServiceErr_NATPortMappingUnsupported :
1716 return ("NAT doesn't support NAT-PMP or UPnP.");
1717
1718 case kDNSServiceErr_NATPortMappingDisabled :
1719 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1720
1721 case kDNSServiceErr_NoRouter :
1722 return ("No Internet/default router configured.");
1723
1724 case kDNSServiceErr_PollingMode :
1725 return ("Service polling mode error.");
1726
1727 #ifndef WIN32
1728 case kDNSServiceErr_Timeout :
1729 return ("Service timeout.");
1730 #endif /* !WIN32 */
1731 }
1732
1733 # elif defined(HAVE_AVAHI)
1734 return (avahi_strerror(error));
1735 # endif /* HAVE_DNSSD */
1736 }
1737
1738
1739 /*
1740 * 'eval_expr()' - Evaluate the expressions against the specified service.
1741 *
1742 * Returns 1 for true and 0 for false.
1743 */
1744
1745 static int /* O - Result of evaluation */
1746 eval_expr(ippfind_srv_t *service, /* I - Service */
1747 ippfind_expr_t *expressions) /* I - Expressions */
1748 {
1749 int logic, /* Logical operation */
1750 result; /* Result of current expression */
1751 ippfind_expr_t *expression; /* Current expression */
1752 const char *val; /* TXT value */
1753
1754 /*
1755 * Loop through the expressions...
1756 */
1757
1758 if (expressions && expressions->parent)
1759 logic = expressions->parent->op;
1760 else
1761 logic = IPPFIND_OP_AND;
1762
1763 for (expression = expressions; expression; expression = expression->next)
1764 {
1765 switch (expression->op)
1766 {
1767 default :
1768 case IPPFIND_OP_AND :
1769 case IPPFIND_OP_OR :
1770 if (expression->child)
1771 result = eval_expr(service, expression->child);
1772 else
1773 result = expression->op == IPPFIND_OP_AND;
1774 break;
1775 case IPPFIND_OP_TRUE :
1776 result = 1;
1777 break;
1778 case IPPFIND_OP_FALSE :
1779 result = 0;
1780 break;
1781 case IPPFIND_OP_IS_LOCAL :
1782 result = service->is_local;
1783 break;
1784 case IPPFIND_OP_IS_REMOTE :
1785 result = !service->is_local;
1786 break;
1787 case IPPFIND_OP_DOMAIN_REGEX :
1788 result = !regexec(&(expression->re), service->domain, 0, NULL, 0);
1789 break;
1790 case IPPFIND_OP_NAME_REGEX :
1791 result = !regexec(&(expression->re), service->name, 0, NULL, 0);
1792 break;
1793 case IPPFIND_OP_HOST_REGEX :
1794 result = !regexec(&(expression->re), service->host, 0, NULL, 0);
1795 break;
1796 case IPPFIND_OP_PORT_RANGE :
1797 result = service->port >= expression->range[0] &&
1798 service->port <= expression->range[1];
1799 break;
1800 case IPPFIND_OP_PATH_REGEX :
1801 result = !regexec(&(expression->re), service->resource, 0, NULL, 0);
1802 break;
1803 case IPPFIND_OP_TXT_EXISTS :
1804 result = cupsGetOption(expression->key, service->num_txt,
1805 service->txt) != NULL;
1806 break;
1807 case IPPFIND_OP_TXT_REGEX :
1808 val = cupsGetOption(expression->key, service->num_txt,
1809 service->txt);
1810 if (val)
1811 result = !regexec(&(expression->re), val, 0, NULL, 0);
1812 else
1813 result = 0;
1814
1815 if (getenv("IPPFIND_DEBUG"))
1816 printf("TXT_REGEX of \"%s\": %d\n", val, result);
1817 break;
1818 case IPPFIND_OP_URI_REGEX :
1819 result = !regexec(&(expression->re), service->uri, 0, NULL, 0);
1820 break;
1821 case IPPFIND_OP_EXEC :
1822 result = exec_program(service, expression->num_args,
1823 expression->args);
1824 break;
1825 case IPPFIND_OP_LIST :
1826 result = list_service(service);
1827 break;
1828 case IPPFIND_OP_PRINT_NAME :
1829 _cupsLangPuts(stdout, service->name);
1830 result = 1;
1831 break;
1832 case IPPFIND_OP_PRINT_URI :
1833 _cupsLangPuts(stdout, service->uri);
1834 result = 1;
1835 break;
1836 case IPPFIND_OP_QUIET :
1837 result = 1;
1838 break;
1839 }
1840
1841 if (expression->invert)
1842 result = !result;
1843
1844 if (logic == IPPFIND_OP_AND && !result)
1845 return (0);
1846 else if (logic == IPPFIND_OP_OR && result)
1847 return (1);
1848 }
1849
1850 return (logic == IPPFIND_OP_AND);
1851 }
1852
1853
1854 /*
1855 * 'exec_program()' - Execute a program for a service.
1856 */
1857
1858 static int /* O - 1 if program terminated
1859 successfully, 0 otherwise. */
1860 exec_program(ippfind_srv_t *service, /* I - Service */
1861 int num_args, /* I - Number of command-line args */
1862 char **args) /* I - Command-line arguments */
1863 {
1864 char **myargv, /* Command-line arguments */
1865 **myenvp, /* Environment variables */
1866 *ptr, /* Pointer into variable */
1867 domain[1024], /* IPPFIND_SERVICE_DOMAIN */
1868 hostname[1024], /* IPPFIND_SERVICE_HOSTNAME */
1869 name[256], /* IPPFIND_SERVICE_NAME */
1870 port[32], /* IPPFIND_SERVICE_PORT */
1871 regtype[256], /* IPPFIND_SERVICE_REGTYPE */
1872 scheme[128], /* IPPFIND_SERVICE_SCHEME */
1873 uri[1024], /* IPPFIND_SERVICE_URI */
1874 txt[100][256]; /* IPPFIND_TXT_foo */
1875 int i, /* Looping var */
1876 myenvc, /* Number of environment variables */
1877 status; /* Exit status of program */
1878 #ifndef WIN32
1879 char program[1024]; /* Program to execute */
1880 int pid; /* Process ID */
1881 #endif /* !WIN32 */
1882
1883
1884 /*
1885 * Environment variables...
1886 */
1887
1888 snprintf(domain, sizeof(domain), "IPPFIND_SERVICE_DOMAIN=%s",
1889 service->domain);
1890 snprintf(hostname, sizeof(hostname), "IPPFIND_SERVICE_HOSTNAME=%s",
1891 service->host);
1892 snprintf(name, sizeof(name), "IPPFIND_SERVICE_NAME=%s", service->name);
1893 snprintf(port, sizeof(port), "IPPFIND_SERVICE_PORT=%d", service->port);
1894 snprintf(regtype, sizeof(regtype), "IPPFIND_SERVICE_REGTYPE=%s",
1895 service->regtype);
1896 snprintf(scheme, sizeof(scheme), "IPPFIND_SERVICE_SCHEME=%s",
1897 !strncmp(service->regtype, "_http._tcp", 10) ? "http" :
1898 !strncmp(service->regtype, "_https._tcp", 11) ? "https" :
1899 !strncmp(service->regtype, "_ipp._tcp", 9) ? "ipp" :
1900 !strncmp(service->regtype, "_ipps._tcp", 10) ? "ipps" : "lpd");
1901 snprintf(uri, sizeof(uri), "IPPFIND_SERVICE_URI=%s", service->uri);
1902 for (i = 0; i < service->num_txt && i < 100; i ++)
1903 {
1904 snprintf(txt[i], sizeof(txt[i]), "IPPFIND_TXT_%s=%s", service->txt[i].name,
1905 service->txt[i].value);
1906 for (ptr = txt[i] + 12; *ptr && *ptr != '='; ptr ++)
1907 *ptr = (char)_cups_toupper(*ptr);
1908 }
1909
1910 for (i = 0, myenvc = 7 + service->num_txt; environ[i]; i ++)
1911 if (strncmp(environ[i], "IPPFIND_", 8))
1912 myenvc ++;
1913
1914 if ((myenvp = calloc(sizeof(char *), (size_t)(myenvc + 1))) == NULL)
1915 {
1916 _cupsLangPuts(stderr, _("ippfind: Out of memory."));
1917 exit(IPPFIND_EXIT_MEMORY);
1918 }
1919
1920 for (i = 0, myenvc = 0; environ[i]; i ++)
1921 if (strncmp(environ[i], "IPPFIND_", 8))
1922 myenvp[myenvc++] = environ[i];
1923
1924 myenvp[myenvc++] = domain;
1925 myenvp[myenvc++] = hostname;
1926 myenvp[myenvc++] = name;
1927 myenvp[myenvc++] = port;
1928 myenvp[myenvc++] = regtype;
1929 myenvp[myenvc++] = scheme;
1930 myenvp[myenvc++] = uri;
1931
1932 for (i = 0; i < service->num_txt && i < 100; i ++)
1933 myenvp[myenvc++] = txt[i];
1934
1935 /*
1936 * Allocate and copy command-line arguments...
1937 */
1938
1939 if ((myargv = calloc(sizeof(char *), (size_t)(num_args + 1))) == NULL)
1940 {
1941 _cupsLangPuts(stderr, _("ippfind: Out of memory."));
1942 exit(IPPFIND_EXIT_MEMORY);
1943 }
1944
1945 for (i = 0; i < num_args; i ++)
1946 {
1947 if (strchr(args[i], '{'))
1948 {
1949 char temp[2048], /* Temporary string */
1950 *tptr, /* Pointer into temporary string */
1951 keyword[256], /* {keyword} */
1952 *kptr; /* Pointer into keyword */
1953
1954 for (ptr = args[i], tptr = temp; *ptr; ptr ++)
1955 {
1956 if (*ptr == '{')
1957 {
1958 /*
1959 * Do a {var} substitution...
1960 */
1961
1962 for (kptr = keyword, ptr ++; *ptr && *ptr != '}'; ptr ++)
1963 if (kptr < (keyword + sizeof(keyword) - 1))
1964 *kptr++ = *ptr;
1965
1966 if (*ptr != '}')
1967 {
1968 _cupsLangPuts(stderr,
1969 _("ippfind: Missing close brace in substitution."));
1970 exit(IPPFIND_EXIT_SYNTAX);
1971 }
1972
1973 *kptr = '\0';
1974 if (!keyword[0] || !strcmp(keyword, "service_uri"))
1975 strlcpy(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp));
1976 else if (!strcmp(keyword, "service_domain"))
1977 strlcpy(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp));
1978 else if (!strcmp(keyword, "service_hostname"))
1979 strlcpy(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp));
1980 else if (!strcmp(keyword, "service_name"))
1981 strlcpy(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp));
1982 else if (!strcmp(keyword, "service_path"))
1983 strlcpy(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp));
1984 else if (!strcmp(keyword, "service_port"))
1985 strlcpy(tptr, port + 21, sizeof(temp) - (size_t)(tptr - temp));
1986 else if (!strcmp(keyword, "service_scheme"))
1987 strlcpy(tptr, scheme + 22, sizeof(temp) - (size_t)(tptr - temp));
1988 else if (!strncmp(keyword, "txt_", 4))
1989 {
1990 const char *val = cupsGetOption(keyword + 4, service->num_txt, service->txt);
1991 if (val)
1992 strlcpy(tptr, val, sizeof(temp) - (size_t)(tptr - temp));
1993 else
1994 *tptr = '\0';
1995 }
1996 else
1997 {
1998 _cupsLangPrintf(stderr, _("ippfind: Unknown variable \"{%s}\"."),
1999 keyword);
2000 exit(IPPFIND_EXIT_SYNTAX);
2001 }
2002
2003 tptr += strlen(tptr);
2004 }
2005 else if (tptr < (temp + sizeof(temp) - 1))
2006 *tptr++ = *ptr;
2007 }
2008
2009 *tptr = '\0';
2010 myargv[i] = strdup(temp);
2011 }
2012 else
2013 myargv[i] = strdup(args[i]);
2014 }
2015
2016 #ifdef WIN32
2017 if (getenv("IPPFIND_DEBUG"))
2018 {
2019 printf("\nProgram:\n %s\n", args[0]);
2020 puts("\nArguments:");
2021 for (i = 0; i < num_args; i ++)
2022 printf(" %s\n", myargv[i]);
2023 puts("\nEnvironment:");
2024 for (i = 0; i < myenvc; i ++)
2025 printf(" %s\n", myenvp[i]);
2026 }
2027
2028 status = _spawnvpe(_P_WAIT, args[0], myargv, myenvp);
2029
2030 #else
2031 /*
2032 * Execute the program...
2033 */
2034
2035 if (strchr(args[0], '/') && !access(args[0], X_OK))
2036 strlcpy(program, args[0], sizeof(program));
2037 else if (!cupsFileFind(args[0], getenv("PATH"), 1, program, sizeof(program)))
2038 {
2039 _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
2040 args[0], strerror(ENOENT));
2041 exit(IPPFIND_EXIT_SYNTAX);
2042 }
2043
2044 if (getenv("IPPFIND_DEBUG"))
2045 {
2046 printf("\nProgram:\n %s\n", program);
2047 puts("\nArguments:");
2048 for (i = 0; i < num_args; i ++)
2049 printf(" %s\n", myargv[i]);
2050 puts("\nEnvironment:");
2051 for (i = 0; i < myenvc; i ++)
2052 printf(" %s\n", myenvp[i]);
2053 }
2054
2055 if ((pid = fork()) == 0)
2056 {
2057 /*
2058 * Child comes here...
2059 */
2060
2061 execve(program, myargv, myenvp);
2062 exit(1);
2063 }
2064 else if (pid < 0)
2065 {
2066 _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
2067 args[0], strerror(errno));
2068 exit(IPPFIND_EXIT_SYNTAX);
2069 }
2070 else
2071 {
2072 /*
2073 * Wait for it to complete...
2074 */
2075
2076 while (wait(&status) != pid)
2077 ;
2078 }
2079 #endif /* WIN32 */
2080
2081 /*
2082 * Free memory...
2083 */
2084
2085 for (i = 0; i < num_args; i ++)
2086 free(myargv[i]);
2087
2088 free(myargv);
2089 free(myenvp);
2090
2091 /*
2092 * Return whether the program succeeded or crashed...
2093 */
2094
2095 if (getenv("IPPFIND_DEBUG"))
2096 {
2097 #ifdef WIN32
2098 printf("Exit Status: %d\n", status);
2099 #else
2100 if (WIFEXITED(status))
2101 printf("Exit Status: %d\n", WEXITSTATUS(status));
2102 else
2103 printf("Terminating Signal: %d\n", WTERMSIG(status));
2104 #endif /* WIN32 */
2105 }
2106
2107 return (status == 0);
2108 }
2109
2110
2111 /*
2112 * 'get_service()' - Create or update a device.
2113 */
2114
2115 static ippfind_srv_t * /* O - Service */
2116 get_service(cups_array_t *services, /* I - Service array */
2117 const char *serviceName, /* I - Name of service/device */
2118 const char *regtype, /* I - Type of service */
2119 const char *replyDomain) /* I - Service domain */
2120 {
2121 ippfind_srv_t key, /* Search key */
2122 *service; /* Service */
2123 char fullName[kDNSServiceMaxDomainName];
2124 /* Full name for query */
2125
2126
2127 /*
2128 * See if this is a new device...
2129 */
2130
2131 key.name = (char *)serviceName;
2132 key.regtype = (char *)regtype;
2133
2134 for (service = cupsArrayFind(services, &key);
2135 service;
2136 service = cupsArrayNext(services))
2137 if (_cups_strcasecmp(service->name, key.name))
2138 break;
2139 else if (!strcmp(service->regtype, key.regtype))
2140 return (service);
2141
2142 /*
2143 * Yes, add the service...
2144 */
2145
2146 service = calloc(sizeof(ippfind_srv_t), 1);
2147 service->name = strdup(serviceName);
2148 service->domain = strdup(replyDomain);
2149 service->regtype = strdup(regtype);
2150
2151 cupsArrayAdd(services, service);
2152
2153 /*
2154 * Set the "full name" of this service, which is used for queries and
2155 * resolves...
2156 */
2157
2158 #ifdef HAVE_DNSSD
2159 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
2160 #else /* HAVE_AVAHI */
2161 avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName,
2162 regtype, replyDomain);
2163 #endif /* HAVE_DNSSD */
2164
2165 service->fullName = strdup(fullName);
2166
2167 return (service);
2168 }
2169
2170
2171 /*
2172 * 'get_time()' - Get the current time-of-day in seconds.
2173 */
2174
2175 static double
2176 get_time(void)
2177 {
2178 #ifdef WIN32
2179 struct _timeb curtime; /* Current Windows time */
2180
2181 _ftime(&curtime);
2182
2183 return (curtime.time + 0.001 * curtime.millitm);
2184
2185 #else
2186 struct timeval curtime; /* Current UNIX time */
2187
2188 if (gettimeofday(&curtime, NULL))
2189 return (0.0);
2190 else
2191 return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
2192 #endif /* WIN32 */
2193 }
2194
2195
2196 /*
2197 * 'list_service()' - List the contents of a service.
2198 */
2199
2200 static int /* O - 1 if successful, 0 otherwise */
2201 list_service(ippfind_srv_t *service) /* I - Service */
2202 {
2203 http_addrlist_t *addrlist; /* Address(es) of service */
2204 char port[10]; /* Port number of service */
2205
2206
2207 snprintf(port, sizeof(port), "%d", service->port);
2208
2209 if ((addrlist = httpAddrGetList(service->host, address_family, port)) == NULL)
2210 {
2211 _cupsLangPrintf(stdout, "%s unreachable", service->uri);
2212 return (0);
2213 }
2214
2215 if (!strncmp(service->regtype, "_ipp._tcp", 9) ||
2216 !strncmp(service->regtype, "_ipps._tcp", 10))
2217 {
2218 /*
2219 * IPP/IPPS printer
2220 */
2221
2222 http_t *http; /* HTTP connection */
2223 ipp_t *request, /* IPP request */
2224 *response; /* IPP response */
2225 ipp_attribute_t *attr; /* IPP attribute */
2226 int i, /* Looping var */
2227 count, /* Number of values */
2228 version, /* IPP version */
2229 paccepting; /* printer-is-accepting-jobs value */
2230 ipp_pstate_t pstate; /* printer-state value */
2231 char preasons[1024], /* Comma-delimited printer-state-reasons */
2232 *ptr, /* Pointer into reasons */
2233 *end; /* End of reasons buffer */
2234 static const char * const rattrs[] =/* Requested attributes */
2235 {
2236 "printer-is-accepting-jobs",
2237 "printer-state",
2238 "printer-state-reasons"
2239 };
2240
2241 /*
2242 * Connect to the printer...
2243 */
2244
2245 http = httpConnect2(service->host, service->port, addrlist, address_family,
2246 !strncmp(service->regtype, "_ipps._tcp", 10) ?
2247 HTTP_ENCRYPTION_ALWAYS :
2248 HTTP_ENCRYPTION_IF_REQUESTED,
2249 1, 30000, NULL);
2250
2251 httpAddrFreeList(addrlist);
2252
2253 if (!http)
2254 {
2255 _cupsLangPrintf(stdout, "%s unavailable", service->uri);
2256 return (0);
2257 }
2258
2259 /*
2260 * Get the current printer state...
2261 */
2262
2263 response = NULL;
2264 version = ipp_version;
2265
2266 do
2267 {
2268 request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
2269 ippSetVersion(request, version / 10, version % 10);
2270 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
2271 service->uri);
2272 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
2273 "requesting-user-name", NULL, cupsUser());
2274 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
2275 "requested-attributes",
2276 (int)(sizeof(rattrs) / sizeof(rattrs[0])), NULL, rattrs);
2277
2278 response = cupsDoRequest(http, request, service->resource);
2279
2280 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11)
2281 version = 11;
2282 }
2283 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11);
2284
2285 /*
2286 * Show results...
2287 */
2288
2289 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
2290 {
2291 _cupsLangPrintf(stdout, "%s: unavailable", service->uri);
2292 return (0);
2293 }
2294
2295 if ((attr = ippFindAttribute(response, "printer-state",
2296 IPP_TAG_ENUM)) != NULL)
2297 pstate = (ipp_pstate_t)ippGetInteger(attr, 0);
2298 else
2299 pstate = IPP_PSTATE_STOPPED;
2300
2301 if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
2302 IPP_TAG_BOOLEAN)) != NULL)
2303 paccepting = ippGetBoolean(attr, 0);
2304 else
2305 paccepting = 0;
2306
2307 if ((attr = ippFindAttribute(response, "printer-state-reasons",
2308 IPP_TAG_KEYWORD)) != NULL)
2309 {
2310 strlcpy(preasons, ippGetString(attr, 0, NULL), sizeof(preasons));
2311
2312 for (i = 1, count = ippGetCount(attr), ptr = preasons + strlen(preasons),
2313 end = preasons + sizeof(preasons) - 1;
2314 i < count && ptr < end;
2315 i ++, ptr += strlen(ptr))
2316 {
2317 *ptr++ = ',';
2318 strlcpy(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1));
2319 }
2320 }
2321 else
2322 strlcpy(preasons, "none", sizeof(preasons));
2323
2324 ippDelete(response);
2325 httpClose(http);
2326
2327 _cupsLangPrintf(stdout, "%s %s %s %s", service->uri,
2328 ippEnumString("printer-state", pstate),
2329 paccepting ? "accepting-jobs" : "not-accepting-jobs",
2330 preasons);
2331 }
2332 else if (!strncmp(service->regtype, "_http._tcp", 10) ||
2333 !strncmp(service->regtype, "_https._tcp", 11))
2334 {
2335 /*
2336 * HTTP/HTTPS web page
2337 */
2338
2339 http_t *http; /* HTTP connection */
2340 http_status_t status; /* HEAD status */
2341
2342
2343 /*
2344 * Connect to the web server...
2345 */
2346
2347 http = httpConnect2(service->host, service->port, addrlist, address_family,
2348 !strncmp(service->regtype, "_ipps._tcp", 10) ?
2349 HTTP_ENCRYPTION_ALWAYS :
2350 HTTP_ENCRYPTION_IF_REQUESTED,
2351 1, 30000, NULL);
2352
2353 httpAddrFreeList(addrlist);
2354
2355 if (!http)
2356 {
2357 _cupsLangPrintf(stdout, "%s unavailable", service->uri);
2358 return (0);
2359 }
2360
2361 if (httpGet(http, service->resource))
2362 {
2363 _cupsLangPrintf(stdout, "%s unavailable", service->uri);
2364 return (0);
2365 }
2366
2367 do
2368 {
2369 status = httpUpdate(http);
2370 }
2371 while (status == HTTP_STATUS_CONTINUE);
2372
2373 httpFlush(http);
2374 httpClose(http);
2375
2376 if (status >= HTTP_STATUS_BAD_REQUEST)
2377 {
2378 _cupsLangPrintf(stdout, "%s unavailable", service->uri);
2379 return (0);
2380 }
2381
2382 _cupsLangPrintf(stdout, "%s available", service->uri);
2383 }
2384 else if (!strncmp(service->regtype, "_printer._tcp", 13))
2385 {
2386 /*
2387 * LPD printer
2388 */
2389
2390 int sock; /* Socket */
2391
2392
2393 if (!httpAddrConnect(addrlist, &sock))
2394 {
2395 _cupsLangPrintf(stdout, "%s unavailable", service->uri);
2396 httpAddrFreeList(addrlist);
2397 return (0);
2398 }
2399
2400 _cupsLangPrintf(stdout, "%s available", service->uri);
2401 httpAddrFreeList(addrlist);
2402
2403 httpAddrClose(NULL, sock);
2404 }
2405 else
2406 {
2407 _cupsLangPrintf(stdout, "%s unsupported", service->uri);
2408 httpAddrFreeList(addrlist);
2409 return (0);
2410 }
2411
2412 return (1);
2413 }
2414
2415
2416 /*
2417 * 'new_expr()' - Create a new expression.
2418 */
2419
2420 static ippfind_expr_t * /* O - New expression */
2421 new_expr(ippfind_op_t op, /* I - Operation */
2422 int invert, /* I - Invert result? */
2423 const char *value, /* I - TXT key or port range */
2424 const char *regex, /* I - Regular expression */
2425 char **args) /* I - Pointer to argument strings */
2426 {
2427 ippfind_expr_t *temp; /* New expression */
2428
2429
2430 if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL)
2431 return (NULL);
2432
2433 temp->op = op;
2434 temp->invert = invert;
2435
2436 if (op == IPPFIND_OP_TXT_EXISTS || op == IPPFIND_OP_TXT_REGEX)
2437 temp->key = (char *)value;
2438 else if (op == IPPFIND_OP_PORT_RANGE)
2439 {
2440 /*
2441 * Pull port number range of the form "number", "-number" (0-number),
2442 * "number-" (number-65535), and "number-number".
2443 */
2444
2445 if (*value == '-')
2446 {
2447 temp->range[1] = atoi(value + 1);
2448 }
2449 else if (strchr(value, '-'))
2450 {
2451 if (sscanf(value, "%d-%d", temp->range, temp->range + 1) == 1)
2452 temp->range[1] = 65535;
2453 }
2454 else
2455 {
2456 temp->range[0] = temp->range[1] = atoi(value);
2457 }
2458 }
2459
2460 if (regex)
2461 {
2462 int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_ICASE | REG_EXTENDED);
2463
2464 if (err)
2465 {
2466 char message[256]; /* Error message */
2467
2468 regerror(err, &(temp->re), message, sizeof(message));
2469 _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"),
2470 message);
2471 exit(IPPFIND_EXIT_SYNTAX);
2472 }
2473 }
2474
2475 if (args)
2476 {
2477 int num_args; /* Number of arguments */
2478
2479 for (num_args = 1; args[num_args]; num_args ++)
2480 if (!strcmp(args[num_args], ";"))
2481 break;
2482
2483 temp->num_args = num_args;
2484 temp->args = malloc((size_t)num_args * sizeof(char *));
2485 memcpy(temp->args, args, (size_t)num_args * sizeof(char *));
2486 }
2487
2488 return (temp);
2489 }
2490
2491
2492 #ifdef HAVE_AVAHI
2493 /*
2494 * 'poll_callback()' - Wait for input on the specified file descriptors.
2495 *
2496 * Note: This function is needed because avahi_simple_poll_iterate is broken
2497 * and always uses a timeout of 0 (!) milliseconds.
2498 * (Avahi Ticket #364)
2499 */
2500
2501 static int /* O - Number of file descriptors matching */
2502 poll_callback(
2503 struct pollfd *pollfds, /* I - File descriptors */
2504 unsigned int num_pollfds, /* I - Number of file descriptors */
2505 int timeout, /* I - Timeout in milliseconds (unused) */
2506 void *context) /* I - User data (unused) */
2507 {
2508 int val; /* Return value */
2509
2510
2511 (void)timeout;
2512 (void)context;
2513
2514 val = poll(pollfds, num_pollfds, 500);
2515
2516 if (val > 0)
2517 avahi_got_data = 1;
2518
2519 return (val);
2520 }
2521 #endif /* HAVE_AVAHI */
2522
2523
2524 /*
2525 * 'resolve_callback()' - Process resolve data.
2526 */
2527
2528 #ifdef HAVE_DNSSD
2529 static void DNSSD_API
2530 resolve_callback(
2531 DNSServiceRef sdRef, /* I - Service reference */
2532 DNSServiceFlags flags, /* I - Data flags */
2533 uint32_t interfaceIndex, /* I - Interface */
2534 DNSServiceErrorType errorCode, /* I - Error, if any */
2535 const char *fullName, /* I - Full service name */
2536 const char *hostTarget, /* I - Hostname */
2537 uint16_t port, /* I - Port number (network byte order) */
2538 uint16_t txtLen, /* I - Length of TXT record data */
2539 const unsigned char *txtRecord, /* I - TXT record data */
2540 void *context) /* I - Service */
2541 {
2542 char key[256], /* TXT key value */
2543 *value; /* Value from TXT record */
2544 const unsigned char *txtEnd; /* End of TXT record */
2545 uint8_t valueLen; /* Length of value */
2546 ippfind_srv_t *service = (ippfind_srv_t *)context;
2547 /* Service */
2548
2549
2550 /*
2551 * Only process "add" data...
2552 */
2553
2554 (void)sdRef;
2555 (void)flags;
2556 (void)interfaceIndex;
2557 (void)fullName;
2558
2559 if (errorCode != kDNSServiceErr_NoError)
2560 {
2561 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
2562 dnssd_error_string(errorCode));
2563 bonjour_error = 1;
2564 return;
2565 }
2566
2567 service->is_resolved = 1;
2568 service->host = strdup(hostTarget);
2569 service->port = ntohs(port);
2570
2571 value = service->host + strlen(service->host) - 1;
2572 if (value >= service->host && *value == '.')
2573 *value = '\0';
2574
2575 /*
2576 * Loop through the TXT key/value pairs and add them to an array...
2577 */
2578
2579 for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen)
2580 {
2581 /*
2582 * Ignore bogus strings...
2583 */
2584
2585 valueLen = *txtRecord++;
2586
2587 memcpy(key, txtRecord, valueLen);
2588 key[valueLen] = '\0';
2589
2590 if ((value = strchr(key, '=')) == NULL)
2591 continue;
2592
2593 *value++ = '\0';
2594
2595 /*
2596 * Add to array of TXT values...
2597 */
2598
2599 service->num_txt = cupsAddOption(key, value, service->num_txt,
2600 &(service->txt));
2601 }
2602
2603 set_service_uri(service);
2604 }
2605
2606
2607 #elif defined(HAVE_AVAHI)
2608 static void
2609 resolve_callback(
2610 AvahiServiceResolver *resolver, /* I - Resolver */
2611 AvahiIfIndex interface, /* I - Interface */
2612 AvahiProtocol protocol, /* I - Address protocol */
2613 AvahiResolverEvent event, /* I - Event */
2614 const char *serviceName,/* I - Service name */
2615 const char *regtype, /* I - Registration type */
2616 const char *replyDomain,/* I - Domain name */
2617 const char *hostTarget, /* I - FQDN */
2618 const AvahiAddress *address, /* I - Address */
2619 uint16_t port, /* I - Port number */
2620 AvahiStringList *txt, /* I - TXT records */
2621 AvahiLookupResultFlags flags, /* I - Lookup flags */
2622 void *context) /* I - Service */
2623 {
2624 char key[256], /* TXT key */
2625 *value; /* TXT value */
2626 ippfind_srv_t *service = (ippfind_srv_t *)context;
2627 /* Service */
2628 AvahiStringList *current; /* Current TXT key/value pair */
2629
2630
2631 (void)address;
2632
2633 if (event != AVAHI_RESOLVER_FOUND)
2634 {
2635 bonjour_error = 1;
2636
2637 avahi_service_resolver_free(resolver);
2638 avahi_simple_poll_quit(avahi_poll);
2639 return;
2640 }
2641
2642 service->is_resolved = 1;
2643 service->host = strdup(hostTarget);
2644 service->port = port;
2645
2646 value = service->host + strlen(service->host) - 1;
2647 if (value >= service->host && *value == '.')
2648 *value = '\0';
2649
2650 /*
2651 * Loop through the TXT key/value pairs and add them to an array...
2652 */
2653
2654 for (current = txt; current; current = current->next)
2655 {
2656 /*
2657 * Ignore bogus strings...
2658 */
2659
2660 if (current->size > (sizeof(key) - 1))
2661 continue;
2662
2663 memcpy(key, current->text, current->size);
2664 key[current->size] = '\0';
2665
2666 if ((value = strchr(key, '=')) == NULL)
2667 continue;
2668
2669 *value++ = '\0';
2670
2671 /*
2672 * Add to array of TXT values...
2673 */
2674
2675 service->num_txt = cupsAddOption(key, value, service->num_txt,
2676 &(service->txt));
2677 }
2678
2679 set_service_uri(service);
2680 }
2681 #endif /* HAVE_DNSSD */
2682
2683
2684 /*
2685 * 'set_service_uri()' - Set the URI of the service.
2686 */
2687
2688 static void
2689 set_service_uri(ippfind_srv_t *service) /* I - Service */
2690 {
2691 char uri[1024]; /* URI */
2692 const char *path, /* Resource path */
2693 *scheme; /* URI scheme */
2694
2695
2696 if (!strncmp(service->regtype, "_http.", 6))
2697 {
2698 scheme = "http";
2699 path = cupsGetOption("path", service->num_txt, service->txt);
2700 }
2701 else if (!strncmp(service->regtype, "_https.", 7))
2702 {
2703 scheme = "https";
2704 path = cupsGetOption("path", service->num_txt, service->txt);
2705 }
2706 else if (!strncmp(service->regtype, "_ipp.", 5))
2707 {
2708 scheme = "ipp";
2709 path = cupsGetOption("rp", service->num_txt, service->txt);
2710 }
2711 else if (!strncmp(service->regtype, "_ipps.", 6))
2712 {
2713 scheme = "ipps";
2714 path = cupsGetOption("rp", service->num_txt, service->txt);
2715 }
2716 else if (!strncmp(service->regtype, "_printer.", 9))
2717 {
2718 scheme = "lpd";
2719 path = cupsGetOption("rp", service->num_txt, service->txt);
2720 }
2721 else
2722 return;
2723
2724 if (!path || !*path)
2725 path = "/";
2726
2727 if (*path == '/')
2728 {
2729 service->resource = strdup(path);
2730 }
2731 else
2732 {
2733 snprintf(uri, sizeof(uri), "/%s", path);
2734 service->resource = strdup(uri);
2735 }
2736
2737 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL,
2738 service->host, service->port, service->resource);
2739 service->uri = strdup(uri);
2740 }
2741
2742
2743 /*
2744 * 'show_usage()' - Show program usage.
2745 */
2746
2747 static void
2748 show_usage(void)
2749 {
2750 _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
2751 "[.domain.] ... [expression]\n"
2752 " ippfind [options] name[.regtype[.domain.]] "
2753 "... [expression]\n"
2754 " ippfind --help\n"
2755 " ippfind --version"));
2756 _cupsLangPuts(stderr, "");
2757 _cupsLangPuts(stderr, _("Options:"));
2758 _cupsLangPuts(stderr, _(" -4 Connect using IPv4."));
2759 _cupsLangPuts(stderr, _(" -6 Connect using IPv6."));
2760 _cupsLangPuts(stderr, _(" -T seconds Set the browse timeout in "
2761 "seconds."));
2762 _cupsLangPuts(stderr, _(" -V version Set default IPP "
2763 "version."));
2764 _cupsLangPuts(stderr, _(" --help Show this help."));
2765 _cupsLangPuts(stderr, _(" --version Show program version."));
2766 _cupsLangPuts(stderr, "");
2767 _cupsLangPuts(stderr, _("Expressions:"));
2768 _cupsLangPuts(stderr, _(" -P number[-number] Match port to number or range."));
2769 _cupsLangPuts(stderr, _(" -d regex Match domain to regular expression."));
2770 _cupsLangPuts(stderr, _(" -h regex Match hostname to regular expression."));
2771 _cupsLangPuts(stderr, _(" -l List attributes."));
2772 _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression."));
2773 _cupsLangPuts(stderr, _(" -p Print URI if true."));
2774 _cupsLangPuts(stderr, _(" -q Quietly report match via exit code."));
2775 _cupsLangPuts(stderr, _(" -r True if service is remote."));
2776 _cupsLangPuts(stderr, _(" -s Print service name if true."));
2777 _cupsLangPuts(stderr, _(" -t key True if the TXT record contains the key."));
2778 _cupsLangPuts(stderr, _(" -u regex Match URI to regular expression."));
2779 _cupsLangPuts(stderr, _(" -x utility [argument ...] ;\n"
2780 " Execute program if true."));
2781 _cupsLangPuts(stderr, _(" --domain regex Match domain to regular expression."));
2782 _cupsLangPuts(stderr, _(" --exec utility [argument ...] ;\n"
2783 " Execute program if true."));
2784 _cupsLangPuts(stderr, _(" --host regex Match hostname to regular expression."));
2785 _cupsLangPuts(stderr, _(" --ls List attributes."));
2786 _cupsLangPuts(stderr, _(" --local True if service is local."));
2787 _cupsLangPuts(stderr, _(" --name regex Match service name to regular expression."));
2788 _cupsLangPuts(stderr, _(" --path regex Match resource path to regular expression."));
2789 _cupsLangPuts(stderr, _(" --port number[-number] Match port to number or range."));
2790 _cupsLangPuts(stderr, _(" --print Print URI if true."));
2791 _cupsLangPuts(stderr, _(" --print-name Print service name if true."));
2792 _cupsLangPuts(stderr, _(" --quiet Quietly report match via exit code."));
2793 _cupsLangPuts(stderr, _(" --remote True if service is remote."));
2794 _cupsLangPuts(stderr, _(" --txt key True if the TXT record contains the key."));
2795 _cupsLangPuts(stderr, _(" --txt-* regex Match TXT record key to regular expression."));
2796 _cupsLangPuts(stderr, _(" --uri regex Match URI to regular expression."));
2797 _cupsLangPuts(stderr, "");
2798 _cupsLangPuts(stderr, _("Modifiers:"));
2799 _cupsLangPuts(stderr, _(" ( expressions ) Group expressions."));
2800 _cupsLangPuts(stderr, _(" ! expression Unary NOT of expression."));
2801 _cupsLangPuts(stderr, _(" --not expression Unary NOT of expression."));
2802 _cupsLangPuts(stderr, _(" --false Always false."));
2803 _cupsLangPuts(stderr, _(" --true Always true."));
2804 _cupsLangPuts(stderr, _(" expression expression Logical AND."));
2805 _cupsLangPuts(stderr, _(" expression --and expression\n"
2806 " Logical AND."));
2807 _cupsLangPuts(stderr, _(" expression --or expression\n"
2808 " Logical OR."));
2809 _cupsLangPuts(stderr, "");
2810 _cupsLangPuts(stderr, _("Substitutions:"));
2811 _cupsLangPuts(stderr, _(" {} URI"));
2812 _cupsLangPuts(stderr, _(" {service_domain} Domain name"));
2813 _cupsLangPuts(stderr, _(" {service_hostname} Fully-qualified domain name"));
2814 _cupsLangPuts(stderr, _(" {service_name} Service instance name"));
2815 _cupsLangPuts(stderr, _(" {service_port} Port number"));
2816 _cupsLangPuts(stderr, _(" {service_regtype} DNS-SD registration type"));
2817 _cupsLangPuts(stderr, _(" {service_scheme} URI scheme"));
2818 _cupsLangPuts(stderr, _(" {service_uri} URI"));
2819 _cupsLangPuts(stderr, _(" {txt_*} Value of TXT record key"));
2820 _cupsLangPuts(stderr, "");
2821 _cupsLangPuts(stderr, _("Environment Variables:"));
2822 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_DOMAIN Domain name"));
2823 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_HOSTNAME\n"
2824 " Fully-qualified domain name"));
2825 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_NAME Service instance name"));
2826 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_PORT Port number"));
2827 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
2828 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_SCHEME URI scheme"));
2829 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_URI URI"));
2830 _cupsLangPuts(stderr, _(" IPPFIND_TXT_* Value of TXT record key"));
2831
2832 exit(IPPFIND_EXIT_TRUE);
2833 }
2834
2835
2836 /*
2837 * 'show_version()' - Show program version.
2838 */
2839
2840 static void
2841 show_version(void)
2842 {
2843 _cupsLangPuts(stderr, CUPS_SVERSION);
2844
2845 exit(IPPFIND_EXIT_TRUE);
2846 }