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