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 DNSServiceRef main_ref
, /* Main service reference */
109 fax_ipp_ref
, /* IPP fax service reference */
110 ipp_ref
, /* IPP service reference */
111 ipp_tls_ref
, /* IPP w/TLS service reference */
112 local_fax_ipp_ref
, /* Local IPP fax service reference */
113 local_ipp_ref
, /* Local IPP service reference */
114 local_ipp_tls_ref
, /* Local IPP w/TLS service reference */
115 local_printer_ref
, /* Local LPD service reference */
116 pdl_datastream_ref
, /* AppSocket service reference */
117 printer_ref
, /* LPD service reference */
118 riousbprint_ref
; /* Remote IO service reference */
119 int fd
; /* Main file descriptor */
120 fd_set input
; /* Input set for select() */
121 struct timeval timeout
; /* Timeout for select() */
122 cups_array_t
*devices
; /* Device array */
123 cups_device_t
*device
; /* Current device */
127 * Check command-line...
130 setbuf(stderr
, NULL
);
136 fputs("Usage: mdns job user title copies options [filename(s)]\n", stderr
);
141 * Create an array to track devices...
144 devices
= cupsArrayNew((cups_array_func_t
)compare_devices
, NULL
);
147 * Browse for different kinds of printers...
150 if (DNSServiceCreateConnection(&main_ref
) != kDNSServiceErr_NoError
)
152 perror("ERROR: Unable to create service connection");
156 fd
= DNSServiceRefSockFD(main_ref
);
158 fax_ipp_ref
= main_ref
;
159 DNSServiceBrowse(&fax_ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
160 "_fax-ipp._tcp", NULL
, browse_callback
, devices
);
163 DNSServiceBrowse(&ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
164 "_ipp._tcp", NULL
, browse_callback
, devices
);
166 ipp_tls_ref
= main_ref
;
167 DNSServiceBrowse(&ipp_tls_ref
, kDNSServiceFlagsShareConnection
, 0,
168 "_ipp-tls._tcp", NULL
, browse_callback
, devices
);
170 local_fax_ipp_ref
= main_ref
;
171 DNSServiceBrowse(&local_fax_ipp_ref
, kDNSServiceFlagsShareConnection
,
172 kDNSServiceInterfaceIndexLocalOnly
,
173 "_fax-ipp._tcp", NULL
, browse_local_callback
, devices
);
175 local_ipp_ref
= main_ref
;
176 DNSServiceBrowse(&local_ipp_ref
, kDNSServiceFlagsShareConnection
,
177 kDNSServiceInterfaceIndexLocalOnly
,
178 "_ipp._tcp", NULL
, browse_local_callback
, devices
);
180 local_ipp_tls_ref
= main_ref
;
181 DNSServiceBrowse(&local_ipp_tls_ref
, kDNSServiceFlagsShareConnection
,
182 kDNSServiceInterfaceIndexLocalOnly
,
183 "_ipp-tls._tcp", NULL
, browse_local_callback
, devices
);
185 local_printer_ref
= main_ref
;
186 DNSServiceBrowse(&local_printer_ref
, kDNSServiceFlagsShareConnection
,
187 kDNSServiceInterfaceIndexLocalOnly
,
188 "_printer._tcp", NULL
, browse_local_callback
, devices
);
190 pdl_datastream_ref
= main_ref
;
191 DNSServiceBrowse(&pdl_datastream_ref
, kDNSServiceFlagsShareConnection
, 0,
192 "_pdl-datastream._tcp", NULL
, browse_callback
, devices
);
194 printer_ref
= main_ref
;
195 DNSServiceBrowse(&printer_ref
, kDNSServiceFlagsShareConnection
, 0,
196 "_printer._tcp", NULL
, browse_callback
, devices
);
198 riousbprint_ref
= main_ref
;
199 DNSServiceBrowse(&riousbprint_ref
, kDNSServiceFlagsShareConnection
, 0,
200 "_riousbprint._tcp", NULL
, browse_callback
, devices
);
203 * Loop until we are killed...
214 if (select(fd
+ 1, &input
, NULL
, NULL
, &timeout
) < 0)
217 if (FD_ISSET(fd
, &input
))
220 * Process results of our browsing...
223 DNSServiceProcessResult(main_ref
);
228 * Announce any devices we've found...
231 cups_device_t
*best
; /* Best matching device */
232 char device_uri
[1024]; /* Device URI */
233 int count
; /* Number of queries */
234 static const char * const schemes
[] =
235 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
236 /* URI schemes for devices */
239 for (device
= (cups_device_t
*)cupsArrayFirst(devices
),
240 best
= NULL
, count
= 0;
242 device
= (cups_device_t
*)cupsArrayNext(devices
))
243 if (!device
->ref
&& !device
->sent
)
246 * Found the device, now get the TXT record(s) for it...
251 device
->ref
= main_ref
;
253 fprintf(stderr
, "DEBUG: Querying \"%s\"...\n", device
->fullName
);
255 if (DNSServiceQueryRecord(&(device
->ref
),
256 kDNSServiceFlagsShareConnection
,
257 0, device
->fullName
, kDNSServiceType_TXT
,
258 kDNSServiceClass_IN
, query_callback
,
259 devices
) != kDNSServiceErr_NoError
)
260 fputs("ERROR: Unable to query for TXT records!\n", stderr
);
265 else if (!device
->sent
)
268 * Got the TXT records, now report the device...
271 DNSServiceRefDeallocate(device
->ref
);
276 else if (strcasecmp(best
->name
, device
->name
) ||
277 strcasecmp(best
->domain
, device
->domain
))
279 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
280 schemes
[best
->type
], NULL
, best
->fullName
, 0,
281 best
->cups_shared
? "/cups" : "/");
283 cupsBackendReport("network", device_uri
, best
->make_and_model
,
284 best
->name
, NULL
, NULL
);
288 else if (best
->priority
> device
->priority
||
289 (best
->priority
== device
->priority
&&
290 best
->type
< device
->type
))
301 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
302 schemes
[best
->type
], NULL
, best
->fullName
, 0,
303 best
->cups_shared
? "/cups" : "/");
305 cupsBackendReport("network", device_uri
, best
->make_and_model
,
306 best
->name
, NULL
, NULL
);
315 * 'browse_callback()' - Browse devices.
320 DNSServiceRef sdRef
, /* I - Service reference */
321 DNSServiceFlags flags
, /* I - Option flags */
322 uint32_t interfaceIndex
, /* I - Interface number */
323 DNSServiceErrorType errorCode
, /* I - Error, if any */
324 const char *serviceName
, /* I - Name of service/device */
325 const char *regtype
, /* I - Type of service */
326 const char *replyDomain
, /* I - Service domain */
327 void *context
) /* I - Devices array */
329 fprintf(stderr
, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
330 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
331 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
332 sdRef
, flags
, interfaceIndex
, errorCode
,
333 serviceName
? serviceName
: "(null)",
334 regtype
? regtype
: "(null)",
335 replyDomain
? replyDomain
: "(null)",
339 * Only process "add" data...
342 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
349 get_device((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
354 * 'browse_local_callback()' - Browse local devices.
358 browse_local_callback(
359 DNSServiceRef sdRef
, /* I - Service reference */
360 DNSServiceFlags flags
, /* I - Option flags */
361 uint32_t interfaceIndex
, /* I - Interface number */
362 DNSServiceErrorType errorCode
, /* I - Error, if any */
363 const char *serviceName
, /* I - Name of service/device */
364 const char *regtype
, /* I - Type of service */
365 const char *replyDomain
, /* I - Service domain */
366 void *context
) /* I - Devices array */
368 cups_device_t
*device
; /* Device */
371 fprintf(stderr
, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
372 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
373 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
374 sdRef
, flags
, interfaceIndex
, errorCode
,
375 serviceName
? serviceName
: "(null)",
376 regtype
? regtype
: "(null)",
377 replyDomain
? replyDomain
: "(null)",
381 * Only process "add" data...
384 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
391 device
= get_device((cups_array_t
*)context
, serviceName
, regtype
,
395 * Hide locally-registered devices...
398 fprintf(stderr
, "DEBUG: Hiding local printer \"%s\"...\n",
405 * 'compare_devices()' - Compare two devices.
408 static int /* O - Result of comparison */
409 compare_devices(cups_device_t
*a
, /* I - First device */
410 cups_device_t
*b
) /* I - Second device */
412 int result
= strcmp(a
->name
, b
->name
);
417 return (strcmp(a
->domain
, b
->domain
));
422 * 'exec_backend()' - Execute the backend that corresponds to the
423 * resolved service name.
427 exec_backend(char **argv
) /* I - Command-line arguments */
429 const char *resolved_uri
, /* Resolved device URI */
430 *cups_serverbin
; /* Location of programs */
431 char scheme
[1024], /* Scheme from URI */
432 *ptr
, /* Pointer into scheme */
433 filename
[1024]; /* Backend filename */
437 * Resolve the device URI...
440 if ((resolved_uri
= cupsBackendDeviceURI(argv
)) == NULL
)
441 exit(CUPS_BACKEND_FAILED
);
444 * Extract the scheme from the URI...
447 strlcpy(scheme
, resolved_uri
, sizeof(scheme
));
448 if ((ptr
= strchr(scheme
, ':')) != NULL
)
452 * Get the filename of the backend...
455 if ((cups_serverbin
= getenv("CUPS_SERVERBIN")) == NULL
)
456 cups_serverbin
= CUPS_SERVERBIN
;
458 snprintf(filename
, sizeof(filename
), "%s/backend/%s", cups_serverbin
, scheme
);
461 * Overwrite the device URIs and run the new backend...
464 setenv("DEVICE_URI", resolved_uri
, 1);
466 argv
[0] = (char *)resolved_uri
;
468 fprintf(stderr
, "DEBUG: Executing backend \"%s\"...\n", filename
);
470 execv(filename
, argv
);
472 fprintf(stderr
, "ERROR: Unable to execute backend \"%s\": %s\n", filename
,
474 exit(CUPS_BACKEND_STOP
);
479 * 'get_device()' - Create or update a device.
482 static cups_device_t
* /* O - Device */
483 get_device(cups_array_t
*devices
, /* I - Device array */
484 const char *serviceName
, /* I - Name of service/device */
485 const char *regtype
, /* I - Type of service */
486 const char *replyDomain
) /* I - Service domain */
488 cups_device_t key
, /* Search key */
489 *device
; /* Device */
490 char fullName
[1024]; /* Full name for query */
494 * See if this is a new device...
497 key
.name
= (char *)serviceName
;
498 key
.domain
= (char *)replyDomain
;
500 if (!strcmp(regtype
, "_ipp._tcp.") ||
501 !strcmp(regtype
, "_ipp-tls._tcp."))
502 key
.type
= CUPS_DEVICE_IPP
;
503 else if (!strcmp(regtype
, "_fax-ipp._tcp."))
504 key
.type
= CUPS_DEVICE_FAX_IPP
;
505 else if (!strcmp(regtype
, "_printer._tcp."))
506 key
.type
= CUPS_DEVICE_PRINTER
;
507 else if (!strcmp(regtype
, "_pdl-datastream._tcp."))
508 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
510 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
512 for (device
= cupsArrayFind(devices
, &key
);
514 device
= cupsArrayNext(devices
))
515 if (strcasecmp(device
->name
, key
.name
) ||
516 strcasecmp(device
->domain
, key
.domain
))
518 else if (device
->type
== key
.type
)
522 * Yes, add the device...
525 fprintf(stderr
, "DEBUG: Found \"%s.%s%s\"...\n", serviceName
, regtype
,
528 device
= calloc(sizeof(cups_device_t
), 1);
529 device
->name
= strdup(serviceName
);
530 device
->domain
= strdup(replyDomain
);
531 device
->type
= key
.type
;
532 device
->priority
= 50;
534 cupsArrayAdd(devices
, device
);
537 * Set the "full name" of this service, which is used for queries...
540 snprintf(fullName
, sizeof(fullName
), "%s.%s%s", serviceName
, regtype
,
542 device
->fullName
= strdup(fullName
);
549 * 'query_callback()' - Process query data.
554 DNSServiceRef sdRef
, /* I - Service reference */
555 DNSServiceFlags flags
, /* I - Data flags */
556 uint32_t interfaceIndex
, /* I - Interface */
557 DNSServiceErrorType errorCode
, /* I - Error, if any */
558 const char *fullName
, /* I - Full service name */
559 uint16_t rrtype
, /* I - Record type */
560 uint16_t rrclass
, /* I - Record class */
561 uint16_t rdlen
, /* I - Length of record data */
562 const void *rdata
, /* I - Record data */
563 uint32_t ttl
, /* I - Time-to-live */
564 void *context
) /* I - Devices array */
566 cups_array_t
*devices
; /* Device array */
567 char name
[1024], /* Service name */
568 *ptr
; /* Pointer into name */
569 cups_device_t key
, /* Search key */
570 *device
; /* Device */
573 fprintf(stderr
, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
574 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
575 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
577 sdRef
, flags
, interfaceIndex
, errorCode
,
578 fullName
? fullName
: "(null)", rrtype
, rrclass
, rdlen
, rdata
, ttl
,
582 * Only process "add" data...
585 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
589 * Lookup the service in the devices array.
592 devices
= (cups_array_t
*)context
;
595 unquote(name
, fullName
, sizeof(name
));
597 if ((key
.domain
= strstr(name
, "._tcp.")) != NULL
)
600 key
.domain
= (char *)"local.";
602 if ((ptr
= strstr(name
, "._")) != NULL
)
605 if (strstr(fullName
, "_ipp._tcp.") ||
606 strstr(fullName
, "_ipp-tls._tcp."))
607 key
.type
= CUPS_DEVICE_IPP
;
608 else if (strstr(fullName
, "_fax-ipp._tcp."))
609 key
.type
= CUPS_DEVICE_FAX_IPP
;
610 else if (strstr(fullName
, "_printer._tcp."))
611 key
.type
= CUPS_DEVICE_PRINTER
;
612 else if (strstr(fullName
, "_pdl-datastream._tcp."))
613 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
615 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
617 for (device
= cupsArrayFind(devices
, &key
);
619 device
= cupsArrayNext(devices
))
621 if (strcasecmp(device
->name
, key
.name
) ||
622 strcasecmp(device
->domain
, key
.domain
))
627 else if (device
->type
== key
.type
)
630 * Found it, pull out the priority and make and model from the TXT
631 * record and save it...
634 const void *value
; /* Pointer to value */
635 uint8_t valueLen
; /* Length of value (max 255) */
636 char make_and_model
[512], /* Manufacturer and model */
637 model
[256], /* Model */
638 priority
[256]; /* Priority */
641 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "priority", &valueLen
);
643 if (value
&& valueLen
)
645 memcpy(priority
, value
, valueLen
);
646 priority
[valueLen
] = '\0';
647 device
->priority
= atoi(priority
);
650 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MFG",
652 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MANUFACTURER",
655 if (value
&& valueLen
)
657 memcpy(make_and_model
, value
, valueLen
);
658 make_and_model
[valueLen
] = '\0';
661 make_and_model
[0] = '\0';
663 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MDL",
665 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MODEL", &valueLen
);
667 if (value
&& valueLen
)
669 memcpy(model
, value
, valueLen
);
670 model
[valueLen
] = '\0';
672 else if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "product",
673 &valueLen
)) != NULL
&& valueLen
> 2)
675 if (((char *)value
)[0] == '(')
678 * Strip parenthesis...
681 memcpy(model
, value
+ 1, valueLen
- 2);
682 model
[valueLen
- 2] = '\0';
686 memcpy(model
, value
, valueLen
);
687 model
[valueLen
] = '\0';
690 if (!strcasecmp(model
, "GPL Ghostscript") ||
691 !strcasecmp(model
, "GNU Ghostscript") ||
692 !strcasecmp(model
, "ESP Ghostscript"))
694 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "ty",
697 memcpy(model
, value
, valueLen
);
698 model
[valueLen
] = '\0';
700 if ((ptr
= strchr(model
, ',')) != NULL
)
704 strcpy(model
, "Unknown");
708 strcpy(model
, "Unknown");
710 if (device
->make_and_model
)
711 free(device
->make_and_model
);
713 if (make_and_model
[0])
715 strlcat(make_and_model
, " ", sizeof(make_and_model
));
716 strlcat(make_and_model
, model
, sizeof(make_and_model
));
717 device
->make_and_model
= strdup(make_and_model
);
720 device
->make_and_model
= strdup(model
);
722 if ((device
->type
== CUPS_DEVICE_IPP
||
723 device
->type
== CUPS_DEVICE_PRINTER
) &&
724 TXTRecordGetValuePtr(rdlen
, rdata
, "printer-type", &valueLen
))
727 * This is a CUPS printer!
730 device
->cups_shared
= 1;
732 if (device
->type
== CUPS_DEVICE_PRINTER
)
741 fprintf(stderr
, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName
);
746 * 'unquote()' - Unquote a name string.
750 unquote(char *dst
, /* I - Destination buffer */
751 const char *src
, /* I - Source string */
752 size_t dstsize
) /* I - Size of destination buffer */
754 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
757 while (*src
&& dst
< dstend
)
762 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
763 isdigit(src
[2] & 255))
765 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';