4 * DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
6 * Copyright 2008 by Apple Inc.
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/".
14 * This file is subject to the Apple OS-Developed Software exception.
18 * main() - Browse for printers.
19 * browse_callback() - Browse devices.
20 * browse_local_callback() - Browse local devices.
21 * compare_devices() - Compare two devices.
22 * get_device() - Create or update a device.
23 * query_callback() - Process query data.
24 * unquote() - Unquote a name string.
28 * Include necessary headers.
31 #include "backend-private.h"
32 #include <cups/array.h>
42 CUPS_DEVICE_PRINTER
= 0, /* lpd://... */
43 CUPS_DEVICE_IPP
, /* ipp://... */
44 CUPS_DEVICE_FAX_IPP
, /* ipp://... */
45 CUPS_DEVICE_PDL_DATASTREAM
, /* socket://... */
46 CUPS_DEVICE_RIOUSBPRINT
/* riousbprint://... */
52 DNSServiceRef ref
; /* Service reference for resolve */
53 char *name
, /* Service name */
54 *domain
, /* Domain name */
55 *fullName
, /* Full name */
56 *make_and_model
; /* Make and model from TXT record */
57 cups_devtype_t type
; /* Device registration type */
58 int priority
, /* Priority associated with type */
59 cups_shared
, /* CUPS shared printer? */
60 sent
; /* Did we list the device? */
68 static void browse_callback(DNSServiceRef sdRef
,
69 DNSServiceFlags flags
,
70 uint32_t interfaceIndex
,
71 DNSServiceErrorType errorCode
,
72 const char *serviceName
,
74 const char *replyDomain
, void *context
);
75 static void browse_local_callback(DNSServiceRef sdRef
,
76 DNSServiceFlags flags
,
77 uint32_t interfaceIndex
,
78 DNSServiceErrorType errorCode
,
79 const char *serviceName
,
81 const char *replyDomain
,
83 static int compare_devices(cups_device_t
*a
, cups_device_t
*b
);
84 static void exec_backend(char **argv
);
85 static cups_device_t
*get_device(cups_array_t
*devices
,
86 const char *serviceName
,
88 const char *replyDomain
);
89 static void query_callback(DNSServiceRef sdRef
,
90 DNSServiceFlags flags
,
91 uint32_t interfaceIndex
,
92 DNSServiceErrorType errorCode
,
93 const char *fullName
, uint16_t rrtype
,
94 uint16_t rrclass
, uint16_t rdlen
,
95 const void *rdata
, uint32_t ttl
,
97 static void unquote(char *dst
, const char *src
, size_t dstsize
);
101 * 'main()' - Browse for printers.
104 int /* O - Exit status */
105 main(int argc
, /* I - Number of command-line args */
106 char *argv
[]) /* I - Command-line arguments */
108 const char *name
; /* Backend name */
109 DNSServiceRef main_ref
, /* Main service reference */
110 fax_ipp_ref
, /* IPP fax service reference */
111 ipp_ref
, /* IPP service reference */
112 ipp_tls_ref
, /* IPP w/TLS service reference */
113 local_fax_ipp_ref
, /* Local IPP fax service reference */
114 local_ipp_ref
, /* Local IPP service reference */
115 local_ipp_tls_ref
, /* Local IPP w/TLS service reference */
116 local_printer_ref
, /* Local LPD service reference */
117 pdl_datastream_ref
, /* AppSocket service reference */
118 printer_ref
, /* LPD service reference */
119 riousbprint_ref
; /* Remote IO service reference */
120 int fd
; /* Main file descriptor */
121 fd_set input
; /* Input set for select() */
122 struct timeval timeout
; /* Timeout for select() */
123 cups_array_t
*devices
; /* Device array */
124 cups_device_t
*device
; /* Current device */
128 * Check command-line...
131 setbuf(stderr
, NULL
);
137 fprintf(stderr
, "Usage: %s job user title copies options [filename(s)]\n",
143 * Only do discovery when run as "dnssd"...
146 if ((name
= strrchr(argv
[0], '/')) != NULL
)
151 if (strcmp(name
, "dnssd"))
155 * Create an array to track devices...
158 devices
= cupsArrayNew((cups_array_func_t
)compare_devices
, NULL
);
161 * Browse for different kinds of printers...
164 if (DNSServiceCreateConnection(&main_ref
) != kDNSServiceErr_NoError
)
166 perror("ERROR: Unable to create service connection");
170 fd
= DNSServiceRefSockFD(main_ref
);
172 fax_ipp_ref
= main_ref
;
173 DNSServiceBrowse(&fax_ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
174 "_fax-ipp._tcp", NULL
, browse_callback
, devices
);
177 DNSServiceBrowse(&ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
178 "_ipp._tcp", NULL
, browse_callback
, devices
);
180 ipp_tls_ref
= main_ref
;
181 DNSServiceBrowse(&ipp_tls_ref
, kDNSServiceFlagsShareConnection
, 0,
182 "_ipp-tls._tcp", NULL
, browse_callback
, devices
);
184 local_fax_ipp_ref
= main_ref
;
185 DNSServiceBrowse(&local_fax_ipp_ref
, kDNSServiceFlagsShareConnection
,
186 kDNSServiceInterfaceIndexLocalOnly
,
187 "_fax-ipp._tcp", NULL
, browse_local_callback
, devices
);
189 local_ipp_ref
= main_ref
;
190 DNSServiceBrowse(&local_ipp_ref
, kDNSServiceFlagsShareConnection
,
191 kDNSServiceInterfaceIndexLocalOnly
,
192 "_ipp._tcp", NULL
, browse_local_callback
, devices
);
194 local_ipp_tls_ref
= main_ref
;
195 DNSServiceBrowse(&local_ipp_tls_ref
, kDNSServiceFlagsShareConnection
,
196 kDNSServiceInterfaceIndexLocalOnly
,
197 "_ipp-tls._tcp", NULL
, browse_local_callback
, devices
);
199 local_printer_ref
= main_ref
;
200 DNSServiceBrowse(&local_printer_ref
, kDNSServiceFlagsShareConnection
,
201 kDNSServiceInterfaceIndexLocalOnly
,
202 "_printer._tcp", NULL
, browse_local_callback
, devices
);
204 pdl_datastream_ref
= main_ref
;
205 DNSServiceBrowse(&pdl_datastream_ref
, kDNSServiceFlagsShareConnection
, 0,
206 "_pdl-datastream._tcp", NULL
, browse_callback
, devices
);
208 printer_ref
= main_ref
;
209 DNSServiceBrowse(&printer_ref
, kDNSServiceFlagsShareConnection
, 0,
210 "_printer._tcp", NULL
, browse_callback
, devices
);
212 riousbprint_ref
= main_ref
;
213 DNSServiceBrowse(&riousbprint_ref
, kDNSServiceFlagsShareConnection
, 0,
214 "_riousbprint._tcp", NULL
, browse_callback
, devices
);
217 * Loop until we are killed...
228 if (select(fd
+ 1, &input
, NULL
, NULL
, &timeout
) < 0)
231 if (FD_ISSET(fd
, &input
))
234 * Process results of our browsing...
237 DNSServiceProcessResult(main_ref
);
242 * Announce any devices we've found...
245 cups_device_t
*best
; /* Best matching device */
246 char device_uri
[1024]; /* Device URI */
247 int count
; /* Number of queries */
248 static const char * const schemes
[] =
249 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
250 /* URI schemes for devices */
253 for (device
= (cups_device_t
*)cupsArrayFirst(devices
),
254 best
= NULL
, count
= 0;
256 device
= (cups_device_t
*)cupsArrayNext(devices
))
257 if (!device
->ref
&& !device
->sent
)
260 * Found the device, now get the TXT record(s) for it...
265 device
->ref
= main_ref
;
267 fprintf(stderr
, "DEBUG: Querying \"%s\"...\n", device
->fullName
);
269 if (DNSServiceQueryRecord(&(device
->ref
),
270 kDNSServiceFlagsShareConnection
,
271 0, device
->fullName
, kDNSServiceType_TXT
,
272 kDNSServiceClass_IN
, query_callback
,
273 devices
) != kDNSServiceErr_NoError
)
274 fputs("ERROR: Unable to query for TXT records!\n", stderr
);
279 else if (!device
->sent
)
282 * Got the TXT records, now report the device...
285 DNSServiceRefDeallocate(device
->ref
);
290 else if (strcasecmp(best
->name
, device
->name
) ||
291 strcasecmp(best
->domain
, device
->domain
))
293 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
294 schemes
[best
->type
], NULL
, best
->fullName
, 0,
295 best
->cups_shared
? "/cups" : "/");
297 cupsBackendReport("network", device_uri
, best
->make_and_model
,
298 best
->name
, NULL
, NULL
);
302 else if (best
->priority
> device
->priority
||
303 (best
->priority
== device
->priority
&&
304 best
->type
< device
->type
))
315 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
316 schemes
[best
->type
], NULL
, best
->fullName
, 0,
317 best
->cups_shared
? "/cups" : "/");
319 cupsBackendReport("network", device_uri
, best
->make_and_model
,
320 best
->name
, NULL
, NULL
);
329 * 'browse_callback()' - Browse devices.
334 DNSServiceRef sdRef
, /* I - Service reference */
335 DNSServiceFlags flags
, /* I - Option flags */
336 uint32_t interfaceIndex
, /* I - Interface number */
337 DNSServiceErrorType errorCode
, /* I - Error, if any */
338 const char *serviceName
, /* I - Name of service/device */
339 const char *regtype
, /* I - Type of service */
340 const char *replyDomain
, /* I - Service domain */
341 void *context
) /* I - Devices array */
343 fprintf(stderr
, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
344 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
345 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
346 sdRef
, flags
, interfaceIndex
, errorCode
,
347 serviceName
? serviceName
: "(null)",
348 regtype
? regtype
: "(null)",
349 replyDomain
? replyDomain
: "(null)",
353 * Only process "add" data...
356 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
363 get_device((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
368 * 'browse_local_callback()' - Browse local devices.
372 browse_local_callback(
373 DNSServiceRef sdRef
, /* I - Service reference */
374 DNSServiceFlags flags
, /* I - Option flags */
375 uint32_t interfaceIndex
, /* I - Interface number */
376 DNSServiceErrorType errorCode
, /* I - Error, if any */
377 const char *serviceName
, /* I - Name of service/device */
378 const char *regtype
, /* I - Type of service */
379 const char *replyDomain
, /* I - Service domain */
380 void *context
) /* I - Devices array */
382 cups_device_t
*device
; /* Device */
385 fprintf(stderr
, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
386 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
387 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
388 sdRef
, flags
, interfaceIndex
, errorCode
,
389 serviceName
? serviceName
: "(null)",
390 regtype
? regtype
: "(null)",
391 replyDomain
? replyDomain
: "(null)",
395 * Only process "add" data...
398 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
405 device
= get_device((cups_array_t
*)context
, serviceName
, regtype
,
409 * Hide locally-registered devices...
412 fprintf(stderr
, "DEBUG: Hiding local printer \"%s\"...\n",
419 * 'compare_devices()' - Compare two devices.
422 static int /* O - Result of comparison */
423 compare_devices(cups_device_t
*a
, /* I - First device */
424 cups_device_t
*b
) /* I - Second device */
426 int result
= strcmp(a
->name
, b
->name
);
431 return (strcmp(a
->domain
, b
->domain
));
436 * 'exec_backend()' - Execute the backend that corresponds to the
437 * resolved service name.
441 exec_backend(char **argv
) /* I - Command-line arguments */
443 const char *resolved_uri
, /* Resolved device URI */
444 *cups_serverbin
; /* Location of programs */
445 char scheme
[1024], /* Scheme from URI */
446 *ptr
, /* Pointer into scheme */
447 filename
[1024]; /* Backend filename */
451 * Resolve the device URI...
454 if ((resolved_uri
= cupsBackendDeviceURI(argv
)) == NULL
)
455 exit(CUPS_BACKEND_FAILED
);
458 * Extract the scheme from the URI...
461 strlcpy(scheme
, resolved_uri
, sizeof(scheme
));
462 if ((ptr
= strchr(scheme
, ':')) != NULL
)
466 * Get the filename of the backend...
469 if ((cups_serverbin
= getenv("CUPS_SERVERBIN")) == NULL
)
470 cups_serverbin
= CUPS_SERVERBIN
;
472 snprintf(filename
, sizeof(filename
), "%s/backend/%s", cups_serverbin
, scheme
);
475 * Overwrite the device URIs and run the new backend...
478 setenv("DEVICE_URI", resolved_uri
, 1);
480 argv
[0] = (char *)resolved_uri
;
482 fprintf(stderr
, "DEBUG: Executing backend \"%s\"...\n", filename
);
484 execv(filename
, argv
);
486 fprintf(stderr
, "ERROR: Unable to execute backend \"%s\": %s\n", filename
,
488 exit(CUPS_BACKEND_STOP
);
493 * 'get_device()' - Create or update a device.
496 static cups_device_t
* /* O - Device */
497 get_device(cups_array_t
*devices
, /* I - Device array */
498 const char *serviceName
, /* I - Name of service/device */
499 const char *regtype
, /* I - Type of service */
500 const char *replyDomain
) /* I - Service domain */
502 cups_device_t key
, /* Search key */
503 *device
; /* Device */
504 char fullName
[1024]; /* Full name for query */
508 * See if this is a new device...
511 key
.name
= (char *)serviceName
;
512 key
.domain
= (char *)replyDomain
;
514 if (!strcmp(regtype
, "_ipp._tcp.") ||
515 !strcmp(regtype
, "_ipp-tls._tcp."))
516 key
.type
= CUPS_DEVICE_IPP
;
517 else if (!strcmp(regtype
, "_fax-ipp._tcp."))
518 key
.type
= CUPS_DEVICE_FAX_IPP
;
519 else if (!strcmp(regtype
, "_printer._tcp."))
520 key
.type
= CUPS_DEVICE_PRINTER
;
521 else if (!strcmp(regtype
, "_pdl-datastream._tcp."))
522 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
524 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
526 for (device
= cupsArrayFind(devices
, &key
);
528 device
= cupsArrayNext(devices
))
529 if (strcasecmp(device
->name
, key
.name
) ||
530 strcasecmp(device
->domain
, key
.domain
))
532 else if (device
->type
== key
.type
)
536 * Yes, add the device...
539 fprintf(stderr
, "DEBUG: Found \"%s.%s%s\"...\n", serviceName
, regtype
,
542 device
= calloc(sizeof(cups_device_t
), 1);
543 device
->name
= strdup(serviceName
);
544 device
->domain
= strdup(replyDomain
);
545 device
->type
= key
.type
;
546 device
->priority
= 50;
548 cupsArrayAdd(devices
, device
);
551 * Set the "full name" of this service, which is used for queries...
554 snprintf(fullName
, sizeof(fullName
), "%s.%s%s", serviceName
, regtype
,
556 device
->fullName
= strdup(fullName
);
563 * 'query_callback()' - Process query data.
568 DNSServiceRef sdRef
, /* I - Service reference */
569 DNSServiceFlags flags
, /* I - Data flags */
570 uint32_t interfaceIndex
, /* I - Interface */
571 DNSServiceErrorType errorCode
, /* I - Error, if any */
572 const char *fullName
, /* I - Full service name */
573 uint16_t rrtype
, /* I - Record type */
574 uint16_t rrclass
, /* I - Record class */
575 uint16_t rdlen
, /* I - Length of record data */
576 const void *rdata
, /* I - Record data */
577 uint32_t ttl
, /* I - Time-to-live */
578 void *context
) /* I - Devices array */
580 cups_array_t
*devices
; /* Device array */
581 char name
[1024], /* Service name */
582 *ptr
; /* Pointer into name */
583 cups_device_t key
, /* Search key */
584 *device
; /* Device */
587 fprintf(stderr
, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
588 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
589 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
591 sdRef
, flags
, interfaceIndex
, errorCode
,
592 fullName
? fullName
: "(null)", rrtype
, rrclass
, rdlen
, rdata
, ttl
,
596 * Only process "add" data...
599 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
603 * Lookup the service in the devices array.
606 devices
= (cups_array_t
*)context
;
609 unquote(name
, fullName
, sizeof(name
));
611 if ((key
.domain
= strstr(name
, "._tcp.")) != NULL
)
614 key
.domain
= (char *)"local.";
616 if ((ptr
= strstr(name
, "._")) != NULL
)
619 if (strstr(fullName
, "_ipp._tcp.") ||
620 strstr(fullName
, "_ipp-tls._tcp."))
621 key
.type
= CUPS_DEVICE_IPP
;
622 else if (strstr(fullName
, "_fax-ipp._tcp."))
623 key
.type
= CUPS_DEVICE_FAX_IPP
;
624 else if (strstr(fullName
, "_printer._tcp."))
625 key
.type
= CUPS_DEVICE_PRINTER
;
626 else if (strstr(fullName
, "_pdl-datastream._tcp."))
627 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
629 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
631 for (device
= cupsArrayFind(devices
, &key
);
633 device
= cupsArrayNext(devices
))
635 if (strcasecmp(device
->name
, key
.name
) ||
636 strcasecmp(device
->domain
, key
.domain
))
641 else if (device
->type
== key
.type
)
644 * Found it, pull out the priority and make and model from the TXT
645 * record and save it...
648 const void *value
; /* Pointer to value */
649 uint8_t valueLen
; /* Length of value (max 255) */
650 char make_and_model
[512], /* Manufacturer and model */
651 model
[256], /* Model */
652 priority
[256]; /* Priority */
655 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "priority", &valueLen
);
657 if (value
&& valueLen
)
659 memcpy(priority
, value
, valueLen
);
660 priority
[valueLen
] = '\0';
661 device
->priority
= atoi(priority
);
664 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MFG",
666 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MANUFACTURER",
669 if (value
&& valueLen
)
671 memcpy(make_and_model
, value
, valueLen
);
672 make_and_model
[valueLen
] = '\0';
675 make_and_model
[0] = '\0';
677 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MDL",
679 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MODEL", &valueLen
);
681 if (value
&& valueLen
)
683 memcpy(model
, value
, valueLen
);
684 model
[valueLen
] = '\0';
686 else if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "product",
687 &valueLen
)) != NULL
&& valueLen
> 2)
689 if (((char *)value
)[0] == '(')
692 * Strip parenthesis...
695 memcpy(model
, value
+ 1, valueLen
- 2);
696 model
[valueLen
- 2] = '\0';
700 memcpy(model
, value
, valueLen
);
701 model
[valueLen
] = '\0';
704 if (!strcasecmp(model
, "GPL Ghostscript") ||
705 !strcasecmp(model
, "GNU Ghostscript") ||
706 !strcasecmp(model
, "ESP Ghostscript"))
708 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "ty",
711 memcpy(model
, value
, valueLen
);
712 model
[valueLen
] = '\0';
714 if ((ptr
= strchr(model
, ',')) != NULL
)
718 strcpy(model
, "Unknown");
722 strcpy(model
, "Unknown");
724 if (device
->make_and_model
)
725 free(device
->make_and_model
);
727 if (make_and_model
[0])
729 strlcat(make_and_model
, " ", sizeof(make_and_model
));
730 strlcat(make_and_model
, model
, sizeof(make_and_model
));
731 device
->make_and_model
= strdup(make_and_model
);
734 device
->make_and_model
= strdup(model
);
736 if ((device
->type
== CUPS_DEVICE_IPP
||
737 device
->type
== CUPS_DEVICE_PRINTER
) &&
738 TXTRecordGetValuePtr(rdlen
, rdata
, "printer-type", &valueLen
))
741 * This is a CUPS printer!
744 device
->cups_shared
= 1;
746 if (device
->type
== CUPS_DEVICE_PRINTER
)
755 fprintf(stderr
, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName
);
760 * 'unquote()' - Unquote a name string.
764 unquote(char *dst
, /* I - Destination buffer */
765 const char *src
, /* I - Source string */
766 size_t dstsize
) /* I - Size of destination buffer */
768 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
771 while (*src
&& dst
< dstend
)
776 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
777 isdigit(src
[2] & 255))
779 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';