1/*
2 * "$Id: dnssd.c 11693 2014-03-11 01:24:45Z msweet $"
3 *
4 *   DNS-SD discovery backend for CUPS.
5 *
6 *   Copyright 2008-2012 by Apple Inc.
7 *
8 *   These coded instructions, statements, and computer programs are the
9 *   property of Apple Inc. and are protected by Federal copyright
10 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
11 *   "LICENSE" which should have been included with this file.  If this
12 *   file is missing or damaged, see the license at "http://www.cups.org/".
13 *
14 *   This file is subject to the Apple OS-Developed Software exception.
15 *
16 * Contents:
17 *
18 *   main()		     - Browse for printers.
19 *   browse_callback()	     - Browse devices.
20 *   browse_local_callback() - Browse local devices.
21 *   client_callback()       - Avahi client callback function.
22 *   compare_devices()	     - Compare two devices.
23 *   exec_backend()	     - Execute the backend that corresponds to the
24 *			       resolved service name.
25 *   device_type()	     - Get DNS-SD type enumeration from string.
26 *   get_device()	     - Create or update a device.
27 *   query_callback()	     - Process query data.
28 *   find_device()	     - Find a device from its name and domain.
29 *   sigterm_handler()	     - Handle termination signals.
30 *   unquote()		     - Unquote a name string.
31 */
32
33/*
34 * Include necessary headers.
35 */
36
37#include "backend-private.h"
38#include <cups/array.h>
39#ifdef HAVE_DNSSD
40#  include <dns_sd.h>
41#endif /* HAVE_DNSSD */
42#ifdef HAVE_AVAHI
43#  include <avahi-client/client.h>
44#  include <avahi-client/lookup.h>
45#  include <avahi-common/simple-watch.h>
46#  include <avahi-common/domain.h>
47#  include <avahi-common/error.h>
48#  include <avahi-common/malloc.h>
49#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
50#endif /* HAVE_AVAHI */
51
52
53/*
54 * Device structure...
55 */
56
57typedef enum
58{
59  CUPS_DEVICE_PRINTER = 0,		/* lpd://... */
60  CUPS_DEVICE_IPPS,			/* ipps://... */
61  CUPS_DEVICE_IPP,			/* ipp://... */
62  CUPS_DEVICE_FAX_IPP,			/* ipp://... */
63  CUPS_DEVICE_PDL_DATASTREAM,		/* socket://... */
64  CUPS_DEVICE_RIOUSBPRINT		/* riousbprint://... */
65} cups_devtype_t;
66
67
68typedef struct
69{
70#ifdef HAVE_DNSSD
71  DNSServiceRef	ref;			/* Service reference for query */
72#endif /* HAVE_DNSSD */
73#ifdef HAVE_AVAHI
74  AvahiRecordBrowser *ref;		/* Browser for query */
75#endif /* HAVE_AVAHI */
76  char		*name,			/* Service name */
77		*domain,		/* Domain name */
78		*fullName,		/* Full name */
79		*make_and_model,	/* Make and model from TXT record */
80		*device_id;		/* 1284 device ID from TXT record */
81  cups_devtype_t type;			/* Device registration type */
82  int		priority,		/* Priority associated with type */
83		cups_shared,		/* CUPS shared printer? */
84		sent;			/* Did we list the device? */
85} cups_device_t;
86
87
88/*
89 * Local globals...
90 */
91
92static int		job_canceled = 0;
93					/* Set to 1 on SIGTERM */
94#ifdef HAVE_AVAHI
95static AvahiSimplePoll	*simple_poll = NULL;
96					/* Poll information */
97static int		got_data = 0;	/* Got data from poll? */
98static int		browsers = 0;	/* Number of running browsers */
99#endif /* HAVE_AVAHI */
100
101
102/*
103 * Local functions...
104 */
105
106#ifdef HAVE_DNSSD
107static void		browse_callback(DNSServiceRef sdRef,
108			                DNSServiceFlags flags,
109				        uint32_t interfaceIndex,
110				        DNSServiceErrorType errorCode,
111				        const char *serviceName,
112				        const char *regtype,
113				        const char *replyDomain, void *context)
114					__attribute__((nonnull(1,5,6,7,8)));
115static void		browse_local_callback(DNSServiceRef sdRef,
116					      DNSServiceFlags flags,
117					      uint32_t interfaceIndex,
118					      DNSServiceErrorType errorCode,
119					      const char *serviceName,
120					      const char *regtype,
121					      const char *replyDomain,
122					      void *context)
123					      __attribute__((nonnull(1,5,6,7,8)));
124#endif /* HAVE_DNSSD */
125#ifdef HAVE_AVAHI
126static void		browse_callback(AvahiServiceBrowser *browser,
127					AvahiIfIndex interface,
128					AvahiProtocol protocol,
129					AvahiBrowserEvent event,
130					const char *serviceName,
131					const char *regtype,
132					const char *replyDomain,
133					AvahiLookupResultFlags flags,
134					void *context);
135static void		client_callback(AvahiClient *client,
136					AvahiClientState state,
137					void *context);
138#endif /* HAVE_AVAHI */
139
140static int		compare_devices(cups_device_t *a, cups_device_t *b);
141static void		exec_backend(char **argv);
142static cups_device_t	*get_device(cups_array_t *devices,
143			            const char *serviceName,
144			            const char *regtype,
145				    const char *replyDomain)
146				    __attribute__((nonnull(1,2,3,4)));
147#ifdef HAVE_DNSSD
148static void		query_callback(DNSServiceRef sdRef,
149			               DNSServiceFlags flags,
150				       uint32_t interfaceIndex,
151				       DNSServiceErrorType errorCode,
152				       const char *fullName, uint16_t rrtype,
153				       uint16_t rrclass, uint16_t rdlen,
154				       const void *rdata, uint32_t ttl,
155				       void *context)
156				       __attribute__((nonnull(1,5,9,11)));
157#elif defined(HAVE_AVAHI)
158static int		poll_callback(struct pollfd *pollfds,
159			              unsigned int num_pollfds, int timeout,
160			              void *context);
161static void		query_callback(AvahiRecordBrowser *browser,
162				       AvahiIfIndex interface,
163				       AvahiProtocol protocol,
164				       AvahiBrowserEvent event,
165				       const char *name, uint16_t rrclass,
166				       uint16_t rrtype, const void *rdata,
167				       size_t rdlen,
168				       AvahiLookupResultFlags flags,
169				       void *context);
170#endif /* HAVE_DNSSD */
171static void		sigterm_handler(int sig);
172static void		unquote(char *dst, const char *src, size_t dstsize)
173			    __attribute__((nonnull(1,2)));
174
175
176/*
177 * 'main()' - Browse for printers.
178 */
179
180int					/* O - Exit status */
181main(int  argc,				/* I - Number of command-line args */
182     char *argv[])			/* I - Command-line arguments */
183{
184  const char	*name;			/* Backend name */
185  cups_array_t	*devices;		/* Device array */
186  cups_device_t	*device;		/* Current device */
187  char		uriName[1024];		/* Unquoted fullName for URI */
188#ifdef HAVE_DNSSD
189  int		fd;			/* Main file descriptor */
190  fd_set	input;			/* Input set for select() */
191  struct timeval timeout;		/* Timeout for select() */
192  DNSServiceRef	main_ref,		/* Main service reference */
193		fax_ipp_ref,		/* IPP fax service reference */
194		ipp_ref,		/* IPP service reference */
195		ipp_tls_ref,		/* IPP w/TLS service reference */
196		ipps_ref,		/* IPP service reference */
197		local_fax_ipp_ref,	/* Local IPP fax service reference */
198		local_ipp_ref,		/* Local IPP service reference */
199		local_ipp_tls_ref,	/* Local IPP w/TLS service reference */
200		local_ipps_ref,		/* Local IPP service reference */
201		local_printer_ref,	/* Local LPD service reference */
202		pdl_datastream_ref,	/* AppSocket service reference */
203		printer_ref,		/* LPD service reference */
204		riousbprint_ref;	/* Remote IO service reference */
205#endif /* HAVE_DNSSD */
206#ifdef HAVE_AVAHI
207  AvahiClient	*client;		/* Client information */
208  int		error;			/* Error code, if any */
209#endif /* HAVE_AVAHI */
210#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
211  struct sigaction action;		/* Actions for POSIX signals */
212#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
213
214
215 /*
216  * Don't buffer stderr, and catch SIGTERM...
217  */
218
219  setbuf(stderr, NULL);
220
221#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
222  sigset(SIGTERM, sigterm_handler);
223#elif defined(HAVE_SIGACTION)
224  memset(&action, 0, sizeof(action));
225
226  sigemptyset(&action.sa_mask);
227  action.sa_handler = sigterm_handler;
228  sigaction(SIGTERM, &action, NULL);
229#else
230  signal(SIGTERM, sigterm_handler);
231#endif /* HAVE_SIGSET */
232
233 /*
234  * Check command-line...
235  */
236
237  if (argc >= 6)
238    exec_backend(argv);
239  else if (argc != 1)
240  {
241    _cupsLangPrintf(stderr,
242                    _("Usage: %s job-id user title copies options [file]"),
243		    argv[0]);
244    return (1);
245  }
246
247 /*
248  * Only do discovery when run as "dnssd"...
249  */
250
251  if ((name = strrchr(argv[0], '/')) != NULL)
252    name ++;
253  else
254    name = argv[0];
255
256  if (strcmp(name, "dnssd"))
257    return (0);
258
259 /*
260  * Create an array to track devices...
261  */
262
263  devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
264
265 /*
266  * Browse for different kinds of printers...
267  */
268
269#ifdef HAVE_DNSSD
270  if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
271  {
272    perror("ERROR: Unable to create service connection");
273    return (1);
274  }
275
276  fd = DNSServiceRefSockFD(main_ref);
277
278  fax_ipp_ref = main_ref;
279  DNSServiceBrowse(&fax_ipp_ref, kDNSServiceFlagsShareConnection, 0,
280                   "_fax-ipp._tcp", NULL, browse_callback, devices);
281
282  ipp_ref = main_ref;
283  DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
284                   "_ipp._tcp", NULL, browse_callback, devices);
285
286  ipp_tls_ref = main_ref;
287  DNSServiceBrowse(&ipp_tls_ref, kDNSServiceFlagsShareConnection, 0,
288                   "_ipp-tls._tcp", NULL, browse_callback, devices);
289
290  ipps_ref = main_ref;
291  DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0,
292                   "_ipps._tcp", NULL, browse_callback, devices);
293
294  local_fax_ipp_ref = main_ref;
295  DNSServiceBrowse(&local_fax_ipp_ref, kDNSServiceFlagsShareConnection,
296                   kDNSServiceInterfaceIndexLocalOnly,
297		   "_fax-ipp._tcp", NULL, browse_local_callback, devices);
298
299  local_ipp_ref = main_ref;
300  DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
301                   kDNSServiceInterfaceIndexLocalOnly,
302		   "_ipp._tcp", NULL, browse_local_callback, devices);
303
304  local_ipp_tls_ref = main_ref;
305  DNSServiceBrowse(&local_ipp_tls_ref, kDNSServiceFlagsShareConnection,
306                   kDNSServiceInterfaceIndexLocalOnly,
307                   "_ipp-tls._tcp", NULL, browse_local_callback, devices);
308
309  local_ipps_ref = main_ref;
310  DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection,
311                   kDNSServiceInterfaceIndexLocalOnly,
312		   "_ipps._tcp", NULL, browse_local_callback, devices);
313
314  local_printer_ref = main_ref;
315  DNSServiceBrowse(&local_printer_ref, kDNSServiceFlagsShareConnection,
316                   kDNSServiceInterfaceIndexLocalOnly,
317                   "_printer._tcp", NULL, browse_local_callback, devices);
318
319  pdl_datastream_ref = main_ref;
320  DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
321                   "_pdl-datastream._tcp", NULL, browse_callback, devices);
322
323  printer_ref = main_ref;
324  DNSServiceBrowse(&printer_ref, kDNSServiceFlagsShareConnection, 0,
325                   "_printer._tcp", NULL, browse_callback, devices);
326
327  riousbprint_ref = main_ref;
328  DNSServiceBrowse(&riousbprint_ref, kDNSServiceFlagsShareConnection, 0,
329                   "_riousbprint._tcp", NULL, browse_callback, devices);
330#endif /* HAVE_DNSSD */
331
332#ifdef HAVE_AVAHI
333  if ((simple_poll = avahi_simple_poll_new()) == NULL)
334  {
335    fputs("DEBUG: Unable to create Avahi simple poll object.\n", stderr);
336    return (1);
337  }
338
339  avahi_simple_poll_set_func(simple_poll, poll_callback, NULL);
340
341  client = avahi_client_new(avahi_simple_poll_get(simple_poll),
342			    0, client_callback, simple_poll, &error);
343  if (!client)
344  {
345    fputs("DEBUG: Unable to create Avahi client.\n", stderr);
346    return (1);
347  }
348
349  browsers = 6;
350  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
351			    AVAHI_PROTO_UNSPEC,
352			    "_fax-ipp._tcp", NULL, 0,
353			    browse_callback, devices);
354  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
355			    AVAHI_PROTO_UNSPEC,
356			    "_ipp._tcp", NULL, 0,
357			    browse_callback, devices);
358  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
359			    AVAHI_PROTO_UNSPEC,
360			    "_ipp-tls._tcp", NULL, 0,
361			    browse_callback, devices);
362  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
363			    AVAHI_PROTO_UNSPEC,
364			    "_ipps._tcp", NULL, 0,
365			    browse_callback, devices);
366  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
367			    AVAHI_PROTO_UNSPEC,
368			    "_pdl-datastream._tcp",
369			    NULL, 0,
370			    browse_callback,
371			    devices);
372  avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
373			    AVAHI_PROTO_UNSPEC,
374			    "_printer._tcp", NULL, 0,
375			    browse_callback, devices);
376#endif /* HAVE_AVAHI */
377
378 /*
379  * Loop until we are killed...
380  */
381
382  while (!job_canceled)
383  {
384    int announce = 0;			/* Announce printers? */
385
386#ifdef HAVE_DNSSD
387    FD_ZERO(&input);
388    FD_SET(fd, &input);
389
390    timeout.tv_sec  = 0;
391    timeout.tv_usec = 500000;
392
393    if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
394      continue;
395
396    if (FD_ISSET(fd, &input))
397    {
398     /*
399      * Process results of our browsing...
400      */
401
402      DNSServiceProcessResult(main_ref);
403    }
404    else
405      announce = 1;
406
407#elif defined(HAVE_AVAHI)
408    got_data = 0;
409
410    if ((error = avahi_simple_poll_iterate(simple_poll, 500)) > 0)
411    {
412     /*
413      * We've been told to exit the loop.  Perhaps the connection to
414      * Avahi failed.
415      */
416
417      break;
418    }
419
420    if (!got_data)
421      announce = 1;
422#endif /* HAVE_DNSSD */
423
424/*    fprintf(stderr, "DEBUG: announce=%d\n", announce);*/
425
426    if (announce)
427    {
428     /*
429      * Announce any devices we've found...
430      */
431
432#ifdef HAVE_DNSSD
433      DNSServiceErrorType status;	/* DNS query status */
434#endif /* HAVE_DNSSD */
435      cups_device_t *best;		/* Best matching device */
436      char	device_uri[1024];	/* Device URI */
437      int	count;			/* Number of queries */
438      int	sent;			/* Number of sent */
439
440      for (device = (cups_device_t *)cupsArrayFirst(devices),
441               best = NULL, count = 0, sent = 0;
442           device;
443	   device = (cups_device_t *)cupsArrayNext(devices))
444      {
445        if (device->sent)
446	  sent ++;
447
448        if (device->ref)
449	  count ++;
450
451        if (!device->ref && !device->sent)
452	{
453	 /*
454	  * Found the device, now get the TXT record(s) for it...
455	  */
456
457          if (count < 50)
458	  {
459	    fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
460
461#ifdef HAVE_DNSSD
462	    device->ref = main_ref;
463
464	    status = DNSServiceQueryRecord(&(device->ref),
465				           kDNSServiceFlagsShareConnection,
466				           0, device->fullName,
467					   kDNSServiceType_TXT,
468				           kDNSServiceClass_IN, query_callback,
469				           device);
470            if (status != kDNSServiceErr_NoError)
471	      fprintf(stderr,
472	              "ERROR: Unable to query \"%s\" for TXT records: %d\n",
473	              device->fullName, status);
474	              			/* Users never see this */
475	    else
476	      count ++;
477
478#else
479	    if ((device->ref = avahi_record_browser_new(client, AVAHI_IF_UNSPEC,
480	                                                AVAHI_PROTO_UNSPEC,
481	                                                device->fullName,
482	                                                AVAHI_DNS_CLASS_IN,
483	                                                AVAHI_DNS_TYPE_TXT,
484	                                                0,
485				                        query_callback,
486				                        device)) == NULL)
487	      fprintf(stderr,
488	              "ERROR: Unable to query \"%s\" for TXT records: %s\n",
489	              device->fullName,
490	              avahi_strerror(avahi_client_errno(client)));
491	              			/* Users never see this */
492	    else
493	      count ++;
494#endif /* HAVE_AVAHI */
495          }
496	}
497	else if (!device->sent)
498	{
499#ifdef HAVE_DNSSD
500	 /*
501	  * Got the TXT records, now report the device...
502	  */
503
504	  DNSServiceRefDeallocate(device->ref);
505#else
506          avahi_record_browser_free(device->ref);
507#endif /* HAVE_DNSSD */
508
509	  device->ref = NULL;
510
511          if (!best)
512	    best = device;
513	  else if (_cups_strcasecmp(best->name, device->name) ||
514	           _cups_strcasecmp(best->domain, device->domain))
515          {
516	    unquote(uriName, best->fullName, sizeof(uriName));
517
518	    httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
519			    "dnssd", NULL, uriName, 0,
520			    best->cups_shared ? "/cups" : "/");
521
522	    cupsBackendReport("network", device_uri, best->make_and_model,
523	                      best->name, best->device_id, NULL);
524	    best->sent = 1;
525	    best       = device;
526
527	    sent ++;
528	  }
529	  else if (best->priority > device->priority ||
530	           (best->priority == device->priority &&
531		    best->type < device->type))
532          {
533	    best->sent = 1;
534	    best       = device;
535
536	    sent ++;
537	  }
538	  else
539	  {
540	    device->sent = 1;
541
542	    sent ++;
543	  }
544        }
545      }
546
547      if (best)
548      {
549	unquote(uriName, best->fullName, sizeof(uriName));
550
551	httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
552			"dnssd", NULL, uriName, 0,
553			best->cups_shared ? "/cups" : "/");
554
555	cupsBackendReport("network", device_uri, best->make_and_model,
556			  best->name, best->device_id, NULL);
557	best->sent = 1;
558	sent ++;
559      }
560
561      fprintf(stderr, "DEBUG: sent=%d, count=%d\n", sent, count);
562
563#ifdef HAVE_AVAHI
564      if (sent == cupsArrayCount(devices) && browsers == 0)
565#else
566      if (sent == cupsArrayCount(devices))
567#endif /* HAVE_AVAHI */
568	break;
569    }
570  }
571
572  return (CUPS_BACKEND_OK);
573}
574
575
576#ifdef HAVE_DNSSD
577/*
578 * 'browse_callback()' - Browse devices.
579 */
580
581static void
582browse_callback(
583    DNSServiceRef       sdRef,		/* I - Service reference */
584    DNSServiceFlags     flags,		/* I - Option flags */
585    uint32_t            interfaceIndex,	/* I - Interface number */
586    DNSServiceErrorType errorCode,	/* I - Error, if any */
587    const char          *serviceName,	/* I - Name of service/device */
588    const char          *regtype,	/* I - Type of service */
589    const char          *replyDomain,	/* I - Service domain */
590    void                *context)	/* I - Devices array */
591{
592  fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
593                  "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
594		  "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
595          sdRef, flags, interfaceIndex, errorCode,
596	  serviceName, regtype, replyDomain, context);
597
598 /*
599  * Only process "add" data...
600  */
601
602  if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
603    return;
604
605 /*
606  * Get the device...
607  */
608
609  get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
610}
611
612
613/*
614 * 'browse_local_callback()' - Browse local devices.
615 */
616
617static void
618browse_local_callback(
619    DNSServiceRef       sdRef,		/* I - Service reference */
620    DNSServiceFlags     flags,		/* I - Option flags */
621    uint32_t            interfaceIndex,	/* I - Interface number */
622    DNSServiceErrorType errorCode,	/* I - Error, if any */
623    const char          *serviceName,	/* I - Name of service/device */
624    const char          *regtype,	/* I - Type of service */
625    const char          *replyDomain,	/* I - Service domain */
626    void                *context)	/* I - Devices array */
627{
628  cups_device_t	*device;		/* Device */
629
630
631  fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
632                  "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
633		  "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
634          sdRef, flags, interfaceIndex, errorCode,
635	  serviceName, regtype, replyDomain, context);
636
637 /*
638  * Only process "add" data...
639  */
640
641  if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
642    return;
643
644 /*
645  * Get the device...
646  */
647
648  device = get_device((cups_array_t *)context, serviceName, regtype,
649                      replyDomain);
650
651 /*
652  * Hide locally-registered devices...
653  */
654
655  fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
656	  device->fullName);
657  device->sent = 1;
658}
659#endif /* HAVE_DNSSD */
660
661
662#ifdef HAVE_AVAHI
663/*
664 * 'browse_callback()' - Browse devices.
665 */
666
667static void
668browse_callback(
669    AvahiServiceBrowser    *browser,	/* I - Browser */
670    AvahiIfIndex           interface,	/* I - Interface index (unused) */
671    AvahiProtocol          protocol,	/* I - Network protocol (unused) */
672    AvahiBrowserEvent      event,	/* I - What happened */
673    const char             *name,	/* I - Service name */
674    const char             *type,	/* I - Registration type */
675    const char             *domain,	/* I - Domain */
676    AvahiLookupResultFlags flags,	/* I - Flags */
677    void                   *context)	/* I - Devices array */
678{
679  AvahiClient *client = avahi_service_browser_get_client(browser);
680					/* Client information */
681
682
683  (void)interface;
684  (void)protocol;
685  (void)context;
686
687  switch (event)
688  {
689    case AVAHI_BROWSER_FAILURE:
690	fprintf(stderr, "DEBUG: browse_callback: %s\n",
691		avahi_strerror(avahi_client_errno(client)));
692	avahi_simple_poll_quit(simple_poll);
693	break;
694
695    case AVAHI_BROWSER_NEW:
696       /*
697	* This object is new on the network.
698	*/
699
700	if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
701	{
702	 /*
703	  * This comes from the local machine so ignore it.
704	  */
705
706	  fprintf(stderr, "DEBUG: Ignoring local service %s.\n", name);
707	}
708	else
709	{
710	 /*
711	  * Create a device entry for it if it doesn't yet exist.
712	  */
713
714	  get_device((cups_array_t *)context, name, type, domain);
715	}
716	break;
717
718    case AVAHI_BROWSER_REMOVE:
719    case AVAHI_BROWSER_CACHE_EXHAUSTED:
720        break;
721
722    case AVAHI_BROWSER_ALL_FOR_NOW:
723	browsers--;
724	break;
725  }
726}
727
728
729/*
730 * 'client_callback()' - Avahi client callback function.
731 */
732
733static void
734client_callback(
735    AvahiClient      *client,		/* I - Client information (unused) */
736    AvahiClientState state,		/* I - Current state */
737    void             *context)		/* I - User data (unused) */
738{
739  (void)client;
740  (void)context;
741
742 /*
743  * If the connection drops, quit.
744  */
745
746  if (state == AVAHI_CLIENT_FAILURE)
747  {
748    fputs("DEBUG: Avahi connection failed.\n", stderr);
749    avahi_simple_poll_quit(simple_poll);
750  }
751}
752#endif /* HAVE_AVAHI */
753
754
755/*
756 * 'compare_devices()' - Compare two devices.
757 */
758
759static int				/* O - Result of comparison */
760compare_devices(cups_device_t *a,	/* I - First device */
761                cups_device_t *b)	/* I - Second device */
762{
763  return (strcmp(a->name, b->name));
764}
765
766
767/*
768 * 'exec_backend()' - Execute the backend that corresponds to the
769 *                    resolved service name.
770 */
771
772static void
773exec_backend(char **argv)		/* I - Command-line arguments */
774{
775  const char	*resolved_uri,		/* Resolved device URI */
776		*cups_serverbin;	/* Location of programs */
777  char		scheme[1024],		/* Scheme from URI */
778		*ptr,			/* Pointer into scheme */
779		filename[1024];		/* Backend filename */
780
781
782 /*
783  * Resolve the device URI...
784  */
785
786  job_canceled = -1;
787
788  while ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
789  {
790    _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer."));
791    sleep(10);
792
793    if (getenv("CLASS") != NULL)
794      exit(CUPS_BACKEND_FAILED);
795  }
796
797 /*
798  * Extract the scheme from the URI...
799  */
800
801  strlcpy(scheme, resolved_uri, sizeof(scheme));
802  if ((ptr = strchr(scheme, ':')) != NULL)
803    *ptr = '\0';
804
805 /*
806  * Get the filename of the backend...
807  */
808
809  if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
810    cups_serverbin = CUPS_SERVERBIN;
811
812  snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
813
814 /*
815  * Overwrite the device URI and run the new backend...
816  */
817
818  setenv("DEVICE_URI", resolved_uri, 1);
819
820  argv[0] = (char *)resolved_uri;
821
822  fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
823
824  execv(filename, argv);
825
826  fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
827          strerror(errno));
828  exit(CUPS_BACKEND_STOP);
829}
830
831
832/*
833 * 'device_type()' - Get DNS-SD type enumeration from string.
834 */
835
836static int
837device_type(const char *regtype)
838{
839#ifdef HAVE_AVAHI
840  if (!strcmp(regtype, "_ipp._tcp"))
841    return (CUPS_DEVICE_IPP);
842  else if (!strcmp(regtype, "_ipps._tcp") ||
843	   !strcmp(regtype, "_ipp-tls._tcp"))
844    return (CUPS_DEVICE_IPPS);
845  else if (!strcmp(regtype, "_fax-ipp._tcp"))
846    return (CUPS_DEVICE_FAX_IPP);
847  else if (!strcmp(regtype, "_printer._tcp"))
848    return (CUPS_DEVICE_PDL_DATASTREAM);
849#else
850  if (!strcmp(regtype, "_ipp._tcp."))
851    return (CUPS_DEVICE_IPP);
852  else if (!strcmp(regtype, "_ipps._tcp.") ||
853	   !strcmp(regtype, "_ipp-tls._tcp."))
854    return (CUPS_DEVICE_IPPS);
855  else if (!strcmp(regtype, "_fax-ipp._tcp."))
856    return (CUPS_DEVICE_FAX_IPP);
857  else if (!strcmp(regtype, "_printer._tcp."))
858    return (CUPS_DEVICE_PRINTER);
859  else if (!strcmp(regtype, "_pdl-datastream._tcp."))
860    return (CUPS_DEVICE_PDL_DATASTREAM);
861#endif /* HAVE_AVAHI */
862
863  return (CUPS_DEVICE_RIOUSBPRINT);
864}
865
866
867/*
868 * 'get_device()' - Create or update a device.
869 */
870
871static cups_device_t *			/* O - Device */
872get_device(cups_array_t *devices,	/* I - Device array */
873           const char   *serviceName,	/* I - Name of service/device */
874           const char   *regtype,	/* I - Type of service */
875           const char   *replyDomain)	/* I - Service domain */
876{
877  cups_device_t	key,			/* Search key */
878		*device;		/* Device */
879  char		fullName[kDNSServiceMaxDomainName];
880					/* Full name for query */
881
882
883 /*
884  * See if this is a new device...
885  */
886
887  key.name = (char *)serviceName;
888  key.type = device_type(regtype);
889
890  for (device = cupsArrayFind(devices, &key);
891       device;
892       device = cupsArrayNext(devices))
893    if (_cups_strcasecmp(device->name, key.name))
894      break;
895    else if (device->type == key.type)
896    {
897      if (!_cups_strcasecmp(device->domain, "local.") &&
898          _cups_strcasecmp(device->domain, replyDomain))
899      {
900       /*
901        * Update the .local listing to use the "global" domain name instead.
902	* The backend will try local lookups first, then the global domain name.
903	*/
904
905        free(device->domain);
906	device->domain = strdup(replyDomain);
907
908#ifdef HAVE_DNSSD
909	DNSServiceConstructFullName(fullName, device->name, regtype,
910	                            replyDomain);
911#else /* HAVE_AVAHI */
912	avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
913				serviceName, regtype, replyDomain);
914#endif /* HAVE_DNSSD */
915
916	free(device->fullName);
917	device->fullName = strdup(fullName);
918      }
919
920      return (device);
921    }
922
923 /*
924  * Yes, add the device...
925  */
926
927  fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
928	  replyDomain);
929
930  device           = calloc(sizeof(cups_device_t), 1);
931  device->name     = strdup(serviceName);
932  device->domain   = strdup(replyDomain);
933  device->type     = key.type;
934  device->priority = 50;
935
936  cupsArrayAdd(devices, device);
937
938 /*
939  * Set the "full name" of this service, which is used for queries...
940  */
941
942#ifdef HAVE_DNSSD
943  DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
944#else /* HAVE_AVAHI */
945  avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
946			   serviceName, regtype, replyDomain);
947#endif /* HAVE_DNSSD */
948
949  device->fullName = strdup(fullName);
950
951  return (device);
952}
953
954
955#ifdef HAVE_AVAHI
956/*
957 * 'poll_callback()' - Wait for input on the specified file descriptors.
958 *
959 * Note: This function is needed because avahi_simple_poll_iterate is broken
960 *       and always uses a timeout of 0 (!) milliseconds.
961 *       (Avahi Ticket #364)
962 */
963
964static int				/* O - Number of file descriptors matching */
965poll_callback(
966    struct pollfd *pollfds,		/* I - File descriptors */
967    unsigned int  num_pollfds,		/* I - Number of file descriptors */
968    int           timeout,		/* I - Timeout in milliseconds (unused) */
969    void          *context)		/* I - User data (unused) */
970{
971  int	val;				/* Return value */
972
973
974  (void)timeout;
975  (void)context;
976
977  val = poll(pollfds, num_pollfds, 500);
978
979  if (val < 0)
980    fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno));
981  else if (val > 0)
982    got_data = 1;
983
984  return (val);
985}
986#endif /* HAVE_AVAHI */
987
988
989#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
990#  ifdef HAVE_DNSSD
991/*
992 * 'query_callback()' - Process query data.
993 */
994
995static void
996query_callback(
997    DNSServiceRef       sdRef,		/* I - Service reference */
998    DNSServiceFlags     flags,		/* I - Data flags */
999    uint32_t            interfaceIndex,	/* I - Interface */
1000    DNSServiceErrorType errorCode,	/* I - Error, if any */
1001    const char          *fullName,	/* I - Full service name */
1002    uint16_t            rrtype,		/* I - Record type */
1003    uint16_t            rrclass,	/* I - Record class */
1004    uint16_t            rdlen,		/* I - Length of record data */
1005    const void          *rdata,		/* I - Record data */
1006    uint32_t            ttl,		/* I - Time-to-live */
1007    void                *context)	/* I - Device */
1008{
1009#  else
1010/*
1011 * 'query_callback()' - Process query data.
1012 */
1013
1014static void
1015query_callback(
1016    AvahiRecordBrowser     *browser,	/* I - Record browser */
1017    AvahiIfIndex           interfaceIndex,
1018					/* I - Interface index (unused) */
1019    AvahiProtocol          protocol,	/* I - Network protocol (unused) */
1020    AvahiBrowserEvent      event,	/* I - What happened? */
1021    const char             *fullName,	/* I - Service name */
1022    uint16_t               rrclass,	/* I - Record class */
1023    uint16_t               rrtype,	/* I - Record type */
1024    const void             *rdata,	/* I - TXT record */
1025    size_t                 rdlen,	/* I - Length of TXT record */
1026    AvahiLookupResultFlags flags,	/* I - Flags */
1027    void                   *context)	/* I - Device */
1028{
1029  AvahiClient		*client = avahi_record_browser_get_client(browser);
1030					/* Client information */
1031#  endif /* HAVE_DNSSD */
1032  char		*ptr;			/* Pointer into string */
1033  cups_device_t	*device = (cups_device_t *)context;
1034					/* Device */
1035  const uint8_t	*data,			/* Pointer into data */
1036		*datanext,		/* Next key/value pair */
1037		*dataend;		/* End of entire TXT record */
1038  uint8_t	datalen;		/* Length of current key/value pair */
1039  char		key[256],		/* Key string */
1040		value[256],		/* Value string */
1041		make_and_model[512],	/* Manufacturer and model */
1042		model[256],		/* Model */
1043		pdl[256],		/* PDL */
1044		device_id[2048];	/* 1284 device ID */
1045
1046
1047#  ifdef HAVE_DNSSD
1048  fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
1049                  "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
1050		  "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
1051		  "context=%p)\n",
1052          sdRef, flags, interfaceIndex, errorCode,
1053	  fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
1054	  context);
1055
1056 /*
1057  * Only process "add" data...
1058  */
1059
1060  if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1061    return;
1062
1063#  else
1064  fprintf(stderr, "DEBUG2: query_callback(browser=%p, interfaceIndex=%d, "
1065                  "protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, "
1066		  "rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)\n",
1067          browser, interfaceIndex, protocol, event,
1068	  fullName ? fullName : "(null)", rrclass, rrtype, rdata,
1069	  (unsigned)rdlen, flags, context);
1070
1071 /*
1072  * Only process "add" data...
1073  */
1074
1075  if (event != AVAHI_BROWSER_NEW)
1076  {
1077    if (event == AVAHI_BROWSER_FAILURE)
1078      fprintf(stderr, "ERROR: %s\n",
1079	      avahi_strerror(avahi_client_errno(client)));
1080
1081    return;
1082  }
1083#  endif /* HAVE_DNSSD */
1084
1085 /*
1086  * Pull out the priority and make and model from the TXT
1087  * record and save it...
1088  */
1089
1090  device_id[0]      = '\0';
1091  make_and_model[0] = '\0';
1092  pdl[0]            = '\0';
1093
1094  strlcpy(model, "Unknown", sizeof(model));
1095
1096  for (data = rdata, dataend = data + rdlen;
1097       data < dataend;
1098       data = datanext)
1099  {
1100   /*
1101    * Read a key/value pair starting with an 8-bit length.  Since the
1102    * length is 8 bits and the size of the key/value buffers is 256, we
1103    * don't need to check for overflow...
1104    */
1105
1106    datalen = *data++;
1107
1108    if (!datalen || (data + datalen) > dataend)
1109      break;
1110
1111    datanext = data + datalen;
1112
1113    for (ptr = key; data < datanext && *data != '='; data ++)
1114      *ptr++ = *data;
1115    *ptr = '\0';
1116
1117    if (data < datanext && *data == '=')
1118    {
1119      data ++;
1120
1121      if (data < datanext)
1122	memcpy(value, data, datanext - data);
1123      value[datanext - data] = '\0';
1124
1125      fprintf(stderr, "DEBUG2: query_callback: \"%s=%s\".\n",
1126	      key, value);
1127    }
1128    else
1129    {
1130      fprintf(stderr, "DEBUG2: query_callback: \"%s\" with no value.\n",
1131	      key);
1132      continue;
1133    }
1134
1135    if (!_cups_strncasecmp(key, "usb_", 4))
1136    {
1137     /*
1138      * Add USB device ID information...
1139      */
1140
1141      ptr = device_id + strlen(device_id);
1142      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s:%s;",
1143	       key + 4, value);
1144    }
1145
1146    if (!_cups_strcasecmp(key, "usb_MFG") || !_cups_strcasecmp(key, "usb_MANU") ||
1147	!_cups_strcasecmp(key, "usb_MANUFACTURER"))
1148      strlcpy(make_and_model, value, sizeof(make_and_model));
1149    else if (!_cups_strcasecmp(key, "usb_MDL") || !_cups_strcasecmp(key, "usb_MODEL"))
1150      strlcpy(model, value, sizeof(model));
1151    else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
1152    {
1153      if (value[0] == '(')
1154      {
1155       /*
1156	* Strip parenthesis...
1157	*/
1158
1159	if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
1160	  *ptr = '\0';
1161
1162	strlcpy(model, value + 1, sizeof(model));
1163      }
1164      else
1165	strlcpy(model, value, sizeof(model));
1166    }
1167    else if (!_cups_strcasecmp(key, "ty"))
1168    {
1169      strlcpy(model, value, sizeof(model));
1170
1171      if ((ptr = strchr(model, ',')) != NULL)
1172	*ptr = '\0';
1173    }
1174    else if (!_cups_strcasecmp(key, "pdl"))
1175      strlcpy(pdl, value, sizeof(pdl));
1176    else if (!_cups_strcasecmp(key, "priority"))
1177      device->priority = atoi(value);
1178    else if ((device->type == CUPS_DEVICE_IPP ||
1179	      device->type == CUPS_DEVICE_IPPS ||
1180	      device->type == CUPS_DEVICE_PRINTER) &&
1181	     !_cups_strcasecmp(key, "printer-type"))
1182    {
1183     /*
1184      * This is a CUPS printer!
1185      */
1186
1187      device->cups_shared = 1;
1188
1189      if (device->type == CUPS_DEVICE_PRINTER)
1190	device->sent = 1;
1191    }
1192  }
1193
1194  if (device->device_id)
1195    free(device->device_id);
1196
1197  if (!device_id[0] && strcmp(model, "Unknown"))
1198  {
1199    if (make_and_model[0])
1200      snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;",
1201	       make_and_model, model);
1202    else if (!_cups_strncasecmp(model, "designjet ", 10))
1203      snprintf(device_id, sizeof(device_id), "MFG:HP;MDL:%s", model + 10);
1204    else if (!_cups_strncasecmp(model, "stylus ", 7))
1205      snprintf(device_id, sizeof(device_id), "MFG:EPSON;MDL:%s", model + 7);
1206    else if ((ptr = strchr(model, ' ')) != NULL)
1207    {
1208     /*
1209      * Assume the first word is the make...
1210      */
1211
1212      memcpy(make_and_model, model, ptr - model);
1213      make_and_model[ptr - model] = '\0';
1214
1215      snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s",
1216	       make_and_model, ptr + 1);
1217    }
1218  }
1219
1220  if (device_id[0] &&
1221      !strstr(device_id, "CMD:") &&
1222      !strstr(device_id, "COMMAND SET:") &&
1223      (strstr(pdl, "application/pdf") ||
1224       strstr(pdl, "application/postscript") ||
1225       strstr(pdl, "application/vnd.hp-PCL") ||
1226       strstr(pdl, "image/")))
1227  {
1228    value[0] = '\0';
1229    if (strstr(pdl, "application/pdf"))
1230      strlcat(value, ",PDF", sizeof(value));
1231    if (strstr(pdl, "application/postscript"))
1232      strlcat(value, ",PS", sizeof(value));
1233    if (strstr(pdl, "application/vnd.hp-PCL"))
1234      strlcat(value, ",PCL", sizeof(value));
1235    for (ptr = strstr(pdl, "image/"); ptr; ptr = strstr(ptr, "image/"))
1236    {
1237      char *valptr = value + strlen(value);
1238      					/* Pointer into value */
1239
1240      if (valptr < (value + sizeof(value) - 1))
1241        *valptr++ = ',';
1242
1243      ptr += 6;
1244      while (isalnum(*ptr & 255) || *ptr == '-' || *ptr == '.')
1245      {
1246        if (isalnum(*ptr & 255) && valptr < (value + sizeof(value) - 1))
1247          *valptr++ = toupper(*ptr++ & 255);
1248        else
1249          break;
1250      }
1251
1252      *valptr = '\0';
1253    }
1254
1255    ptr = device_id + strlen(device_id);
1256    snprintf(ptr, sizeof(device_id) - (ptr - device_id), "CMD:%s;",
1257	     value + 1);
1258  }
1259
1260  if (device_id[0])
1261    device->device_id = strdup(device_id);
1262  else
1263    device->device_id = NULL;
1264
1265  if (device->make_and_model)
1266    free(device->make_and_model);
1267
1268  if (make_and_model[0])
1269  {
1270    strlcat(make_and_model, " ", sizeof(make_and_model));
1271    strlcat(make_and_model, model, sizeof(make_and_model));
1272
1273    device->make_and_model = strdup(make_and_model);
1274  }
1275  else
1276    device->make_and_model = strdup(model);
1277}
1278#endif /* HAVE_DNSSD || HAVE_AVAHI */
1279
1280
1281/*
1282 * 'sigterm_handler()' - Handle termination signals.
1283 */
1284
1285static void
1286sigterm_handler(int sig)		/* I - Signal number (unused) */
1287{
1288  (void)sig;
1289
1290  if (job_canceled)
1291    exit(CUPS_BACKEND_OK);
1292  else
1293    job_canceled = 1;
1294}
1295
1296
1297/*
1298 * 'unquote()' - Unquote a name string.
1299 */
1300
1301static void
1302unquote(char       *dst,		/* I - Destination buffer */
1303        const char *src,		/* I - Source string */
1304	size_t     dstsize)		/* I - Size of destination buffer */
1305{
1306  char	*dstend = dst + dstsize - 1;	/* End of destination buffer */
1307
1308
1309  while (*src && dst < dstend)
1310  {
1311    if (*src == '\\')
1312    {
1313      src ++;
1314      if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
1315          isdigit(src[2] & 255))
1316      {
1317        *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
1318	src += 3;
1319      }
1320      else
1321        *dst++ = *src++;
1322    }
1323    else
1324      *dst++ = *src ++;
1325  }
1326
1327  *dst = '\0';
1328}
1329
1330
1331/*
1332 * End of "$Id: dnssd.c 11693 2014-03-11 01:24:45Z msweet $".
1333 */
1334