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