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