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 DNSServiceErrorType status
; /* DNS query status */
246 cups_device_t
*best
; /* Best matching device */
247 char device_uri
[1024]; /* Device URI */
248 int count
; /* Number of queries */
249 static const char * const schemes
[] =
250 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
251 /* URI schemes for devices */
254 for (device
= (cups_device_t
*)cupsArrayFirst(devices
),
255 best
= NULL
, count
= 0;
257 device
= (cups_device_t
*)cupsArrayNext(devices
))
258 if (!device
->ref
&& !device
->sent
)
261 * Found the device, now get the TXT record(s) for it...
266 device
->ref
= main_ref
;
268 fprintf(stderr
, "DEBUG: Querying \"%s\"...\n", device
->fullName
);
270 status
= DNSServiceQueryRecord(&(device
->ref
),
271 kDNSServiceFlagsShareConnection
,
274 kDNSServiceClass_IN
, query_callback
,
276 if (status
!= kDNSServiceErr_NoError
)
278 fputs("ERROR: Unable to query for TXT records!\n", stderr
);
279 fprintf(stderr
, "DEBUG: DNSServiceQueryRecord returned %d\n",
286 else if (!device
->sent
)
289 * Got the TXT records, now report the device...
292 DNSServiceRefDeallocate(device
->ref
);
297 else if (strcasecmp(best
->name
, device
->name
) ||
298 strcasecmp(best
->domain
, device
->domain
))
300 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
301 schemes
[best
->type
], NULL
, best
->fullName
, 0,
302 best
->cups_shared
? "/cups" : "/");
304 cupsBackendReport("network", device_uri
, best
->make_and_model
,
305 best
->name
, NULL
, NULL
);
309 else if (best
->priority
> device
->priority
||
310 (best
->priority
== device
->priority
&&
311 best
->type
< device
->type
))
322 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
323 schemes
[best
->type
], NULL
, best
->fullName
, 0,
324 best
->cups_shared
? "/cups" : "/");
326 cupsBackendReport("network", device_uri
, best
->make_and_model
,
327 best
->name
, NULL
, NULL
);
336 * 'browse_callback()' - Browse devices.
341 DNSServiceRef sdRef
, /* I - Service reference */
342 DNSServiceFlags flags
, /* I - Option flags */
343 uint32_t interfaceIndex
, /* I - Interface number */
344 DNSServiceErrorType errorCode
, /* I - Error, if any */
345 const char *serviceName
, /* I - Name of service/device */
346 const char *regtype
, /* I - Type of service */
347 const char *replyDomain
, /* I - Service domain */
348 void *context
) /* I - Devices array */
350 fprintf(stderr
, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
351 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
352 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
353 sdRef
, flags
, interfaceIndex
, errorCode
,
354 serviceName
? serviceName
: "(null)",
355 regtype
? regtype
: "(null)",
356 replyDomain
? replyDomain
: "(null)",
360 * Only process "add" data...
363 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
370 get_device((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
375 * 'browse_local_callback()' - Browse local devices.
379 browse_local_callback(
380 DNSServiceRef sdRef
, /* I - Service reference */
381 DNSServiceFlags flags
, /* I - Option flags */
382 uint32_t interfaceIndex
, /* I - Interface number */
383 DNSServiceErrorType errorCode
, /* I - Error, if any */
384 const char *serviceName
, /* I - Name of service/device */
385 const char *regtype
, /* I - Type of service */
386 const char *replyDomain
, /* I - Service domain */
387 void *context
) /* I - Devices array */
389 cups_device_t
*device
; /* Device */
392 fprintf(stderr
, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
393 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
394 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
395 sdRef
, flags
, interfaceIndex
, errorCode
,
396 serviceName
? serviceName
: "(null)",
397 regtype
? regtype
: "(null)",
398 replyDomain
? replyDomain
: "(null)",
402 * Only process "add" data...
405 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
412 device
= get_device((cups_array_t
*)context
, serviceName
, regtype
,
416 * Hide locally-registered devices...
419 fprintf(stderr
, "DEBUG: Hiding local printer \"%s\"...\n",
426 * 'compare_devices()' - Compare two devices.
429 static int /* O - Result of comparison */
430 compare_devices(cups_device_t
*a
, /* I - First device */
431 cups_device_t
*b
) /* I - Second device */
433 int result
= strcmp(a
->name
, b
->name
);
438 return (strcmp(a
->domain
, b
->domain
));
443 * 'exec_backend()' - Execute the backend that corresponds to the
444 * resolved service name.
448 exec_backend(char **argv
) /* I - Command-line arguments */
450 const char *resolved_uri
, /* Resolved device URI */
451 *cups_serverbin
; /* Location of programs */
452 char scheme
[1024], /* Scheme from URI */
453 *ptr
, /* Pointer into scheme */
454 filename
[1024]; /* Backend filename */
458 * Resolve the device URI...
461 if ((resolved_uri
= cupsBackendDeviceURI(argv
)) == NULL
)
462 exit(CUPS_BACKEND_FAILED
);
465 * Extract the scheme from the URI...
468 strlcpy(scheme
, resolved_uri
, sizeof(scheme
));
469 if ((ptr
= strchr(scheme
, ':')) != NULL
)
473 * Get the filename of the backend...
476 if ((cups_serverbin
= getenv("CUPS_SERVERBIN")) == NULL
)
477 cups_serverbin
= CUPS_SERVERBIN
;
479 snprintf(filename
, sizeof(filename
), "%s/backend/%s", cups_serverbin
, scheme
);
482 * Overwrite the device URIs and run the new backend...
485 setenv("DEVICE_URI", resolved_uri
, 1);
487 argv
[0] = (char *)resolved_uri
;
489 fprintf(stderr
, "DEBUG: Executing backend \"%s\"...\n", filename
);
491 execv(filename
, argv
);
493 fprintf(stderr
, "ERROR: Unable to execute backend \"%s\": %s\n", filename
,
495 exit(CUPS_BACKEND_STOP
);
500 * 'get_device()' - Create or update a device.
503 static cups_device_t
* /* O - Device */
504 get_device(cups_array_t
*devices
, /* I - Device array */
505 const char *serviceName
, /* I - Name of service/device */
506 const char *regtype
, /* I - Type of service */
507 const char *replyDomain
) /* I - Service domain */
509 cups_device_t key
, /* Search key */
510 *device
; /* Device */
511 char fullName
[kDNSServiceMaxDomainName
];
512 /* Full name for query */
516 * See if this is a new device...
519 key
.name
= (char *)serviceName
;
520 key
.domain
= (char *)replyDomain
;
522 if (!strcmp(regtype
, "_ipp._tcp.") ||
523 !strcmp(regtype
, "_ipp-tls._tcp."))
524 key
.type
= CUPS_DEVICE_IPP
;
525 else if (!strcmp(regtype
, "_fax-ipp._tcp."))
526 key
.type
= CUPS_DEVICE_FAX_IPP
;
527 else if (!strcmp(regtype
, "_printer._tcp."))
528 key
.type
= CUPS_DEVICE_PRINTER
;
529 else if (!strcmp(regtype
, "_pdl-datastream._tcp."))
530 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
532 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
534 for (device
= cupsArrayFind(devices
, &key
);
536 device
= cupsArrayNext(devices
))
537 if (strcasecmp(device
->name
, key
.name
) ||
538 strcasecmp(device
->domain
, key
.domain
))
540 else if (device
->type
== key
.type
)
544 * Yes, add the device...
547 fprintf(stderr
, "DEBUG: Found \"%s.%s%s\"...\n", serviceName
, regtype
,
550 device
= calloc(sizeof(cups_device_t
), 1);
551 device
->name
= strdup(serviceName
);
552 device
->domain
= strdup(replyDomain
);
553 device
->type
= key
.type
;
554 device
->priority
= 50;
556 cupsArrayAdd(devices
, device
);
559 * Set the "full name" of this service, which is used for queries...
562 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
563 device
->fullName
= strdup(fullName
);
570 * 'query_callback()' - Process query data.
575 DNSServiceRef sdRef
, /* I - Service reference */
576 DNSServiceFlags flags
, /* I - Data flags */
577 uint32_t interfaceIndex
, /* I - Interface */
578 DNSServiceErrorType errorCode
, /* I - Error, if any */
579 const char *fullName
, /* I - Full service name */
580 uint16_t rrtype
, /* I - Record type */
581 uint16_t rrclass
, /* I - Record class */
582 uint16_t rdlen
, /* I - Length of record data */
583 const void *rdata
, /* I - Record data */
584 uint32_t ttl
, /* I - Time-to-live */
585 void *context
) /* I - Devices array */
587 cups_array_t
*devices
; /* Device array */
588 char name
[1024], /* Service name */
589 *ptr
; /* Pointer into name */
590 cups_device_t key
, /* Search key */
591 *device
; /* Device */
594 fprintf(stderr
, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
595 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
596 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
598 sdRef
, flags
, interfaceIndex
, errorCode
,
599 fullName
? fullName
: "(null)", rrtype
, rrclass
, rdlen
, rdata
, ttl
,
603 * Only process "add" data...
606 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
610 * Lookup the service in the devices array.
613 devices
= (cups_array_t
*)context
;
616 unquote(name
, fullName
, sizeof(name
));
618 if ((key
.domain
= strstr(name
, "._tcp.")) != NULL
)
621 key
.domain
= (char *)"local.";
623 if ((ptr
= strstr(name
, "._")) != NULL
)
626 if (strstr(fullName
, "_ipp._tcp.") ||
627 strstr(fullName
, "_ipp-tls._tcp."))
628 key
.type
= CUPS_DEVICE_IPP
;
629 else if (strstr(fullName
, "_fax-ipp._tcp."))
630 key
.type
= CUPS_DEVICE_FAX_IPP
;
631 else if (strstr(fullName
, "_printer._tcp."))
632 key
.type
= CUPS_DEVICE_PRINTER
;
633 else if (strstr(fullName
, "_pdl-datastream._tcp."))
634 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
636 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
638 for (device
= cupsArrayFind(devices
, &key
);
640 device
= cupsArrayNext(devices
))
642 if (strcasecmp(device
->name
, key
.name
) ||
643 strcasecmp(device
->domain
, key
.domain
))
648 else if (device
->type
== key
.type
)
651 * Found it, pull out the priority and make and model from the TXT
652 * record and save it...
655 const void *value
; /* Pointer to value */
656 uint8_t valueLen
; /* Length of value (max 255) */
657 char make_and_model
[512], /* Manufacturer and model */
658 model
[256], /* Model */
659 priority
[256]; /* Priority */
662 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "priority", &valueLen
);
664 if (value
&& valueLen
)
666 memcpy(priority
, value
, valueLen
);
667 priority
[valueLen
] = '\0';
668 device
->priority
= atoi(priority
);
671 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MFG",
673 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MANUFACTURER",
676 if (value
&& valueLen
)
678 memcpy(make_and_model
, value
, valueLen
);
679 make_and_model
[valueLen
] = '\0';
682 make_and_model
[0] = '\0';
684 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MDL",
686 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MODEL", &valueLen
);
688 if (value
&& valueLen
)
690 memcpy(model
, value
, valueLen
);
691 model
[valueLen
] = '\0';
693 else if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "product",
694 &valueLen
)) != NULL
&& valueLen
> 2)
696 if (((char *)value
)[0] == '(')
699 * Strip parenthesis...
702 memcpy(model
, value
+ 1, valueLen
- 2);
703 model
[valueLen
- 2] = '\0';
707 memcpy(model
, value
, valueLen
);
708 model
[valueLen
] = '\0';
711 if (!strcasecmp(model
, "GPL Ghostscript") ||
712 !strcasecmp(model
, "GNU Ghostscript") ||
713 !strcasecmp(model
, "ESP Ghostscript"))
715 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "ty",
718 memcpy(model
, value
, valueLen
);
719 model
[valueLen
] = '\0';
721 if ((ptr
= strchr(model
, ',')) != NULL
)
725 strcpy(model
, "Unknown");
729 strcpy(model
, "Unknown");
731 if (device
->make_and_model
)
732 free(device
->make_and_model
);
734 if (make_and_model
[0])
736 strlcat(make_and_model
, " ", sizeof(make_and_model
));
737 strlcat(make_and_model
, model
, sizeof(make_and_model
));
738 device
->make_and_model
= strdup(make_and_model
);
741 device
->make_and_model
= strdup(model
);
743 if ((device
->type
== CUPS_DEVICE_IPP
||
744 device
->type
== CUPS_DEVICE_PRINTER
) &&
745 TXTRecordGetValuePtr(rdlen
, rdata
, "printer-type", &valueLen
))
748 * This is a CUPS printer!
751 device
->cups_shared
= 1;
753 if (device
->type
== CUPS_DEVICE_PRINTER
)
762 fprintf(stderr
, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName
);
767 * 'unquote()' - Unquote a name string.
771 unquote(char *dst
, /* I - Destination buffer */
772 const char *src
, /* I - Source string */
773 size_t dstsize
) /* I - Size of destination buffer */
775 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
778 while (*src
&& dst
< dstend
)
783 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
784 isdigit(src
[2] & 255))
786 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';