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