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 cups_shared
, /* CUPS shared printer? */
59 sent
; /* Did we list the device? */
67 static void browse_callback(DNSServiceRef sdRef
,
68 DNSServiceFlags flags
,
69 uint32_t interfaceIndex
,
70 DNSServiceErrorType errorCode
,
71 const char *serviceName
,
73 const char *replyDomain
, void *context
);
74 static void browse_local_callback(DNSServiceRef sdRef
,
75 DNSServiceFlags flags
,
76 uint32_t interfaceIndex
,
77 DNSServiceErrorType errorCode
,
78 const char *serviceName
,
80 const char *replyDomain
,
82 static int compare_devices(cups_device_t
*a
, cups_device_t
*b
);
83 static void exec_backend(char **argv
);
84 static cups_device_t
*get_device(cups_array_t
*devices
,
85 const char *serviceName
,
87 const char *replyDomain
);
88 static void query_callback(DNSServiceRef sdRef
,
89 DNSServiceFlags flags
,
90 uint32_t interfaceIndex
,
91 DNSServiceErrorType errorCode
,
92 const char *fullName
, uint16_t rrtype
,
93 uint16_t rrclass
, uint16_t rdlen
,
94 const void *rdata
, uint32_t ttl
,
96 static void unquote(char *dst
, const char *src
, size_t dstsize
);
100 * 'main()' - Browse for printers.
103 int /* O - Exit status */
104 main(int argc
, /* I - Number of command-line args */
105 char *argv
[]) /* I - Command-line arguments */
107 DNSServiceRef main_ref
, /* Main service reference */
108 fax_ipp_ref
, /* IPP fax service reference */
109 ipp_ref
, /* IPP service reference */
110 ipp_tls_ref
, /* IPP w/TLS service reference */
111 local_fax_ipp_ref
, /* Local IPP fax service reference */
112 local_ipp_ref
, /* Local IPP service reference */
113 local_ipp_tls_ref
, /* Local IPP w/TLS service reference */
114 local_printer_ref
, /* Local LPD service reference */
115 pdl_datastream_ref
, /* AppSocket service reference */
116 printer_ref
, /* LPD service reference */
117 riousbprint_ref
; /* Remote IO service reference */
118 int fd
; /* Main file descriptor */
119 fd_set input
; /* Input set for select() */
120 struct timeval timeout
; /* Timeout for select() */
121 cups_array_t
*devices
; /* Device array */
122 cups_device_t
*device
; /* Current device */
126 * Check command-line...
129 setbuf(stderr
, NULL
);
135 fputs("Usage: mdns job user title copies options [filename(s)]\n", stderr
);
140 * Create an array to track devices...
143 devices
= cupsArrayNew((cups_array_func_t
)compare_devices
, NULL
);
146 * Browse for different kinds of printers...
149 if (DNSServiceCreateConnection(&main_ref
) != kDNSServiceErr_NoError
)
151 perror("ERROR: Unable to create service connection");
155 fd
= DNSServiceRefSockFD(main_ref
);
157 fax_ipp_ref
= main_ref
;
158 DNSServiceBrowse(&fax_ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
159 "_fax-ipp._tcp", NULL
, browse_callback
, devices
);
162 DNSServiceBrowse(&ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
163 "_ipp._tcp", NULL
, browse_callback
, devices
);
165 ipp_tls_ref
= main_ref
;
166 DNSServiceBrowse(&ipp_tls_ref
, kDNSServiceFlagsShareConnection
, 0,
167 "_ipp-tls._tcp", NULL
, browse_callback
, devices
);
169 local_fax_ipp_ref
= main_ref
;
170 DNSServiceBrowse(&local_fax_ipp_ref
, kDNSServiceFlagsShareConnection
,
171 kDNSServiceInterfaceIndexLocalOnly
,
172 "_fax-ipp._tcp", NULL
, browse_local_callback
, devices
);
174 local_ipp_ref
= main_ref
;
175 DNSServiceBrowse(&local_ipp_ref
, kDNSServiceFlagsShareConnection
,
176 kDNSServiceInterfaceIndexLocalOnly
,
177 "_ipp._tcp", NULL
, browse_local_callback
, devices
);
179 local_ipp_tls_ref
= main_ref
;
180 DNSServiceBrowse(&local_ipp_tls_ref
, kDNSServiceFlagsShareConnection
,
181 kDNSServiceInterfaceIndexLocalOnly
,
182 "_ipp-tls._tcp", NULL
, browse_local_callback
, devices
);
184 local_printer_ref
= main_ref
;
185 DNSServiceBrowse(&local_printer_ref
, kDNSServiceFlagsShareConnection
,
186 kDNSServiceInterfaceIndexLocalOnly
,
187 "_printer._tcp", NULL
, browse_local_callback
, devices
);
189 pdl_datastream_ref
= main_ref
;
190 DNSServiceBrowse(&pdl_datastream_ref
, kDNSServiceFlagsShareConnection
, 0,
191 "_pdl-datastream._tcp", NULL
, browse_callback
, devices
);
193 printer_ref
= main_ref
;
194 DNSServiceBrowse(&printer_ref
, kDNSServiceFlagsShareConnection
, 0,
195 "_printer._tcp", NULL
, browse_callback
, devices
);
197 riousbprint_ref
= main_ref
;
198 DNSServiceBrowse(&riousbprint_ref
, kDNSServiceFlagsShareConnection
, 0,
199 "_riousbprint._tcp", NULL
, browse_callback
, devices
);
202 * Loop until we are killed...
213 if (select(fd
+ 1, &input
, NULL
, NULL
, &timeout
) < 0)
216 if (FD_ISSET(fd
, &input
))
219 * Process results of our browsing...
222 DNSServiceProcessResult(main_ref
);
227 * Announce any devices we've found...
230 char device_uri
[1024]; /* Device URI */
231 static const char * const schemes
[] =
232 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
233 /* URI schemes for devices */
236 for (device
= (cups_device_t
*)cupsArrayFirst(devices
);
238 device
= (cups_device_t
*)cupsArrayNext(devices
))
239 if (!device
->ref
&& !device
->sent
)
242 * Found the device, now get the TXT record(s) for it...
245 device
->ref
= main_ref
;
247 fprintf(stderr
, "DEBUG: Querying \"%s\"...\n", device
->fullName
);
249 if (DNSServiceQueryRecord(&(device
->ref
),
250 kDNSServiceFlagsShareConnection
,
251 0, device
->fullName
, kDNSServiceType_TXT
,
252 kDNSServiceClass_IN
, query_callback
,
253 devices
) != kDNSServiceErr_NoError
)
254 fputs("ERROR: Unable to query for TXT records!\n", stderr
);
256 else if (!device
->sent
)
259 * Got the TXT records, now report the device...
262 DNSServiceRefDeallocate(device
->ref
);
265 httpAssembleURIf(HTTP_URI_CODING_ALL
, device_uri
, sizeof(device_uri
),
266 schemes
[device
->type
], NULL
,
267 device
->cups_shared
? "cups" : "", 0,
268 "/%s", device
->fullName
);
270 printf("network %s \"%s\" \"%s\"\n", device_uri
,
271 device
->make_and_model
? device
->make_and_model
: "Unknown",
283 * 'browse_callback()' - Browse devices.
288 DNSServiceRef sdRef
, /* I - Service reference */
289 DNSServiceFlags flags
, /* I - Option flags */
290 uint32_t interfaceIndex
, /* I - Interface number */
291 DNSServiceErrorType errorCode
, /* I - Error, if any */
292 const char *serviceName
, /* I - Name of service/device */
293 const char *regtype
, /* I - Type of service */
294 const char *replyDomain
, /* I - Service domain */
295 void *context
) /* I - Devices array */
297 fprintf(stderr
, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
298 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
299 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
300 sdRef
, flags
, interfaceIndex
, errorCode
,
301 serviceName
? serviceName
: "(null)",
302 regtype
? regtype
: "(null)",
303 replyDomain
? replyDomain
: "(null)",
307 * Only process "add" data...
310 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
317 get_device((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
322 * 'browse_local_callback()' - Browse local devices.
326 browse_local_callback(
327 DNSServiceRef sdRef
, /* I - Service reference */
328 DNSServiceFlags flags
, /* I - Option flags */
329 uint32_t interfaceIndex
, /* I - Interface number */
330 DNSServiceErrorType errorCode
, /* I - Error, if any */
331 const char *serviceName
, /* I - Name of service/device */
332 const char *regtype
, /* I - Type of service */
333 const char *replyDomain
, /* I - Service domain */
334 void *context
) /* I - Devices array */
336 cups_device_t
*device
; /* Device */
339 fprintf(stderr
, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
340 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
341 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
342 sdRef
, flags
, interfaceIndex
, errorCode
,
343 serviceName
? serviceName
: "(null)",
344 regtype
? regtype
: "(null)",
345 replyDomain
? replyDomain
: "(null)",
349 * Only process "add" data...
352 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
359 device
= get_device((cups_array_t
*)context
, serviceName
, regtype
,
363 * Hide locally-registered devices...
366 fprintf(stderr
, "DEBUG: Hiding local printer \"%s\"...\n",
373 * 'compare_devices()' - Compare two devices.
376 static int /* O - Result of comparison */
377 compare_devices(cups_device_t
*a
, /* I - First device */
378 cups_device_t
*b
) /* I - Second device */
380 int result
= strcmp(a
->name
, b
->name
);
385 return (strcmp(a
->domain
, b
->domain
));
390 * 'exec_backend()' - Execute the backend that corresponds to the
391 * resolved service name.
395 exec_backend(char **argv
) /* I - Command-line arguments */
397 const char *resolved_uri
, /* Resolved device URI */
398 *cups_serverbin
; /* Location of programs */
399 char scheme
[1024], /* Scheme from URI */
400 *ptr
, /* Pointer into scheme */
401 filename
[1024]; /* Backend filename */
405 * Resolve the device URI...
408 resolved_uri
= backendResolveURI(argv
);
411 * Extract the scheme from the URI...
414 strlcpy(scheme
, resolved_uri
, sizeof(scheme
));
415 if ((ptr
= strchr(scheme
, ':')) != NULL
)
419 * Get the filename of the backend...
422 if ((cups_serverbin
= getenv("CUPS_SERVERBIN")) == NULL
)
423 cups_serverbin
= CUPS_SERVERBIN
;
425 snprintf(filename
, sizeof(filename
), "%s/backend/%s", cups_serverbin
, scheme
);
428 * Overwrite the device URIs and run the new backend...
431 setenv("DEVICE_URI", resolved_uri
, 1);
433 argv
[0] = (char *)resolved_uri
;
435 fprintf(stderr
, "DEBUG: Executing backend \"%s\"...\n", filename
);
437 execv(filename
, argv
);
439 fprintf(stderr
, "ERROR: Unable to execute backend \"%s\": %s\n", filename
,
441 exit(CUPS_BACKEND_STOP
);
446 * 'get_device()' - Create or update a device.
449 static cups_device_t
* /* O - Device */
450 get_device(cups_array_t
*devices
, /* I - Device array */
451 const char *serviceName
, /* I - Name of service/device */
452 const char *regtype
, /* I - Type of service */
453 const char *replyDomain
) /* I - Service domain */
455 cups_device_t key
, /* Search key */
456 *device
; /* Device */
457 char fullName
[1024]; /* Full name for query */
461 * See if this is a new device...
464 key
.name
= (char *)serviceName
;
465 key
.domain
= (char *)replyDomain
;
467 if (!strcmp(regtype
, "_ipp._tcp.") ||
468 !strcmp(regtype
, "_ipp-tls._tcp."))
469 key
.type
= CUPS_DEVICE_IPP
;
470 else if (!strcmp(regtype
, "_fax-ipp._tcp."))
471 key
.type
= CUPS_DEVICE_FAX_IPP
;
472 else if (!strcmp(regtype
, "_printer._tcp."))
473 key
.type
= CUPS_DEVICE_PRINTER
;
474 else if (!strcmp(regtype
, "_pdl-datastream._tcp."))
475 key
.type
= CUPS_DEVICE_PDL_DATASTREAM
;
477 key
.type
= CUPS_DEVICE_RIOUSBPRINT
;
479 if ((device
= cupsArrayFind(devices
, &key
)) != NULL
)
482 * No, see if this registration is a higher priority protocol...
485 if (key
.type
> device
->type
)
487 fprintf(stderr
, "DEBUG: Updating \"%s\" to \"%s.%s%s\"...\n",
488 device
->fullName
, serviceName
, regtype
, replyDomain
);
490 device
->type
= key
.type
;
496 * Yes, add the device...
499 fprintf(stderr
, "DEBUG: Found \"%s.%s%s\"...\n", serviceName
, regtype
,
502 device
= calloc(sizeof(cups_device_t
), 1);
503 device
->name
= strdup(serviceName
);
504 device
->domain
= strdup(replyDomain
);
505 device
->type
= key
.type
;
507 cupsArrayAdd(devices
, device
);
511 * Update the "full name" of this service, which is used for queries...
514 snprintf(fullName
, sizeof(fullName
), "%s.%s%s", serviceName
, regtype
,
517 if (device
->fullName
)
518 free(device
->fullName
);
520 device
->fullName
= strdup(fullName
);
527 * 'query_callback()' - Process query data.
532 DNSServiceRef sdRef
, /* I - Service reference */
533 DNSServiceFlags flags
, /* I - Data flags */
534 uint32_t interfaceIndex
, /* I - Interface */
535 DNSServiceErrorType errorCode
, /* I - Error, if any */
536 const char *fullName
, /* I - Full service name */
537 uint16_t rrtype
, /* I - Record type */
538 uint16_t rrclass
, /* I - Record class */
539 uint16_t rdlen
, /* I - Length of record data */
540 const void *rdata
, /* I - Record data */
541 uint32_t ttl
, /* I - Time-to-live */
542 void *context
) /* I - Devices array */
544 cups_array_t
*devices
; /* Device array */
545 char name
[1024], /* Service name */
546 *ptr
; /* Pointer into name */
547 cups_device_t key
, /* Search key */
548 *device
; /* Device */
551 fprintf(stderr
, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
552 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
553 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
555 sdRef
, flags
, interfaceIndex
, errorCode
,
556 fullName
? fullName
: "(null)", rrtype
, rrclass
, rdlen
, rdata
, ttl
,
560 * Only process "add" data...
563 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
567 * Lookup the service in the devices array.
570 devices
= (cups_array_t
*)context
;
573 unquote(name
, fullName
, sizeof(name
));
575 if ((key
.domain
= strstr(name
, "._tcp.")) != NULL
)
578 key
.domain
= (char *)"local.";
580 if ((ptr
= strstr(name
, "._")) != NULL
)
583 if ((device
= cupsArrayFind(devices
, &key
)) != NULL
)
586 * Found it, pull out the make and model from the TXT record and save it...
589 const void *value
; /* Pointer to value */
590 uint8_t valueLen
; /* Length of value (max 255) */
591 char make_and_model
[512], /* Manufacturer and model */
592 model
[256]; /* Model */
595 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MFG",
597 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MANUFACTURER", &valueLen
);
599 if (value
&& valueLen
)
601 memcpy(make_and_model
, value
, valueLen
);
602 make_and_model
[valueLen
] = '\0';
605 make_and_model
[0] = '\0';
607 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MDL",
609 value
= TXTRecordGetValuePtr(rdlen
, rdata
, "usb_MODEL", &valueLen
);
611 if (value
&& valueLen
)
613 memcpy(model
, value
, valueLen
);
614 model
[valueLen
] = '\0';
616 else if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "product",
617 &valueLen
)) != NULL
&& valueLen
> 2)
619 memcpy(model
, value
+ 1, valueLen
- 2);
620 model
[valueLen
- 2] = '\0';
622 if (!strcasecmp(model
, "GPL Ghostscript") ||
623 !strcasecmp(model
, "GNU Ghostscript") ||
624 !strcasecmp(model
, "ESP Ghostscript"))
626 if ((value
= TXTRecordGetValuePtr(rdlen
, rdata
, "ty",
629 memcpy(model
, value
, valueLen
);
630 model
[valueLen
] = '\0';
632 if ((ptr
= strchr(model
, ',')) != NULL
)
636 strcpy(model
, "Unknown");
640 strcpy(model
, "Unknown");
642 if (device
->make_and_model
)
643 free(device
->make_and_model
);
645 if (make_and_model
[0])
647 strlcat(make_and_model
, " ", sizeof(make_and_model
));
648 strlcat(make_and_model
, model
, sizeof(make_and_model
));
649 device
->make_and_model
= strdup(make_and_model
);
652 device
->make_and_model
= strdup(model
);
654 if ((device
->type
== CUPS_DEVICE_IPP
||
655 device
->type
== CUPS_DEVICE_PRINTER
) &&
656 (value
= TXTRecordGetValuePtr(rdlen
, rdata
, "printer-type",
660 * This is a CUPS printer!
663 device
->cups_shared
= 1;
665 if (device
->type
== CUPS_DEVICE_PRINTER
)
670 fprintf(stderr
, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName
);
675 * 'unquote()' - Unquote a name string.
679 unquote(char *dst
, /* I - Destination buffer */
680 const char *src
, /* I - Source string */
681 size_t dstsize
) /* I - Size of destination buffer */
683 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
686 while (*src
&& dst
< dstend
)
691 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
692 isdigit(src
[2] & 255))
694 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';