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