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