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