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