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