4 * DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
6 * Copyright 2008-2009 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 * exec_backend() - Execute the backend that corresponds to the
23 * resolved service name.
24 * get_device() - Create or update a device.
25 * query_callback() - Process query data.
26 * unquote() - Unquote a name string.
30 * Include necessary headers.
33 #include "backend-private.h"
34 #include <cups/array.h>
44 CUPS_DEVICE_PRINTER
= 0, /* lpd://... */
45 CUPS_DEVICE_IPP
, /* ipp://... */
46 CUPS_DEVICE_FAX_IPP
, /* ipp://... */
47 CUPS_DEVICE_PDL_DATASTREAM
, /* socket://... */
48 CUPS_DEVICE_RIOUSBPRINT
/* riousbprint://... */
54 DNSServiceRef ref
; /* Service reference for resolve */
55 char *name
, /* Service name */
56 *domain
, /* Domain name */
57 *fullName
, /* Full name */
58 *make_and_model
, /* Make and model from TXT record */
59 *device_id
; /* 1284 device ID from TXT record */
60 cups_devtype_t type
; /* Device registration type */
61 int priority
, /* Priority associated with type */
62 cups_shared
, /* CUPS shared printer? */
63 sent
; /* Did we list the device? */
71 static void browse_callback(DNSServiceRef sdRef
,
72 DNSServiceFlags flags
,
73 uint32_t interfaceIndex
,
74 DNSServiceErrorType errorCode
,
75 const char *serviceName
,
77 const char *replyDomain
, void *context
);
78 static void browse_local_callback(DNSServiceRef sdRef
,
79 DNSServiceFlags flags
,
80 uint32_t interfaceIndex
,
81 DNSServiceErrorType errorCode
,
82 const char *serviceName
,
84 const char *replyDomain
,
86 static int compare_devices(cups_device_t
*a
, cups_device_t
*b
);
87 static void exec_backend(char **argv
);
88 static cups_device_t
*get_device(cups_array_t
*devices
,
89 const char *serviceName
,
91 const char *replyDomain
);
92 static void query_callback(DNSServiceRef sdRef
,
93 DNSServiceFlags flags
,
94 uint32_t interfaceIndex
,
95 DNSServiceErrorType errorCode
,
96 const char *fullName
, uint16_t rrtype
,
97 uint16_t rrclass
, uint16_t rdlen
,
98 const void *rdata
, uint32_t ttl
,
100 static void unquote(char *dst
, const char *src
, size_t dstsize
);
104 * 'main()' - Browse for printers.
107 int /* O - Exit status */
108 main(int argc
, /* I - Number of command-line args */
109 char *argv
[]) /* I - Command-line arguments */
111 const char *name
; /* Backend name */
112 DNSServiceRef main_ref
, /* Main service reference */
113 fax_ipp_ref
, /* IPP fax service reference */
114 ipp_ref
, /* IPP service reference */
115 ipp_tls_ref
, /* IPP w/TLS service reference */
116 local_fax_ipp_ref
, /* Local IPP fax service reference */
117 local_ipp_ref
, /* Local IPP service reference */
118 local_ipp_tls_ref
, /* Local IPP w/TLS service reference */
119 local_printer_ref
, /* Local LPD service reference */
120 pdl_datastream_ref
, /* AppSocket service reference */
121 printer_ref
, /* LPD service reference */
122 riousbprint_ref
; /* Remote IO service reference */
123 int fd
; /* Main file descriptor */
124 fd_set input
; /* Input set for select() */
125 struct timeval timeout
; /* Timeout for select() */
126 cups_array_t
*devices
; /* Device array */
127 cups_device_t
*device
; /* Current device */
128 char uriName
[1024]; /* Unquoted fullName for URI */
132 * Check command-line...
135 setbuf(stderr
, NULL
);
141 fprintf(stderr
, "Usage: %s job user title copies options [filename(s)]\n",
147 * Only do discovery when run as "dnssd"...
150 if ((name
= strrchr(argv
[0], '/')) != NULL
)
155 if (strcmp(name
, "dnssd"))
159 * Create an array to track devices...
162 devices
= cupsArrayNew((cups_array_func_t
)compare_devices
, NULL
);
165 * Browse for different kinds of printers...
168 if (DNSServiceCreateConnection(&main_ref
) != kDNSServiceErr_NoError
)
170 perror("ERROR: Unable to create service connection");
174 fd
= DNSServiceRefSockFD(main_ref
);
176 fax_ipp_ref
= main_ref
;
177 DNSServiceBrowse(&fax_ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
178 "_fax-ipp._tcp", NULL
, browse_callback
, devices
);
181 DNSServiceBrowse(&ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
182 "_ipp._tcp", NULL
, browse_callback
, devices
);
184 ipp_tls_ref
= main_ref
;
185 DNSServiceBrowse(&ipp_tls_ref
, kDNSServiceFlagsShareConnection
, 0,
186 "_ipp-tls._tcp", NULL
, browse_callback
, devices
);
188 local_fax_ipp_ref
= main_ref
;
189 DNSServiceBrowse(&local_fax_ipp_ref
, kDNSServiceFlagsShareConnection
,
190 kDNSServiceInterfaceIndexLocalOnly
,
191 "_fax-ipp._tcp", NULL
, browse_local_callback
, devices
);
193 local_ipp_ref
= main_ref
;
194 DNSServiceBrowse(&local_ipp_ref
, kDNSServiceFlagsShareConnection
,
195 kDNSServiceInterfaceIndexLocalOnly
,
196 "_ipp._tcp", NULL
, browse_local_callback
, devices
);
198 local_ipp_tls_ref
= main_ref
;
199 DNSServiceBrowse(&local_ipp_tls_ref
, kDNSServiceFlagsShareConnection
,
200 kDNSServiceInterfaceIndexLocalOnly
,
201 "_ipp-tls._tcp", NULL
, browse_local_callback
, devices
);
203 local_printer_ref
= main_ref
;
204 DNSServiceBrowse(&local_printer_ref
, kDNSServiceFlagsShareConnection
,
205 kDNSServiceInterfaceIndexLocalOnly
,
206 "_printer._tcp", NULL
, browse_local_callback
, devices
);
208 pdl_datastream_ref
= main_ref
;
209 DNSServiceBrowse(&pdl_datastream_ref
, kDNSServiceFlagsShareConnection
, 0,
210 "_pdl-datastream._tcp", NULL
, browse_callback
, devices
);
212 printer_ref
= main_ref
;
213 DNSServiceBrowse(&printer_ref
, kDNSServiceFlagsShareConnection
, 0,
214 "_printer._tcp", NULL
, browse_callback
, devices
);
216 riousbprint_ref
= main_ref
;
217 DNSServiceBrowse(&riousbprint_ref
, kDNSServiceFlagsShareConnection
, 0,
218 "_riousbprint._tcp", NULL
, browse_callback
, devices
);
221 * Loop until we are killed...
232 if (select(fd
+ 1, &input
, NULL
, NULL
, &timeout
) < 0)
235 if (FD_ISSET(fd
, &input
))
238 * Process results of our browsing...
241 DNSServiceProcessResult(main_ref
);
246 * Announce any devices we've found...
249 DNSServiceErrorType status
; /* DNS query status */
250 cups_device_t
*best
; /* Best matching device */
251 char device_uri
[1024]; /* Device URI */
252 int count
; /* Number of queries */
253 static const char * const schemes
[] =
254 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
255 /* URI schemes for devices */
258 for (device
= (cups_device_t
*)cupsArrayFirst(devices
),
259 best
= NULL
, count
= 0;
261 device
= (cups_device_t
*)cupsArrayNext(devices
))
262 if (!device
->ref
&& !device
->sent
)
265 * Found the device, now get the TXT record(s) for it...
270 device
->ref
= main_ref
;
272 fprintf(stderr
, "DEBUG: Querying \"%s\"...\n", device
->fullName
);
274 status
= DNSServiceQueryRecord(&(device
->ref
),
275 kDNSServiceFlagsShareConnection
,
278 kDNSServiceClass_IN
, query_callback
,
280 if (status
!= kDNSServiceErr_NoError
)
282 fputs("ERROR: Unable to query for TXT records!\n", stderr
);
283 fprintf(stderr
, "DEBUG: DNSServiceQueryRecord returned %d\n",
290 else if (!device
->sent
)
293 * Got the TXT records, now report the device...
296 DNSServiceRefDeallocate(device
->ref
);
301 else if (strcasecmp(best
->name
, device
->name
) ||
302 strcasecmp(best
->domain
, device
->domain
))
304 unquote(uriName
, best
->fullName
, sizeof(uriName
));
306 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
307 schemes
[best
->type
], NULL
, uriName
, 0,
308 best
->cups_shared
? "/cups" : "/");
310 cupsBackendReport("network", device_uri
, best
->make_and_model
,
311 best
->name
, best
->device_id
, NULL
);
315 else if (best
->priority
> device
->priority
||
316 (best
->priority
== device
->priority
&&
317 best
->type
< device
->type
))
328 unquote(uriName
, best
->fullName
, sizeof(uriName
));
330 httpAssembleURI(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
331 schemes
[best
->type
], NULL
, uriName
, 0,
332 best
->cups_shared
? "/cups" : "/");
334 cupsBackendReport("network", device_uri
, best
->make_and_model
,
335 best
->name
, best
->device_id
, NULL
);
344 * 'browse_callback()' - Browse devices.
349 DNSServiceRef sdRef
, /* I - Service reference */
350 DNSServiceFlags flags
, /* I - Option flags */
351 uint32_t interfaceIndex
, /* I - Interface number */
352 DNSServiceErrorType errorCode
, /* I - Error, if any */
353 const char *serviceName
, /* I - Name of service/device */
354 const char *regtype
, /* I - Type of service */
355 const char *replyDomain
, /* I - Service domain */
356 void *context
) /* I - Devices array */
358 fprintf(stderr
, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
359 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
360 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
361 sdRef
, flags
, interfaceIndex
, errorCode
,
362 serviceName
? serviceName
: "(null)",
363 regtype
? regtype
: "(null)",
364 replyDomain
? replyDomain
: "(null)",
368 * Only process "add" data...
371 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
378 get_device((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
383 * 'browse_local_callback()' - Browse local devices.
387 browse_local_callback(
388 DNSServiceRef sdRef
, /* I - Service reference */
389 DNSServiceFlags flags
, /* I - Option flags */
390 uint32_t interfaceIndex
, /* I - Interface number */
391 DNSServiceErrorType errorCode
, /* I - Error, if any */
392 const char *serviceName
, /* I - Name of service/device */
393 const char *regtype
, /* I - Type of service */
394 const char *replyDomain
, /* I - Service domain */
395 void *context
) /* I - Devices array */
397 cups_device_t
*device
; /* Device */
400 fprintf(stderr
, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
401 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
402 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
403 sdRef
, flags
, interfaceIndex
, errorCode
,
404 serviceName
? serviceName
: "(null)",
405 regtype
? regtype
: "(null)",
406 replyDomain
? replyDomain
: "(null)",
410 * Only process "add" data...
413 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
420 device
= get_device((cups_array_t
*)context
, serviceName
, regtype
,
424 * Hide locally-registered devices...
427 fprintf(stderr
, "DEBUG: Hiding local printer \"%s\"...\n",
434 * 'compare_devices()' - Compare two devices.
437 static int /* O - Result of comparison */
438 compare_devices(cups_device_t
*a
, /* I - First device */
439 cups_device_t
*b
) /* I - Second device */
441 int result
= strcmp(a
->name
, b
->name
);
446 return (strcmp(a
->domain
, b
->domain
));
451 * 'exec_backend()' - Execute the backend that corresponds to the
452 * resolved service name.
456 exec_backend(char **argv
) /* I - Command-line arguments */
458 const char *resolved_uri
, /* Resolved device URI */
459 *cups_serverbin
; /* Location of programs */
460 char scheme
[1024], /* Scheme from URI */
461 *ptr
, /* Pointer into scheme */
462 filename
[1024]; /* Backend filename */
466 * Resolve the device URI...
469 if ((resolved_uri
= cupsBackendDeviceURI(argv
)) == NULL
)
470 exit(CUPS_BACKEND_FAILED
);
473 * Extract the scheme from the URI...
476 strlcpy(scheme
, resolved_uri
, sizeof(scheme
));
477 if ((ptr
= strchr(scheme
, ':')) != NULL
)
481 * Get the filename of the backend...
484 if ((cups_serverbin
= getenv("CUPS_SERVERBIN")) == NULL
)
485 cups_serverbin
= CUPS_SERVERBIN
;
487 snprintf(filename
, sizeof(filename
), "%s/backend/%s", cups_serverbin
, scheme
);
490 * Overwrite the device URIs and run the new backend...
493 setenv("DEVICE_URI", resolved_uri
, 1);
495 argv
[0] = (char *)resolved_uri
;
497 fprintf(stderr
, "DEBUG: Executing backend \"%s\"...\n", filename
);
499 execv(filename
, argv
);
501 fprintf(stderr
, "ERROR: Unable to execute backend \"%s\": %s\n", filename
,
503 exit(CUPS_BACKEND_STOP
);
508 * 'get_device()' - Create or update a device.
511 static cups_device_t
* /* O - Device */
512 get_device(cups_array_t
*devices
, /* I - Device array */
513 const char *serviceName
, /* I - Name of service/device */
514 const char *regtype
, /* I - Type of service */
515 const char *replyDomain
) /* I - Service domain */
517 cups_device_t key
, /* Search key */
518 *device
; /* Device */
519 char fullName
[kDNSServiceMaxDomainName
];
520 /* Full name for query */
524 * See if this is a new device...
527 key
.name
= (char *)serviceName
;
528 key
.domain
= (char *)replyDomain
;
530 if (!strcmp(regtype
, "_ipp._tcp.") ||
531 !strcmp(regtype
, "_ipp-tls._tcp."))
532 key
.type
= CUPS_DEVICE_IPP
;
533 else if (!strcmp(regtype
, "_fax-ipp._tcp."))
534 key
.type
= CUPS_DEVICE_FAX_IPP
;
535 else if (!strcmp(regtype
, "_printer._tcp."))
536 key
.type
= CUPS_DEVICE_PRINTER
;
537 else if (!strcmp(regtype
, "_pdl-datastream._tcp."))
538 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
540 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
542 for (device
= cupsArrayFind(devices
, &key
);
544 device
= cupsArrayNext(devices
))
545 if (strcasecmp(device
->name
, key
.name
) ||
546 strcasecmp(device
->domain
, key
.domain
))
548 else if (device
->type
== key
.type
)
552 * Yes, add the device...
555 fprintf(stderr
, "DEBUG: Found \"%s.%s%s\"...\n", serviceName
, regtype
,
558 device
= calloc(sizeof(cups_device_t
), 1);
559 device
->name
= strdup(serviceName
);
560 device
->domain
= strdup(replyDomain
);
561 device
->type
= key
.type
;
562 device
->priority
= 50;
564 cupsArrayAdd(devices
, device
);
567 * Set the "full name" of this service, which is used for queries...
570 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
571 device
->fullName
= strdup(fullName
);
578 * 'query_callback()' - Process query data.
583 DNSServiceRef sdRef
, /* I - Service reference */
584 DNSServiceFlags flags
, /* I - Data flags */
585 uint32_t interfaceIndex
, /* I - Interface */
586 DNSServiceErrorType errorCode
, /* I - Error, if any */
587 const char *fullName
, /* I - Full service name */
588 uint16_t rrtype
, /* I - Record type */
589 uint16_t rrclass
, /* I - Record class */
590 uint16_t rdlen
, /* I - Length of record data */
591 const void *rdata
, /* I - Record data */
592 uint32_t ttl
, /* I - Time-to-live */
593 void *context
) /* I - Devices array */
595 cups_array_t
*devices
; /* Device array */
596 char name
[1024], /* Service name */
597 *ptr
; /* Pointer into string */
598 cups_device_t dkey
, /* Search key */
599 *device
; /* Device */
602 fprintf(stderr
, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
603 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
604 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
606 sdRef
, flags
, interfaceIndex
, errorCode
,
607 fullName
? fullName
: "(null)", rrtype
, rrclass
, rdlen
, rdata
, ttl
,
611 * Only process "add" data...
614 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
618 * Lookup the service in the devices array.
621 devices
= (cups_array_t
*)context
;
624 unquote(name
, fullName
, sizeof(name
));
626 if ((dkey
.domain
= strstr(name
, "._tcp.")) != NULL
)
629 dkey
.domain
= (char *)"local.";
631 if ((ptr
= strstr(name
, "._")) != NULL
)
634 if (strstr(fullName
, "_ipp._tcp.") ||
635 strstr(fullName
, "_ipp-tls._tcp."))
636 dkey
.type
= CUPS_DEVICE_IPP
;
637 else if (strstr(fullName
, "_fax-ipp._tcp."))
638 dkey
.type
= CUPS_DEVICE_FAX_IPP
;
639 else if (strstr(fullName
, "_printer._tcp."))
640 dkey
.type
= CUPS_DEVICE_PRINTER
;
641 else if (strstr(fullName
, "_pdl-datastream._tcp."))
642 dkey
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
644 dkey
.type
= CUPS_DEVICE_RIOUSBPRINT
;
646 for (device
= cupsArrayFind(devices
, &dkey
);
648 device
= cupsArrayNext(devices
))
650 if (strcasecmp(device
->name
, dkey
.name
) ||
651 strcasecmp(device
->domain
, dkey
.domain
))
656 else if (device
->type
== dkey
.type
)
659 * Found it, pull out the priority and make and model from the TXT
660 * record and save it...
663 const uint8_t *data
, /* Pointer into data */
664 *datanext
, /* Next key/value pair */
665 *dataend
; /* End of entire TXT record */
666 uint8_t datalen
; /* Length of current key/value pair */
667 char key
[256], /* Key string */
668 value
[256], /* Value string */
670 /* Manufacturer and model */
671 model
[256], /* Model */
672 device_id
[2048];/* 1284 device ID */
676 make_and_model
[0] = '\0';
678 strcpy(model
, "Unknown");
680 for (data
= rdata
, dataend
= data
+ rdlen
;
685 * Read a key/value pair starting with an 8-bit length. Since the
686 * length is 8 bits and the size of the key/value buffers is 256, we
687 * don't need to check for overflow...
692 if (!datalen
|| (data
+ datalen
) >= dataend
)
695 datanext
= data
+ datalen
;
697 for (ptr
= key
; data
< datanext
&& *data
!= '='; data
++)
701 if (data
< datanext
&& *data
== '=')
706 memcpy(value
, data
, datanext
- data
);
707 value
[datanext
- data
] = '\0';
712 if (!strncasecmp(key
, "usb_", 4))
715 * Add USB device ID information...
718 ptr
= device_id
+ strlen(device_id
);
719 snprintf(ptr
, sizeof(device_id
) - (ptr
- device_id
), "%s:%s;",
723 if (!strcasecmp(key
, "usb_MFG") || !strcasecmp(key
, "usb_MANU") ||
724 !strcasecmp(key
, "usb_MANUFACTURER"))
725 strcpy(make_and_model
, value
);
726 else if (!strcasecmp(key
, "usb_MDL") || !strcasecmp(key
, "usb_MODEL"))
727 strcpy(model
, value
);
728 else if (!strcasecmp(key
, "product") && !strstr(value
, "Ghostscript"))
733 * Strip parenthesis...
736 if ((ptr
= value
+ strlen(value
) - 1) > value
&& *ptr
== ')')
739 strcpy(model
, value
+ 1);
742 strcpy(model
, value
);
744 else if (!strcasecmp(key
, "ty"))
746 strcpy(model
, value
);
748 if ((ptr
= strchr(model
, ',')) != NULL
)
751 else if (!strcasecmp(key
, "priority"))
752 device
->priority
= atoi(value
);
753 else if ((device
->type
== CUPS_DEVICE_IPP
||
754 device
->type
== CUPS_DEVICE_PRINTER
) &&
755 !strcasecmp(key
, "printer-type"))
758 * This is a CUPS printer!
761 device
->cups_shared
= 1;
763 if (device
->type
== CUPS_DEVICE_PRINTER
)
768 if (device
->device_id
)
769 free(device
->device_id
);
771 if (!device_id
[0] && strcmp(model
, "Unknown"))
773 if (make_and_model
[0])
774 snprintf(device_id
, sizeof(device_id
), "MFG:%s;MDL:%s;",
775 make_and_model
, model
);
776 else if (!strncasecmp(model
, "designjet ", 10))
777 snprintf(device_id
, sizeof(device_id
), "MFG:HP;MDL:%s", model
+ 10);
778 else if (!strncasecmp(model
, "stylus ", 7))
779 snprintf(device_id
, sizeof(device_id
), "MFG:EPSON;MDL:%s", model
+ 7);
780 else if ((ptr
= strchr(model
, ' ')) != NULL
)
783 * Assume the first word is the make...
786 memcpy(make_and_model
, model
, ptr
- model
);
787 make_and_model
[ptr
- model
] = '\0';
789 snprintf(device_id
, sizeof(device_id
), "MFG:%s;MDL:%s",
790 make_and_model
, ptr
+ 1);
795 device
->device_id
= strdup(device_id
);
797 device
->device_id
= NULL
;
799 if (device
->make_and_model
)
800 free(device
->make_and_model
);
802 if (make_and_model
[0])
804 strlcat(make_and_model
, " ", sizeof(make_and_model
));
805 strlcat(make_and_model
, model
, sizeof(make_and_model
));
807 device
->make_and_model
= strdup(make_and_model
);
810 device
->make_and_model
= strdup(model
);
816 fprintf(stderr
, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName
);
821 * 'unquote()' - Unquote a name string.
825 unquote(char *dst
, /* I - Destination buffer */
826 const char *src
, /* I - Source string */
827 size_t dstsize
) /* I - Size of destination buffer */
829 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
832 while (*src
&& dst
< dstend
)
837 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
838 isdigit(src
[2] & 255))
840 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';