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