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