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