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