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