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