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