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