]> git.ipfire.org Git - thirdparty/cups.git/blame - backend/mdns.c
Merge CUPS 1.4svn-r7524.
[thirdparty/cups.git] / backend / mdns.c
CommitLineData
7a14d768
MS
1/*
2 * "$Id$"
3 *
4 * DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2008 by Apple Inc.
7 *
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/".
13 *
14 * This file is subject to the Apple OS-Developed Software exception.
15 *
16 * Contents:
17 *
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.
25 */
26
27/*
28 * Include necessary headers.
29 */
30
31#include "backend-private.h"
32#include <cups/array.h>
33#include <dns_sd.h>
34
35
36/*
37 * Device structure...
38 */
39
40typedef enum
41{
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://... */
47} cups_devtype_t;
48
49
50typedef struct
51{
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? */
60} cups_device_t;
61
62
63/*
64 * Local functions...
65 */
66
67static void browse_callback(DNSServiceRef sdRef,
68 DNSServiceFlags flags,
69 uint32_t interfaceIndex,
70 DNSServiceErrorType errorCode,
71 const char *serviceName,
72 const char *regtype,
73 const char *replyDomain, void *context);
74static void browse_local_callback(DNSServiceRef sdRef,
75 DNSServiceFlags flags,
76 uint32_t interfaceIndex,
77 DNSServiceErrorType errorCode,
78 const char *serviceName,
79 const char *regtype,
80 const char *replyDomain,
81 void *context);
82static int compare_devices(cups_device_t *a, cups_device_t *b);
83static void exec_backend(char **argv);
84static cups_device_t *get_device(cups_array_t *devices,
85 const char *serviceName,
86 const char *regtype,
87 const char *replyDomain);
88static 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,
95 void *context);
96static void unquote(char *dst, const char *src, size_t dstsize);
97
98
99/*
100 * 'main()' - Browse for printers.
101 */
102
103int /* O - Exit status */
104main(int argc, /* I - Number of command-line args */
105 char *argv[]) /* I - Command-line arguments */
106{
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 */
123
124
125 /*
126 * Check command-line...
127 */
128
129 setbuf(stderr, NULL);
130
131 if (argc >= 6)
132 exec_backend(argv);
133 else if (argc != 1)
134 {
135 fputs("Usage: mdns job user title copies options [filename(s)]\n", stderr);
136 return (1);
137 }
138
139 /*
140 * Create an array to track devices...
141 */
142
143 devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
144
145 /*
146 * Browse for different kinds of printers...
147 */
148
149 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
150 {
151 perror("ERROR: Unable to create service connection");
152 return (1);
153 }
154
155 fd = DNSServiceRefSockFD(main_ref);
156
157 fax_ipp_ref = main_ref;
158 DNSServiceBrowse(&fax_ipp_ref, kDNSServiceFlagsShareConnection, 0,
159 "_fax-ipp._tcp", NULL, browse_callback, devices);
160
161 ipp_ref = main_ref;
162 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
163 "_ipp._tcp", NULL, browse_callback, devices);
164
165 ipp_tls_ref = main_ref;
166 DNSServiceBrowse(&ipp_tls_ref, kDNSServiceFlagsShareConnection, 0,
167 "_ipp-tls._tcp", NULL, browse_callback, devices);
168
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);
173
174 local_ipp_ref = main_ref;
175 DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
176 kDNSServiceInterfaceIndexLocalOnly,
177 "_ipp._tcp", NULL, browse_local_callback, devices);
178
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);
183
184 local_printer_ref = main_ref;
185 DNSServiceBrowse(&local_printer_ref, kDNSServiceFlagsShareConnection,
186 kDNSServiceInterfaceIndexLocalOnly,
187 "_printer._tcp", NULL, browse_local_callback, devices);
188
189 pdl_datastream_ref = main_ref;
190 DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
191 "_pdl-datastream._tcp", NULL, browse_callback, devices);
192
193 printer_ref = main_ref;
194 DNSServiceBrowse(&printer_ref, kDNSServiceFlagsShareConnection, 0,
195 "_printer._tcp", NULL, browse_callback, devices);
196
197 riousbprint_ref = main_ref;
198 DNSServiceBrowse(&riousbprint_ref, kDNSServiceFlagsShareConnection, 0,
199 "_riousbprint._tcp", NULL, browse_callback, devices);
200
201 /*
202 * Loop until we are killed...
203 */
204
205 for (;;)
206 {
207 FD_ZERO(&input);
208 FD_SET(fd, &input);
209
210 timeout.tv_sec = 1;
211 timeout.tv_usec = 0;
212
213 if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
214 continue;
215
216 if (FD_ISSET(fd, &input))
217 {
218 /*
219 * Process results of our browsing...
220 */
221
222 DNSServiceProcessResult(main_ref);
223 }
224 else
225 {
226 /*
227 * Announce any devices we've found...
228 */
229
230 char device_uri[1024]; /* Device URI */
231 static const char * const schemes[] =
232 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
233 /* URI schemes for devices */
234
235
236 for (device = (cups_device_t *)cupsArrayFirst(devices);
237 device;
238 device = (cups_device_t *)cupsArrayNext(devices))
239 if (!device->ref && !device->sent)
240 {
241 /*
242 * Found the device, now get the TXT record(s) for it...
243 */
244
245 device->ref = main_ref;
246
247 fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
248
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);
255 }
256 else if (!device->sent)
257 {
258 /*
259 * Got the TXT records, now report the device...
260 */
261
262 DNSServiceRefDeallocate(device->ref);
263 device->ref = 0;
264
265 httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
266 schemes[device->type], NULL, device->fullName, 0,
267 device->cups_shared ? "/cups" : "");
268
269 printf("network %s \"%s\" \"%s\"\n", device_uri,
270 device->make_and_model ? device->make_and_model : "Unknown",
271 device->name);
272
273 device->sent = 1;
274 }
275 }
276 }
277}
278
279
280/*
281 * 'browse_callback()' - Browse devices.
282 */
283
284static void
285browse_callback(
286 DNSServiceRef sdRef, /* I - Service reference */
287 DNSServiceFlags flags, /* I - Option flags */
288 uint32_t interfaceIndex, /* I - Interface number */
289 DNSServiceErrorType errorCode, /* I - Error, if any */
290 const char *serviceName, /* I - Name of service/device */
291 const char *regtype, /* I - Type of service */
292 const char *replyDomain, /* I - Service domain */
293 void *context) /* I - Devices array */
294{
295 fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
296 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
297 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
298 sdRef, flags, interfaceIndex, errorCode,
299 serviceName ? serviceName : "(null)",
300 regtype ? regtype : "(null)",
301 replyDomain ? replyDomain : "(null)",
302 context);
303
304 /*
305 * Only process "add" data...
306 */
307
308 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
309 return;
310
311 /*
312 * Get the device...
313 */
314
315 get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
316}
317
318
319/*
320 * 'browse_local_callback()' - Browse local devices.
321 */
322
323static void
324browse_local_callback(
325 DNSServiceRef sdRef, /* I - Service reference */
326 DNSServiceFlags flags, /* I - Option flags */
327 uint32_t interfaceIndex, /* I - Interface number */
328 DNSServiceErrorType errorCode, /* I - Error, if any */
329 const char *serviceName, /* I - Name of service/device */
330 const char *regtype, /* I - Type of service */
331 const char *replyDomain, /* I - Service domain */
332 void *context) /* I - Devices array */
333{
334 cups_device_t *device; /* Device */
335
336
337 fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
338 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
339 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
340 sdRef, flags, interfaceIndex, errorCode,
341 serviceName ? serviceName : "(null)",
342 regtype ? regtype : "(null)",
343 replyDomain ? replyDomain : "(null)",
344 context);
345
346 /*
347 * Only process "add" data...
348 */
349
350 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
351 return;
352
353 /*
354 * Get the device...
355 */
356
357 device = get_device((cups_array_t *)context, serviceName, regtype,
358 replyDomain);
359
360 /*
361 * Hide locally-registered devices...
362 */
363
364 fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
365 device->fullName);
366 device->sent = 1;
367}
368
369
370/*
371 * 'compare_devices()' - Compare two devices.
372 */
373
374static int /* O - Result of comparison */
375compare_devices(cups_device_t *a, /* I - First device */
376 cups_device_t *b) /* I - Second device */
377{
378 int result = strcmp(a->name, b->name);
379
380 if (result)
381 return (result);
382 else
383 return (strcmp(a->domain, b->domain));
384}
385
386
387/*
388 * 'exec_backend()' - Execute the backend that corresponds to the
389 * resolved service name.
390 */
391
392static void
393exec_backend(char **argv) /* I - Command-line arguments */
394{
395 const char *resolved_uri, /* Resolved device URI */
396 *cups_serverbin; /* Location of programs */
397 char scheme[1024], /* Scheme from URI */
398 *ptr, /* Pointer into scheme */
399 filename[1024]; /* Backend filename */
400
401
402 /*
403 * Resolve the device URI...
404 */
405
406 resolved_uri = backendResolveURI(argv);
407
408 /*
409 * Extract the scheme from the URI...
410 */
411
412 strlcpy(scheme, resolved_uri, sizeof(scheme));
413 if ((ptr = strchr(scheme, ':')) != NULL)
414 *ptr = '\0';
415
416 /*
417 * Get the filename of the backend...
418 */
419
420 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
421 cups_serverbin = CUPS_SERVERBIN;
422
423 snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
424
425 /*
426 * Overwrite the device URIs and run the new backend...
427 */
428
429 setenv("DEVICE_URI", resolved_uri, 1);
430
431 argv[0] = (char *)resolved_uri;
432
433 fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
434
435 execv(filename, argv);
436
437 fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
438 strerror(errno));
439 exit(CUPS_BACKEND_STOP);
440}
441
442
443/*
444 * 'get_device()' - Create or update a device.
445 */
446
447static cups_device_t * /* O - Device */
448get_device(cups_array_t *devices, /* I - Device array */
449 const char *serviceName, /* I - Name of service/device */
450 const char *regtype, /* I - Type of service */
451 const char *replyDomain) /* I - Service domain */
452{
453 cups_device_t key, /* Search key */
454 *device; /* Device */
455 char fullName[1024]; /* Full name for query */
456
457
458 /*
459 * See if this is a new device...
460 */
461
462 key.name = (char *)serviceName;
463 key.domain = (char *)replyDomain;
464
465 if (!strcmp(regtype, "_ipp._tcp.") ||
466 !strcmp(regtype, "_ipp-tls._tcp."))
467 key.type = CUPS_DEVICE_IPP;
468 else if (!strcmp(regtype, "_fax-ipp._tcp."))
469 key.type = CUPS_DEVICE_FAX_IPP;
470 else if (!strcmp(regtype, "_printer._tcp."))
471 key.type = CUPS_DEVICE_PRINTER;
472 else if (!strcmp(regtype, "_pdl-datastream._tcp."))
473 key.type = CUPS_DEVICE_PDL_DATASTREAM;
474 else
475 key.type = CUPS_DEVICE_RIOUSBPRINT;
476
477 if ((device = cupsArrayFind(devices, &key)) != NULL)
478 {
479 /*
480 * No, see if this registration is a higher priority protocol...
481 */
482
483 if (key.type > device->type)
484 {
485 fprintf(stderr, "DEBUG: Updating \"%s\" to \"%s.%s%s\"...\n",
486 device->fullName, serviceName, regtype, replyDomain);
487
488 device->type = key.type;
489 }
490 }
491 else
492 {
493 /*
494 * Yes, add the device...
495 */
496
497 fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
498 replyDomain);
499
500 device = calloc(sizeof(cups_device_t), 1);
501 device->name = strdup(serviceName);
502 device->domain = strdup(replyDomain);
503 device->type = key.type;
504
505 cupsArrayAdd(devices, device);
506 }
507
508 /*
509 * Update the "full name" of this service, which is used for queries...
510 */
511
512 snprintf(fullName, sizeof(fullName), "%s.%s%s", serviceName, regtype,
513 replyDomain);
514
515 if (device->fullName)
516 free(device->fullName);
517
518 device->fullName = strdup(fullName);
519
520 return (device);
521}
522
523
524/*
525 * 'query_callback()' - Process query data.
526 */
527
528static void
529query_callback(
530 DNSServiceRef sdRef, /* I - Service reference */
531 DNSServiceFlags flags, /* I - Data flags */
532 uint32_t interfaceIndex, /* I - Interface */
533 DNSServiceErrorType errorCode, /* I - Error, if any */
534 const char *fullName, /* I - Full service name */
535 uint16_t rrtype, /* I - Record type */
536 uint16_t rrclass, /* I - Record class */
537 uint16_t rdlen, /* I - Length of record data */
538 const void *rdata, /* I - Record data */
539 uint32_t ttl, /* I - Time-to-live */
540 void *context) /* I - Devices array */
541{
542 cups_array_t *devices; /* Device array */
543 char name[1024], /* Service name */
544 *ptr; /* Pointer into name */
545 cups_device_t key, /* Search key */
546 *device; /* Device */
547
548
549 fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
550 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
551 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
552 "context=%p)\n",
553 sdRef, flags, interfaceIndex, errorCode,
554 fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
555 context);
556
557 /*
558 * Only process "add" data...
559 */
560
561 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
562 return;
563
564 /*
565 * Lookup the service in the devices array.
566 */
567
568 devices = (cups_array_t *)context;
569 key.name = name;
570
571 unquote(name, fullName, sizeof(name));
572
573 if ((key.domain = strstr(name, "._tcp.")) != NULL)
574 key.domain += 6;
575 else
576 key.domain = (char *)"local.";
577
578 if ((ptr = strstr(name, "._")) != NULL)
579 *ptr = '\0';
580
581 if ((device = cupsArrayFind(devices, &key)) != NULL)
582 {
583 /*
584 * Found it, pull out the make and model from the TXT record and save it...
585 */
586
587 const void *value; /* Pointer to value */
588 uint8_t valueLen; /* Length of value (max 255) */
589 char make_and_model[512], /* Manufacturer and model */
590 model[256]; /* Model */
591
592
593 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MFG",
594 &valueLen)) == NULL)
595 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MANUFACTURER", &valueLen);
596
597 if (value && valueLen)
598 {
599 memcpy(make_and_model, value, valueLen);
600 make_and_model[valueLen] = '\0';
601 }
602 else
603 make_and_model[0] = '\0';
604
605 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MDL",
606 &valueLen)) == NULL)
607 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MODEL", &valueLen);
608
609 if (value && valueLen)
610 {
611 memcpy(model, value, valueLen);
612 model[valueLen] = '\0';
613 }
614 else if ((value = TXTRecordGetValuePtr(rdlen, rdata, "product",
615 &valueLen)) != NULL && valueLen > 2)
616 {
617 memcpy(model, value + 1, valueLen - 2);
618 model[valueLen - 2] = '\0';
619
620 if (!strcasecmp(model, "GPL Ghostscript") ||
621 !strcasecmp(model, "GNU Ghostscript") ||
622 !strcasecmp(model, "ESP Ghostscript"))
623 {
624 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "ty",
625 &valueLen)) != NULL)
626 {
627 memcpy(model, value, valueLen);
628 model[valueLen] = '\0';
629
630 if ((ptr = strchr(model, ',')) != NULL)
631 *ptr = '\0';
632 }
633 else
634 strcpy(model, "Unknown");
635 }
636 }
637 else
638 strcpy(model, "Unknown");
639
640 if (device->make_and_model)
641 free(device->make_and_model);
642
643 if (make_and_model[0])
644 {
645 strlcat(make_and_model, " ", sizeof(make_and_model));
646 strlcat(make_and_model, model, sizeof(make_and_model));
647 device->make_and_model = strdup(make_and_model);
648 }
649 else
650 device->make_and_model = strdup(model);
651
652 if (device->type == CUPS_DEVICE_IPP &&
653 (value = TXTRecordGetValuePtr(rdlen, rdata, "printer-type",
654 &valueLen)) != NULL)
655 {
656 /*
657 * This is a CUPS printer!
658 */
659
660 device->cups_shared = 1;
661 }
662 }
663 else
664 fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
665}
666
667
668/*
669 * 'unquote()' - Unquote a name string.
670 */
671
672static void
673unquote(char *dst, /* I - Destination buffer */
674 const char *src, /* I - Source string */
675 size_t dstsize) /* I - Size of destination buffer */
676{
677 char *dstend = dst + dstsize - 1; /* End of destination buffer */
678
679
680 while (*src && dst < dstend)
681 {
682 if (*src == '\\')
683 {
684 src ++;
685 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
686 isdigit(src[2] & 255))
687 {
688 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
689 src += 3;
690 }
691 else
692 *dst++ = *src++;
693 }
694 else
695 *dst++ = *src ++;
696 }
697
698 *dst = '\0';
699}
700
701
702/*
703 * End of "$Id$".
704 */
705/*
706 * "$Id$"
707 *
708 * DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
709 *
710 * Copyright 2008 by Apple Inc.
711 *
712 * These coded instructions, statements, and computer programs are the
713 * property of Apple Inc. and are protected by Federal copyright
714 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
715 * "LICENSE" which should have been included with this file. If this
716 * file is missing or damaged, see the license at "http://www.cups.org/".
717 *
718 * This file is subject to the Apple OS-Developed Software exception.
719 *
720 * Contents:
721 *
722 * main() - Browse for printers.
723 * browse_callback() - Browse devices.
724 * browse_local_callback() - Browse local devices.
725 * compare_devices() - Compare two devices.
726 * get_device() - Create or update a device.
727 * query_callback() - Process query data.
728 * unquote() - Unquote a name string.
729 */
730
731/*
732 * Include necessary headers.
733 */
734
735#include "backend-private.h"
736#include <cups/array.h>
737#include <dns_sd.h>
738
739
740/*
741 * Device structure...
742 */
743
744typedef enum
745{
746 CUPS_DEVICE_PRINTER = 0, /* lpd://... */
747 CUPS_DEVICE_IPP, /* ipp://... */
748 CUPS_DEVICE_FAX_IPP, /* ipp://... */
749 CUPS_DEVICE_PDL_DATASTREAM, /* socket://... */
750 CUPS_DEVICE_RIOUSBPRINT /* riousbprint://... */
751} cups_devtype_t;
752
753
754typedef struct
755{
756 DNSServiceRef ref; /* Service reference for resolve */
757 char *name, /* Service name */
758 *domain, /* Domain name */
759 *fullName, /* Full name */
760 *make_and_model; /* Make and model from TXT record */
761 cups_devtype_t type; /* Device registration type */
762 int cups_shared, /* CUPS shared printer? */
763 sent; /* Did we list the device? */
764} cups_device_t;
765
766
767/*
768 * Local functions...
769 */
770
771static void browse_callback(DNSServiceRef sdRef,
772 DNSServiceFlags flags,
773 uint32_t interfaceIndex,
774 DNSServiceErrorType errorCode,
775 const char *serviceName,
776 const char *regtype,
777 const char *replyDomain, void *context);
778static void browse_local_callback(DNSServiceRef sdRef,
779 DNSServiceFlags flags,
780 uint32_t interfaceIndex,
781 DNSServiceErrorType errorCode,
782 const char *serviceName,
783 const char *regtype,
784 const char *replyDomain,
785 void *context);
786static int compare_devices(cups_device_t *a, cups_device_t *b);
787static void exec_backend(char **argv);
788static cups_device_t *get_device(cups_array_t *devices,
789 const char *serviceName,
790 const char *regtype,
791 const char *replyDomain);
792static void query_callback(DNSServiceRef sdRef,
793 DNSServiceFlags flags,
794 uint32_t interfaceIndex,
795 DNSServiceErrorType errorCode,
796 const char *fullName, uint16_t rrtype,
797 uint16_t rrclass, uint16_t rdlen,
798 const void *rdata, uint32_t ttl,
799 void *context);
800static void unquote(char *dst, const char *src, size_t dstsize);
801
802
803/*
804 * 'main()' - Browse for printers.
805 */
806
807int /* O - Exit status */
808main(int argc, /* I - Number of command-line args */
809 char *argv[]) /* I - Command-line arguments */
810{
811 DNSServiceRef main_ref, /* Main service reference */
812 fax_ipp_ref, /* IPP fax service reference */
813 ipp_ref, /* IPP service reference */
814 ipp_tls_ref, /* IPP w/TLS service reference */
815 local_fax_ipp_ref, /* Local IPP fax service reference */
816 local_ipp_ref, /* Local IPP service reference */
817 local_ipp_tls_ref, /* Local IPP w/TLS service reference */
818 local_printer_ref, /* Local LPD service reference */
819 pdl_datastream_ref, /* AppSocket service reference */
820 printer_ref, /* LPD service reference */
821 riousbprint_ref; /* Remote IO service reference */
822 int fd; /* Main file descriptor */
823 fd_set input; /* Input set for select() */
824 struct timeval timeout; /* Timeout for select() */
825 cups_array_t *devices; /* Device array */
826 cups_device_t *device; /* Current device */
827
828
829 /*
830 * Check command-line...
831 */
832
833 setbuf(stderr, NULL);
834
835 if (argc >= 6)
836 exec_backend(argv);
837 else if (argc != 1)
838 {
839 fputs("Usage: mdns job user title copies options [filename(s)]\n", stderr);
840 return (1);
841 }
842
843 /*
844 * Create an array to track devices...
845 */
846
847 devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
848
849 /*
850 * Browse for different kinds of printers...
851 */
852
853 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
854 {
855 perror("ERROR: Unable to create service connection");
856 return (1);
857 }
858
859 fd = DNSServiceRefSockFD(main_ref);
860
861 fax_ipp_ref = main_ref;
862 DNSServiceBrowse(&fax_ipp_ref, kDNSServiceFlagsShareConnection, 0,
863 "_fax-ipp._tcp", NULL, browse_callback, devices);
864
865 ipp_ref = main_ref;
866 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
867 "_ipp._tcp", NULL, browse_callback, devices);
868
869 ipp_tls_ref = main_ref;
870 DNSServiceBrowse(&ipp_tls_ref, kDNSServiceFlagsShareConnection, 0,
871 "_ipp-tls._tcp", NULL, browse_callback, devices);
872
873 local_fax_ipp_ref = main_ref;
874 DNSServiceBrowse(&local_fax_ipp_ref, kDNSServiceFlagsShareConnection,
875 kDNSServiceInterfaceIndexLocalOnly,
876 "_fax-ipp._tcp", NULL, browse_local_callback, devices);
877
878 local_ipp_ref = main_ref;
879 DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
880 kDNSServiceInterfaceIndexLocalOnly,
881 "_ipp._tcp", NULL, browse_local_callback, devices);
882
883 local_ipp_tls_ref = main_ref;
884 DNSServiceBrowse(&local_ipp_tls_ref, kDNSServiceFlagsShareConnection,
885 kDNSServiceInterfaceIndexLocalOnly,
886 "_ipp-tls._tcp", NULL, browse_local_callback, devices);
887
888 local_printer_ref = main_ref;
889 DNSServiceBrowse(&local_printer_ref, kDNSServiceFlagsShareConnection,
890 kDNSServiceInterfaceIndexLocalOnly,
891 "_printer._tcp", NULL, browse_local_callback, devices);
892
893 pdl_datastream_ref = main_ref;
894 DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
895 "_pdl-datastream._tcp", NULL, browse_callback, devices);
896
897 printer_ref = main_ref;
898 DNSServiceBrowse(&printer_ref, kDNSServiceFlagsShareConnection, 0,
899 "_printer._tcp", NULL, browse_callback, devices);
900
901 riousbprint_ref = main_ref;
902 DNSServiceBrowse(&riousbprint_ref, kDNSServiceFlagsShareConnection, 0,
903 "_riousbprint._tcp", NULL, browse_callback, devices);
904
905 /*
906 * Loop until we are killed...
907 */
908
909 for (;;)
910 {
911 FD_ZERO(&input);
912 FD_SET(fd, &input);
913
914 timeout.tv_sec = 1;
915 timeout.tv_usec = 0;
916
917 if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
918 continue;
919
920 if (FD_ISSET(fd, &input))
921 {
922 /*
923 * Process results of our browsing...
924 */
925
926 DNSServiceProcessResult(main_ref);
927 }
928 else
929 {
930 /*
931 * Announce any devices we've found...
932 */
933
934 char device_uri[1024]; /* Device URI */
935 static const char * const schemes[] =
936 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
937 /* URI schemes for devices */
938
939
940 for (device = (cups_device_t *)cupsArrayFirst(devices);
941 device;
942 device = (cups_device_t *)cupsArrayNext(devices))
943 if (!device->ref && !device->sent)
944 {
945 /*
946 * Found the device, now get the TXT record(s) for it...
947 */
948
949 device->ref = main_ref;
950
951 fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
952
953 if (DNSServiceQueryRecord(&(device->ref),
954 kDNSServiceFlagsShareConnection,
955 0, device->fullName, kDNSServiceType_TXT,
956 kDNSServiceClass_IN, query_callback,
957 devices) != kDNSServiceErr_NoError)
958 fputs("ERROR: Unable to query for TXT records!\n", stderr);
959 }
960 else if (!device->sent)
961 {
962 /*
963 * Got the TXT records, now report the device...
964 */
965
966 DNSServiceRefDeallocate(device->ref);
967 device->ref = 0;
968
969 httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
970 schemes[device->type], NULL, device->fullName, 0,
971 device->cups_shared ? "/cups" : "");
972
973 printf("network %s \"%s\" \"%s\"\n", device_uri,
974 device->make_and_model ? device->make_and_model : "Unknown",
975 device->name);
976
977 device->sent = 1;
978 }
979 }
980 }
981}
982
983
984/*
985 * 'browse_callback()' - Browse devices.
986 */
987
988static void
989browse_callback(
990 DNSServiceRef sdRef, /* I - Service reference */
991 DNSServiceFlags flags, /* I - Option flags */
992 uint32_t interfaceIndex, /* I - Interface number */
993 DNSServiceErrorType errorCode, /* I - Error, if any */
994 const char *serviceName, /* I - Name of service/device */
995 const char *regtype, /* I - Type of service */
996 const char *replyDomain, /* I - Service domain */
997 void *context) /* I - Devices array */
998{
999 fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
1000 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
1001 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
1002 sdRef, flags, interfaceIndex, errorCode,
1003 serviceName ? serviceName : "(null)",
1004 regtype ? regtype : "(null)",
1005 replyDomain ? replyDomain : "(null)",
1006 context);
1007
1008 /*
1009 * Only process "add" data...
1010 */
1011
1012 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1013 return;
1014
1015 /*
1016 * Get the device...
1017 */
1018
1019 get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
1020}
1021
1022
1023/*
1024 * 'browse_local_callback()' - Browse local devices.
1025 */
1026
1027static void
1028browse_local_callback(
1029 DNSServiceRef sdRef, /* I - Service reference */
1030 DNSServiceFlags flags, /* I - Option flags */
1031 uint32_t interfaceIndex, /* I - Interface number */
1032 DNSServiceErrorType errorCode, /* I - Error, if any */
1033 const char *serviceName, /* I - Name of service/device */
1034 const char *regtype, /* I - Type of service */
1035 const char *replyDomain, /* I - Service domain */
1036 void *context) /* I - Devices array */
1037{
1038 cups_device_t *device; /* Device */
1039
1040
1041 fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
1042 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
1043 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
1044 sdRef, flags, interfaceIndex, errorCode,
1045 serviceName ? serviceName : "(null)",
1046 regtype ? regtype : "(null)",
1047 replyDomain ? replyDomain : "(null)",
1048 context);
1049
1050 /*
1051 * Only process "add" data...
1052 */
1053
1054 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1055 return;
1056
1057 /*
1058 * Get the device...
1059 */
1060
1061 device = get_device((cups_array_t *)context, serviceName, regtype,
1062 replyDomain);
1063
1064 /*
1065 * Hide locally-registered devices...
1066 */
1067
1068 fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
1069 device->fullName);
1070 device->sent = 1;
1071}
1072
1073
1074/*
1075 * 'compare_devices()' - Compare two devices.
1076 */
1077
1078static int /* O - Result of comparison */
1079compare_devices(cups_device_t *a, /* I - First device */
1080 cups_device_t *b) /* I - Second device */
1081{
1082 int result = strcmp(a->name, b->name);
1083
1084 if (result)
1085 return (result);
1086 else
1087 return (strcmp(a->domain, b->domain));
1088}
1089
1090
1091/*
1092 * 'exec_backend()' - Execute the backend that corresponds to the
1093 * resolved service name.
1094 */
1095
1096static void
1097exec_backend(char **argv) /* I - Command-line arguments */
1098{
1099 const char *resolved_uri, /* Resolved device URI */
1100 *cups_serverbin; /* Location of programs */
1101 char scheme[1024], /* Scheme from URI */
1102 *ptr, /* Pointer into scheme */
1103 filename[1024]; /* Backend filename */
1104
1105
1106 /*
1107 * Resolve the device URI...
1108 */
1109
1110 resolved_uri = backendResolveURI(argv);
1111
1112 /*
1113 * Extract the scheme from the URI...
1114 */
1115
1116 strlcpy(scheme, resolved_uri, sizeof(scheme));
1117 if ((ptr = strchr(scheme, ':')) != NULL)
1118 *ptr = '\0';
1119
1120 /*
1121 * Get the filename of the backend...
1122 */
1123
1124 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
1125 cups_serverbin = CUPS_SERVERBIN;
1126
1127 snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
1128
1129 /*
1130 * Overwrite the device URIs and run the new backend...
1131 */
1132
1133 setenv("DEVICE_URI", resolved_uri, 1);
1134
1135 argv[0] = (char *)resolved_uri;
1136
1137 fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
1138
1139 execv(filename, argv);
1140
1141 fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
1142 strerror(errno));
1143 exit(CUPS_BACKEND_STOP);
1144}
1145
1146
1147/*
1148 * 'get_device()' - Create or update a device.
1149 */
1150
1151static cups_device_t * /* O - Device */
1152get_device(cups_array_t *devices, /* I - Device array */
1153 const char *serviceName, /* I - Name of service/device */
1154 const char *regtype, /* I - Type of service */
1155 const char *replyDomain) /* I - Service domain */
1156{
1157 cups_device_t key, /* Search key */
1158 *device; /* Device */
1159 char fullName[1024]; /* Full name for query */
1160
1161
1162 /*
1163 * See if this is a new device...
1164 */
1165
1166 key.name = (char *)serviceName;
1167 key.domain = (char *)replyDomain;
1168
1169 if (!strcmp(regtype, "_ipp._tcp.") ||
1170 !strcmp(regtype, "_ipp-tls._tcp."))
1171 key.type = CUPS_DEVICE_IPP;
1172 else if (!strcmp(regtype, "_fax-ipp._tcp."))
1173 key.type = CUPS_DEVICE_FAX_IPP;
1174 else if (!strcmp(regtype, "_printer._tcp."))
1175 key.type = CUPS_DEVICE_PRINTER;
1176 else if (!strcmp(regtype, "_pdl-datastream._tcp."))
1177 key.type = CUPS_DEVICE_PDL_DATASTREAM;
1178 else
1179 key.type = CUPS_DEVICE_RIOUSBPRINT;
1180
1181 if ((device = cupsArrayFind(devices, &key)) != NULL)
1182 {
1183 /*
1184 * No, see if this registration is a higher priority protocol...
1185 */
1186
1187 if (key.type > device->type)
1188 {
1189 fprintf(stderr, "DEBUG: Updating \"%s\" to \"%s.%s%s\"...\n",
1190 device->fullName, serviceName, regtype, replyDomain);
1191
1192 device->type = key.type;
1193 }
1194 }
1195 else
1196 {
1197 /*
1198 * Yes, add the device...
1199 */
1200
1201 fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
1202 replyDomain);
1203
1204 device = calloc(sizeof(cups_device_t), 1);
1205 device->name = strdup(serviceName);
1206 device->domain = strdup(replyDomain);
1207 device->type = key.type;
1208
1209 cupsArrayAdd(devices, device);
1210 }
1211
1212 /*
1213 * Update the "full name" of this service, which is used for queries...
1214 */
1215
1216 snprintf(fullName, sizeof(fullName), "%s.%s%s", serviceName, regtype,
1217 replyDomain);
1218
1219 if (device->fullName)
1220 free(device->fullName);
1221
1222 device->fullName = strdup(fullName);
1223
1224 return (device);
1225}
1226
1227
1228/*
1229 * 'query_callback()' - Process query data.
1230 */
1231
1232static void
1233query_callback(
1234 DNSServiceRef sdRef, /* I - Service reference */
1235 DNSServiceFlags flags, /* I - Data flags */
1236 uint32_t interfaceIndex, /* I - Interface */
1237 DNSServiceErrorType errorCode, /* I - Error, if any */
1238 const char *fullName, /* I - Full service name */
1239 uint16_t rrtype, /* I - Record type */
1240 uint16_t rrclass, /* I - Record class */
1241 uint16_t rdlen, /* I - Length of record data */
1242 const void *rdata, /* I - Record data */
1243 uint32_t ttl, /* I - Time-to-live */
1244 void *context) /* I - Devices array */
1245{
1246 cups_array_t *devices; /* Device array */
1247 char name[1024], /* Service name */
1248 *ptr; /* Pointer into name */
1249 cups_device_t key, /* Search key */
1250 *device; /* Device */
1251
1252
1253 fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
1254 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
1255 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
1256 "context=%p)\n",
1257 sdRef, flags, interfaceIndex, errorCode,
1258 fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
1259 context);
1260
1261 /*
1262 * Only process "add" data...
1263 */
1264
1265 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1266 return;
1267
1268 /*
1269 * Lookup the service in the devices array.
1270 */
1271
1272 devices = (cups_array_t *)context;
1273 key.name = name;
1274
1275 unquote(name, fullName, sizeof(name));
1276
1277 if ((key.domain = strstr(name, "._tcp.")) != NULL)
1278 key.domain += 6;
1279 else
1280 key.domain = (char *)"local.";
1281
1282 if ((ptr = strstr(name, "._")) != NULL)
1283 *ptr = '\0';
1284
1285 if ((device = cupsArrayFind(devices, &key)) != NULL)
1286 {
1287 /*
1288 * Found it, pull out the make and model from the TXT record and save it...
1289 */
1290
1291 const void *value; /* Pointer to value */
1292 uint8_t valueLen; /* Length of value (max 255) */
1293 char make_and_model[512], /* Manufacturer and model */
1294 model[256]; /* Model */
1295
1296
1297 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MFG",
1298 &valueLen)) == NULL)
1299 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MANUFACTURER", &valueLen);
1300
1301 if (value && valueLen)
1302 {
1303 memcpy(make_and_model, value, valueLen);
1304 make_and_model[valueLen] = '\0';
1305 }
1306 else
1307 make_and_model[0] = '\0';
1308
1309 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MDL",
1310 &valueLen)) == NULL)
1311 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MODEL", &valueLen);
1312
1313 if (value && valueLen)
1314 {
1315 memcpy(model, value, valueLen);
1316 model[valueLen] = '\0';
1317 }
1318 else if ((value = TXTRecordGetValuePtr(rdlen, rdata, "product",
1319 &valueLen)) != NULL && valueLen > 2)
1320 {
1321 memcpy(model, value + 1, valueLen - 2);
1322 model[valueLen - 2] = '\0';
1323
1324 if (!strcasecmp(model, "GPL Ghostscript") ||
1325 !strcasecmp(model, "GNU Ghostscript") ||
1326 !strcasecmp(model, "ESP Ghostscript"))
1327 {
1328 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "ty",
1329 &valueLen)) != NULL)
1330 {
1331 memcpy(model, value, valueLen);
1332 model[valueLen] = '\0';
1333
1334 if ((ptr = strchr(model, ',')) != NULL)
1335 *ptr = '\0';
1336 }
1337 else
1338 strcpy(model, "Unknown");
1339 }
1340 }
1341 else
1342 strcpy(model, "Unknown");
1343
1344 if (device->make_and_model)
1345 free(device->make_and_model);
1346
1347 if (make_and_model[0])
1348 {
1349 strlcat(make_and_model, " ", sizeof(make_and_model));
1350 strlcat(make_and_model, model, sizeof(make_and_model));
1351 device->make_and_model = strdup(make_and_model);
1352 }
1353 else
1354 device->make_and_model = strdup(model);
1355
1356 if (device->type == CUPS_DEVICE_IPP &&
1357 (value = TXTRecordGetValuePtr(rdlen, rdata, "printer-type",
1358 &valueLen)) != NULL)
1359 {
1360 /*
1361 * This is a CUPS printer!
1362 */
1363
1364 device->cups_shared = 1;
1365 }
1366 }
1367 else
1368 fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
1369}
1370
1371
1372/*
1373 * 'unquote()' - Unquote a name string.
1374 */
1375
1376static void
1377unquote(char *dst, /* I - Destination buffer */
1378 const char *src, /* I - Source string */
1379 size_t dstsize) /* I - Size of destination buffer */
1380{
1381 char *dstend = dst + dstsize - 1; /* End of destination buffer */
1382
1383
1384 while (*src && dst < dstend)
1385 {
1386 if (*src == '\\')
1387 {
1388 src ++;
1389 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
1390 isdigit(src[2] & 255))
1391 {
1392 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
1393 src += 3;
1394 }
1395 else
1396 *dst++ = *src++;
1397 }
1398 else
1399 *dst++ = *src ++;
1400 }
1401
1402 *dst = '\0';
1403}
1404
1405
1406/*
1407 * End of "$Id$".
1408 */