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