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