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