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