]> git.ipfire.org Git - thirdparty/cups.git/blame - backend/mdns.c
Merge changes from CUPS 1.4svn-r7715.
[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.
7a14d768
MS
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 */
5eb9da71 231 int count; /* Number of queries */
7a14d768
MS
232 static const char * const schemes[] =
233 { "lpd", "ipp", "ipp", "socket", "riousbprint" };
234 /* URI schemes for devices */
235
236
5eb9da71 237 for (device = (cups_device_t *)cupsArrayFirst(devices), count = 0;
7a14d768
MS
238 device;
239 device = (cups_device_t *)cupsArrayNext(devices))
240 if (!device->ref && !device->sent)
241 {
242 /*
243 * Found the device, now get the TXT record(s) for it...
244 */
245
5eb9da71
MS
246 if (count < 10)
247 {
248 device->ref = main_ref;
249
250 fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
251
252 if (DNSServiceQueryRecord(&(device->ref),
253 kDNSServiceFlagsShareConnection,
254 0, device->fullName, kDNSServiceType_TXT,
255 kDNSServiceClass_IN, query_callback,
256 devices) != kDNSServiceErr_NoError)
257 fputs("ERROR: Unable to query for TXT records!\n", stderr);
258 else
259 count ++;
260 }
7a14d768
MS
261 }
262 else if (!device->sent)
263 {
264 /*
265 * Got the TXT records, now report the device...
266 */
267
268 DNSServiceRefDeallocate(device->ref);
269 device->ref = 0;
270
c9fc04c6
MS
271 httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
272 schemes[device->type], NULL, device->fullName, 0,
273 device->cups_shared ? "/cups" : "/");
7a14d768
MS
274
275 printf("network %s \"%s\" \"%s\"\n", device_uri,
276 device->make_and_model ? device->make_and_model : "Unknown",
277 device->name);
a0f6818e 278 fflush(stdout);
7a14d768
MS
279
280 device->sent = 1;
281 }
282 }
283 }
284}
285
286
287/*
288 * 'browse_callback()' - Browse devices.
289 */
290
291static void
292browse_callback(
293 DNSServiceRef sdRef, /* I - Service reference */
294 DNSServiceFlags flags, /* I - Option flags */
295 uint32_t interfaceIndex, /* I - Interface number */
296 DNSServiceErrorType errorCode, /* I - Error, if any */
297 const char *serviceName, /* I - Name of service/device */
298 const char *regtype, /* I - Type of service */
299 const char *replyDomain, /* I - Service domain */
300 void *context) /* I - Devices array */
301{
302 fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
303 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
304 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
305 sdRef, flags, interfaceIndex, errorCode,
306 serviceName ? serviceName : "(null)",
307 regtype ? regtype : "(null)",
308 replyDomain ? replyDomain : "(null)",
309 context);
310
311 /*
312 * Only process "add" data...
313 */
314
315 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
316 return;
317
318 /*
319 * Get the device...
320 */
321
322 get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
323}
324
325
326/*
327 * 'browse_local_callback()' - Browse local devices.
328 */
329
330static void
331browse_local_callback(
332 DNSServiceRef sdRef, /* I - Service reference */
333 DNSServiceFlags flags, /* I - Option flags */
334 uint32_t interfaceIndex, /* I - Interface number */
335 DNSServiceErrorType errorCode, /* I - Error, if any */
336 const char *serviceName, /* I - Name of service/device */
337 const char *regtype, /* I - Type of service */
338 const char *replyDomain, /* I - Service domain */
339 void *context) /* I - Devices array */
340{
341 cups_device_t *device; /* Device */
342
343
344 fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
345 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
346 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
347 sdRef, flags, interfaceIndex, errorCode,
348 serviceName ? serviceName : "(null)",
349 regtype ? regtype : "(null)",
350 replyDomain ? replyDomain : "(null)",
351 context);
352
353 /*
354 * Only process "add" data...
355 */
356
357 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
358 return;
359
360 /*
361 * Get the device...
362 */
363
364 device = get_device((cups_array_t *)context, serviceName, regtype,
365 replyDomain);
366
367 /*
368 * Hide locally-registered devices...
369 */
370
371 fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
372 device->fullName);
373 device->sent = 1;
374}
375
376
377/*
378 * 'compare_devices()' - Compare two devices.
379 */
380
381static int /* O - Result of comparison */
382compare_devices(cups_device_t *a, /* I - First device */
383 cups_device_t *b) /* I - Second device */
384{
385 int result = strcmp(a->name, b->name);
386
387 if (result)
388 return (result);
389 else
390 return (strcmp(a->domain, b->domain));
391}
392
393
394/*
395 * 'exec_backend()' - Execute the backend that corresponds to the
396 * resolved service name.
397 */
398
399static void
400exec_backend(char **argv) /* I - Command-line arguments */
401{
402 const char *resolved_uri, /* Resolved device URI */
403 *cups_serverbin; /* Location of programs */
404 char scheme[1024], /* Scheme from URI */
405 *ptr, /* Pointer into scheme */
406 filename[1024]; /* Backend filename */
407
408
409 /*
410 * Resolve the device URI...
411 */
412
5eb9da71 413 resolved_uri = cupsBackendDeviceURI(argv);
7a14d768
MS
414
415 /*
416 * Extract the scheme from the URI...
417 */
418
419 strlcpy(scheme, resolved_uri, sizeof(scheme));
420 if ((ptr = strchr(scheme, ':')) != NULL)
421 *ptr = '\0';
422
423 /*
424 * Get the filename of the backend...
425 */
426
427 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
428 cups_serverbin = CUPS_SERVERBIN;
429
430 snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
431
432 /*
433 * Overwrite the device URIs and run the new backend...
434 */
435
436 setenv("DEVICE_URI", resolved_uri, 1);
437
438 argv[0] = (char *)resolved_uri;
439
440 fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
441
442 execv(filename, argv);
443
444 fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
445 strerror(errno));
446 exit(CUPS_BACKEND_STOP);
447}
448
449
450/*
451 * 'get_device()' - Create or update a device.
452 */
453
454static cups_device_t * /* O - Device */
455get_device(cups_array_t *devices, /* I - Device array */
456 const char *serviceName, /* I - Name of service/device */
457 const char *regtype, /* I - Type of service */
458 const char *replyDomain) /* I - Service domain */
459{
460 cups_device_t key, /* Search key */
461 *device; /* Device */
462 char fullName[1024]; /* Full name for query */
463
464
465 /*
466 * See if this is a new device...
467 */
468
469 key.name = (char *)serviceName;
470 key.domain = (char *)replyDomain;
471
472 if (!strcmp(regtype, "_ipp._tcp.") ||
473 !strcmp(regtype, "_ipp-tls._tcp."))
474 key.type = CUPS_DEVICE_IPP;
475 else if (!strcmp(regtype, "_fax-ipp._tcp."))
476 key.type = CUPS_DEVICE_FAX_IPP;
477 else if (!strcmp(regtype, "_printer._tcp."))
478 key.type = CUPS_DEVICE_PRINTER;
479 else if (!strcmp(regtype, "_pdl-datastream._tcp."))
480 key.type = CUPS_DEVICE_PDL_DATASTREAM;
481 else
482 key.type = CUPS_DEVICE_RIOUSBPRINT;
483
484 if ((device = cupsArrayFind(devices, &key)) != NULL)
485 {
486 /*
487 * No, see if this registration is a higher priority protocol...
488 */
489
490 if (key.type > device->type)
491 {
492 fprintf(stderr, "DEBUG: Updating \"%s\" to \"%s.%s%s\"...\n",
493 device->fullName, serviceName, regtype, replyDomain);
494
495 device->type = key.type;
496 }
497 }
498 else
499 {
500 /*
501 * Yes, add the device...
502 */
503
504 fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
505 replyDomain);
506
507 device = calloc(sizeof(cups_device_t), 1);
508 device->name = strdup(serviceName);
509 device->domain = strdup(replyDomain);
510 device->type = key.type;
511
512 cupsArrayAdd(devices, device);
513 }
514
515 /*
516 * Update the "full name" of this service, which is used for queries...
517 */
518
519 snprintf(fullName, sizeof(fullName), "%s.%s%s", serviceName, regtype,
520 replyDomain);
521
522 if (device->fullName)
523 free(device->fullName);
524
525 device->fullName = strdup(fullName);
526
527 return (device);
528}
529
530
531/*
532 * 'query_callback()' - Process query data.
533 */
534
535static void
536query_callback(
537 DNSServiceRef sdRef, /* I - Service reference */
538 DNSServiceFlags flags, /* I - Data flags */
539 uint32_t interfaceIndex, /* I - Interface */
540 DNSServiceErrorType errorCode, /* I - Error, if any */
541 const char *fullName, /* I - Full service name */
542 uint16_t rrtype, /* I - Record type */
543 uint16_t rrclass, /* I - Record class */
544 uint16_t rdlen, /* I - Length of record data */
545 const void *rdata, /* I - Record data */
546 uint32_t ttl, /* I - Time-to-live */
547 void *context) /* I - Devices array */
548{
549 cups_array_t *devices; /* Device array */
550 char name[1024], /* Service name */
551 *ptr; /* Pointer into name */
552 cups_device_t key, /* Search key */
553 *device; /* Device */
554
555
556 fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
557 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
558 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
559 "context=%p)\n",
560 sdRef, flags, interfaceIndex, errorCode,
561 fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
562 context);
563
564 /*
565 * Only process "add" data...
566 */
567
568 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
569 return;
570
571 /*
572 * Lookup the service in the devices array.
573 */
574
575 devices = (cups_array_t *)context;
576 key.name = name;
577
578 unquote(name, fullName, sizeof(name));
579
580 if ((key.domain = strstr(name, "._tcp.")) != NULL)
581 key.domain += 6;
582 else
583 key.domain = (char *)"local.";
584
585 if ((ptr = strstr(name, "._")) != NULL)
586 *ptr = '\0';
587
588 if ((device = cupsArrayFind(devices, &key)) != NULL)
589 {
590 /*
591 * Found it, pull out the make and model from the TXT record and save it...
592 */
593
594 const void *value; /* Pointer to value */
595 uint8_t valueLen; /* Length of value (max 255) */
596 char make_and_model[512], /* Manufacturer and model */
597 model[256]; /* Model */
598
599
600 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MFG",
601 &valueLen)) == NULL)
602 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MANUFACTURER", &valueLen);
603
604 if (value && valueLen)
605 {
606 memcpy(make_and_model, value, valueLen);
607 make_and_model[valueLen] = '\0';
608 }
609 else
610 make_and_model[0] = '\0';
611
612 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MDL",
613 &valueLen)) == NULL)
614 value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MODEL", &valueLen);
615
616 if (value && valueLen)
617 {
618 memcpy(model, value, valueLen);
619 model[valueLen] = '\0';
620 }
621 else if ((value = TXTRecordGetValuePtr(rdlen, rdata, "product",
622 &valueLen)) != NULL && valueLen > 2)
623 {
5eb9da71
MS
624 if (((char *)value)[0] == '(')
625 {
626 /*
627 * Strip parenthesis...
628 */
629
630 memcpy(model, value + 1, valueLen - 2);
631 model[valueLen - 2] = '\0';
632 }
633 else
634 {
635 memcpy(model, value, valueLen);
636 model[valueLen] = '\0';
637 }
7a14d768
MS
638
639 if (!strcasecmp(model, "GPL Ghostscript") ||
640 !strcasecmp(model, "GNU Ghostscript") ||
641 !strcasecmp(model, "ESP Ghostscript"))
642 {
643 if ((value = TXTRecordGetValuePtr(rdlen, rdata, "ty",
644 &valueLen)) != NULL)
645 {
646 memcpy(model, value, valueLen);
647 model[valueLen] = '\0';
648
649 if ((ptr = strchr(model, ',')) != NULL)
650 *ptr = '\0';
651 }
652 else
653 strcpy(model, "Unknown");
654 }
655 }
656 else
657 strcpy(model, "Unknown");
658
659 if (device->make_and_model)
660 free(device->make_and_model);
661
662 if (make_and_model[0])
663 {
664 strlcat(make_and_model, " ", sizeof(make_and_model));
665 strlcat(make_and_model, model, sizeof(make_and_model));
666 device->make_and_model = strdup(make_and_model);
667 }
668 else
669 device->make_and_model = strdup(model);
670
a0f6818e
MS
671 if ((device->type == CUPS_DEVICE_IPP ||
672 device->type == CUPS_DEVICE_PRINTER) &&
7a14d768
MS
673 (value = TXTRecordGetValuePtr(rdlen, rdata, "printer-type",
674 &valueLen)) != NULL)
675 {
676 /*
677 * This is a CUPS printer!
678 */
679
680 device->cups_shared = 1;
a0f6818e
MS
681
682 if (device->type == CUPS_DEVICE_PRINTER)
683 device->sent = 1;
7a14d768
MS
684 }
685 }
686 else
687 fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
688}
689
690
691/*
692 * 'unquote()' - Unquote a name string.
693 */
694
695static void
696unquote(char *dst, /* I - Destination buffer */
697 const char *src, /* I - Source string */
698 size_t dstsize) /* I - Size of destination buffer */
699{
700 char *dstend = dst + dstsize - 1; /* End of destination buffer */
701
702
703 while (*src && dst < dstend)
704 {
705 if (*src == '\\')
706 {
707 src ++;
708 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
709 isdigit(src[2] & 255))
710 {
711 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
712 src += 3;
713 }
714 else
715 *dst++ = *src++;
716 }
717 else
718 *dst++ = *src ++;
719 }
720
721 *dst = '\0';
722}
723
724
725/*
726 * End of "$Id$".
727 */