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