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