]> git.ipfire.org Git - thirdparty/cups.git/blob - backend/dnssd.c
1ac000f37f173794835c69fb671380fa3a6c071c
[thirdparty/cups.git] / backend / dnssd.c
1 /*
2 * "$Id$"
3 *
4 * DNS-SD discovery backend for CUPS.
5 *
6 * Copyright 2008-2012 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 * client_callback() - Avahi client callback function.
22 * compare_devices() - Compare two devices.
23 * exec_backend() - Execute the backend that corresponds to the
24 * resolved service name.
25 * device_type() - Get DNS-SD type enumeration from string.
26 * get_device() - Create or update a device.
27 * query_callback() - Process query data.
28 * find_device() - Find a device from its name and domain.
29 * sigterm_handler() - Handle termination signals.
30 * unquote() - Unquote a name string.
31 */
32
33 /*
34 * Include necessary headers.
35 */
36
37 #include "backend-private.h"
38 #include <cups/array.h>
39 #ifdef HAVE_DNSSD
40 # include <dns_sd.h>
41 #endif /* HAVE_DNSSD */
42 #ifdef HAVE_AVAHI
43 # include <avahi-client/client.h>
44 # include <avahi-client/lookup.h>
45 # include <avahi-common/simple-watch.h>
46 # include <avahi-common/domain.h>
47 # include <avahi-common/error.h>
48 # include <avahi-common/malloc.h>
49 #define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
50 #endif /* HAVE_AVAHI */
51
52
53 /*
54 * Device structure...
55 */
56
57 typedef enum
58 {
59 CUPS_DEVICE_PRINTER = 0, /* lpd://... */
60 CUPS_DEVICE_IPPS, /* ipps://... */
61 CUPS_DEVICE_IPP, /* ipp://... */
62 CUPS_DEVICE_FAX_IPP, /* ipp://... */
63 CUPS_DEVICE_PDL_DATASTREAM, /* socket://... */
64 CUPS_DEVICE_RIOUSBPRINT /* riousbprint://... */
65 } cups_devtype_t;
66
67
68 typedef struct
69 {
70 #ifdef HAVE_DNSSD
71 DNSServiceRef ref; /* Service reference for query */
72 #endif /* HAVE_DNSSD */
73 #ifdef HAVE_AVAHI
74 AvahiRecordBrowser *ref; /* Browser for query */
75 #endif /* HAVE_AVAHI */
76 char *name, /* Service name */
77 *domain, /* Domain name */
78 *fullName, /* Full name */
79 *make_and_model, /* Make and model from TXT record */
80 *device_id, /* 1284 device ID from TXT record */
81 *uuid; /* UUID from TXT record */
82 cups_devtype_t type; /* Device registration type */
83 int priority, /* Priority associated with type */
84 cups_shared, /* CUPS shared printer? */
85 sent; /* Did we list the device? */
86 } cups_device_t;
87
88
89 /*
90 * Local globals...
91 */
92
93 static int job_canceled = 0;
94 /* Set to 1 on SIGTERM */
95 #ifdef HAVE_AVAHI
96 static AvahiSimplePoll *simple_poll = NULL;
97 /* Poll information */
98 static int got_data = 0; /* Got data from poll? */
99 #endif /* HAVE_AVAHI */
100
101
102 /*
103 * Local functions...
104 */
105
106 #ifdef HAVE_DNSSD
107 static void browse_callback(DNSServiceRef sdRef,
108 DNSServiceFlags flags,
109 uint32_t interfaceIndex,
110 DNSServiceErrorType errorCode,
111 const char *serviceName,
112 const char *regtype,
113 const char *replyDomain, void *context)
114 __attribute__((nonnull(1,5,6,7,8)));
115 static void browse_local_callback(DNSServiceRef sdRef,
116 DNSServiceFlags flags,
117 uint32_t interfaceIndex,
118 DNSServiceErrorType errorCode,
119 const char *serviceName,
120 const char *regtype,
121 const char *replyDomain,
122 void *context)
123 __attribute__((nonnull(1,5,6,7,8)));
124 #endif /* HAVE_DNSSD */
125 #ifdef HAVE_AVAHI
126 static void browse_callback(AvahiServiceBrowser *browser,
127 AvahiIfIndex interface,
128 AvahiProtocol protocol,
129 AvahiBrowserEvent event,
130 const char *serviceName,
131 const char *regtype,
132 const char *replyDomain,
133 AvahiLookupResultFlags flags,
134 void *context);
135 static void client_callback(AvahiClient *client,
136 AvahiClientState state,
137 void *context);
138 #endif /* HAVE_AVAHI */
139
140 static int compare_devices(cups_device_t *a, cups_device_t *b);
141 static void exec_backend(char **argv);
142 static cups_device_t *get_device(cups_array_t *devices,
143 const char *serviceName,
144 const char *regtype,
145 const char *replyDomain)
146 __attribute__((nonnull(1,2,3,4)));
147 #ifdef HAVE_DNSSD
148 static void query_callback(DNSServiceRef sdRef,
149 DNSServiceFlags flags,
150 uint32_t interfaceIndex,
151 DNSServiceErrorType errorCode,
152 const char *fullName, uint16_t rrtype,
153 uint16_t rrclass, uint16_t rdlen,
154 const void *rdata, uint32_t ttl,
155 void *context)
156 __attribute__((nonnull(1,5,9,11)));
157 #elif defined(HAVE_AVAHI)
158 static int poll_callback(struct pollfd *pollfds,
159 unsigned int num_pollfds, int timeout,
160 void *context);
161 static void query_callback(AvahiRecordBrowser *browser,
162 AvahiIfIndex interface,
163 AvahiProtocol protocol,
164 AvahiBrowserEvent event,
165 const char *name, uint16_t rrclass,
166 uint16_t rrtype, const void *rdata,
167 size_t rdlen,
168 AvahiLookupResultFlags flags,
169 void *context);
170 #endif /* HAVE_DNSSD */
171 static void sigterm_handler(int sig);
172 static void unquote(char *dst, const char *src, size_t dstsize)
173 __attribute__((nonnull(1,2)));
174
175
176 /*
177 * 'main()' - Browse for printers.
178 */
179
180 int /* O - Exit status */
181 main(int argc, /* I - Number of command-line args */
182 char *argv[]) /* I - Command-line arguments */
183 {
184 const char *name; /* Backend name */
185 cups_array_t *devices; /* Device array */
186 cups_device_t *device; /* Current device */
187 char uriName[1024]; /* Unquoted fullName for URI */
188 #ifdef HAVE_DNSSD
189 int fd; /* Main file descriptor */
190 fd_set input; /* Input set for select() */
191 struct timeval timeout; /* Timeout for select() */
192 DNSServiceRef main_ref, /* Main service reference */
193 fax_ipp_ref, /* IPP fax service reference */
194 ipp_ref, /* IPP service reference */
195 ipp_tls_ref, /* IPP w/TLS service reference */
196 ipps_ref, /* IPP service reference */
197 local_fax_ipp_ref, /* Local IPP fax service reference */
198 local_ipp_ref, /* Local IPP service reference */
199 local_ipp_tls_ref, /* Local IPP w/TLS service reference */
200 local_ipps_ref, /* Local IPP service reference */
201 local_printer_ref, /* Local LPD service reference */
202 pdl_datastream_ref, /* AppSocket service reference */
203 printer_ref, /* LPD service reference */
204 riousbprint_ref; /* Remote IO service reference */
205 #endif /* HAVE_DNSSD */
206 #ifdef HAVE_AVAHI
207 AvahiClient *client; /* Client information */
208 int error; /* Error code, if any */
209 #endif /* HAVE_AVAHI */
210 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
211 struct sigaction action; /* Actions for POSIX signals */
212 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
213
214
215 /*
216 * Don't buffer stderr, and catch SIGTERM...
217 */
218
219 setbuf(stderr, NULL);
220
221 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
222 sigset(SIGTERM, sigterm_handler);
223 #elif defined(HAVE_SIGACTION)
224 memset(&action, 0, sizeof(action));
225
226 sigemptyset(&action.sa_mask);
227 action.sa_handler = sigterm_handler;
228 sigaction(SIGTERM, &action, NULL);
229 #else
230 signal(SIGTERM, sigterm_handler);
231 #endif /* HAVE_SIGSET */
232
233 /*
234 * Check command-line...
235 */
236
237 if (argc >= 6)
238 exec_backend(argv);
239 else if (argc != 1)
240 {
241 _cupsLangPrintf(stderr,
242 _("Usage: %s job-id user title copies options [file]"),
243 argv[0]);
244 return (1);
245 }
246
247 /*
248 * Only do discovery when run as "dnssd"...
249 */
250
251 if ((name = strrchr(argv[0], '/')) != NULL)
252 name ++;
253 else
254 name = argv[0];
255
256 if (strcmp(name, "dnssd"))
257 return (0);
258
259 /*
260 * Create an array to track devices...
261 */
262
263 devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
264
265 /*
266 * Browse for different kinds of printers...
267 */
268
269 #ifdef HAVE_DNSSD
270 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
271 {
272 perror("ERROR: Unable to create service connection");
273 return (1);
274 }
275
276 fd = DNSServiceRefSockFD(main_ref);
277
278 fax_ipp_ref = main_ref;
279 DNSServiceBrowse(&fax_ipp_ref, kDNSServiceFlagsShareConnection, 0,
280 "_fax-ipp._tcp", NULL, browse_callback, devices);
281
282 ipp_ref = main_ref;
283 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
284 "_ipp._tcp", NULL, browse_callback, devices);
285
286 ipp_tls_ref = main_ref;
287 DNSServiceBrowse(&ipp_tls_ref, kDNSServiceFlagsShareConnection, 0,
288 "_ipp-tls._tcp", NULL, browse_callback, devices);
289
290 ipps_ref = main_ref;
291 DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0,
292 "_ipps._tcp", NULL, browse_callback, devices);
293
294 local_fax_ipp_ref = main_ref;
295 DNSServiceBrowse(&local_fax_ipp_ref, kDNSServiceFlagsShareConnection,
296 kDNSServiceInterfaceIndexLocalOnly,
297 "_fax-ipp._tcp", NULL, browse_local_callback, devices);
298
299 local_ipp_ref = main_ref;
300 DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
301 kDNSServiceInterfaceIndexLocalOnly,
302 "_ipp._tcp", NULL, browse_local_callback, devices);
303
304 local_ipp_tls_ref = main_ref;
305 DNSServiceBrowse(&local_ipp_tls_ref, kDNSServiceFlagsShareConnection,
306 kDNSServiceInterfaceIndexLocalOnly,
307 "_ipp-tls._tcp", NULL, browse_local_callback, devices);
308
309 local_ipps_ref = main_ref;
310 DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection,
311 kDNSServiceInterfaceIndexLocalOnly,
312 "_ipps._tcp", NULL, browse_local_callback, devices);
313
314 local_printer_ref = main_ref;
315 DNSServiceBrowse(&local_printer_ref, kDNSServiceFlagsShareConnection,
316 kDNSServiceInterfaceIndexLocalOnly,
317 "_printer._tcp", NULL, browse_local_callback, devices);
318
319 pdl_datastream_ref = main_ref;
320 DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
321 "_pdl-datastream._tcp", NULL, browse_callback, devices);
322
323 printer_ref = main_ref;
324 DNSServiceBrowse(&printer_ref, kDNSServiceFlagsShareConnection, 0,
325 "_printer._tcp", NULL, browse_callback, devices);
326
327 riousbprint_ref = main_ref;
328 DNSServiceBrowse(&riousbprint_ref, kDNSServiceFlagsShareConnection, 0,
329 "_riousbprint._tcp", NULL, browse_callback, devices);
330 #endif /* HAVE_DNSSD */
331
332 #ifdef HAVE_AVAHI
333 if ((simple_poll = avahi_simple_poll_new()) == NULL)
334 {
335 fputs("DEBUG: Unable to create Avahi simple poll object.\n", stderr);
336 return (1);
337 }
338
339 avahi_simple_poll_set_func(simple_poll, poll_callback, NULL);
340
341 client = avahi_client_new(avahi_simple_poll_get(simple_poll),
342 0, client_callback, simple_poll, &error);
343 if (!client)
344 {
345 fputs("DEBUG: Unable to create Avahi client.\n", stderr);
346 return (1);
347 }
348
349 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
350 AVAHI_PROTO_UNSPEC,
351 "_fax-ipp._tcp", NULL, 0,
352 browse_callback, devices);
353 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
354 AVAHI_PROTO_UNSPEC,
355 "_ipp._tcp", NULL, 0,
356 browse_callback, devices);
357 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
358 AVAHI_PROTO_UNSPEC,
359 "_ipp-tls._tcp", NULL, 0,
360 browse_callback, devices);
361 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
362 AVAHI_PROTO_UNSPEC,
363 "_ipps._tcp", NULL, 0,
364 browse_callback, devices);
365 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
366 AVAHI_PROTO_UNSPEC,
367 "_pdl-datastream._tcp",
368 NULL, 0,
369 browse_callback,
370 devices);
371 avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
372 AVAHI_PROTO_UNSPEC,
373 "_printer._tcp", NULL, 0,
374 browse_callback, devices);
375 #endif /* HAVE_AVAHI */
376
377 /*
378 * Loop until we are killed...
379 */
380
381 while (!job_canceled)
382 {
383 int announce = 0; /* Announce printers? */
384
385 #ifdef HAVE_DNSSD
386 FD_ZERO(&input);
387 FD_SET(fd, &input);
388
389 timeout.tv_sec = 0;
390 timeout.tv_usec = 500000;
391
392 if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
393 continue;
394
395 if (FD_ISSET(fd, &input))
396 {
397 /*
398 * Process results of our browsing...
399 */
400
401 DNSServiceProcessResult(main_ref);
402 }
403 else
404 announce = 1;
405
406 #elif defined(HAVE_AVAHI)
407 got_data = 0;
408
409 if ((error = avahi_simple_poll_iterate(simple_poll, 500)) > 0)
410 {
411 /*
412 * We've been told to exit the loop. Perhaps the connection to
413 * Avahi failed.
414 */
415
416 break;
417 }
418
419 if (!got_data)
420 announce = 1;
421 #endif /* HAVE_DNSSD */
422
423 /* fprintf(stderr, "DEBUG: announce=%d\n", announce);*/
424
425 if (announce)
426 {
427 /*
428 * Announce any devices we've found...
429 */
430
431 #ifdef HAVE_DNSSD
432 DNSServiceErrorType status; /* DNS query status */
433 #endif /* HAVE_DNSSD */
434 cups_device_t *best; /* Best matching device */
435 char device_uri[1024]; /* Device URI */
436 int count; /* Number of queries */
437 int sent; /* Number of sent */
438
439 for (device = (cups_device_t *)cupsArrayFirst(devices),
440 best = NULL, count = 0, sent = 0;
441 device;
442 device = (cups_device_t *)cupsArrayNext(devices))
443 {
444 if (device->sent)
445 sent ++;
446
447 if (device->ref)
448 count ++;
449
450 if (!device->ref && !device->sent)
451 {
452 /*
453 * Found the device, now get the TXT record(s) for it...
454 */
455
456 if (count < 50)
457 {
458 fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
459
460 #ifdef HAVE_DNSSD
461 device->ref = main_ref;
462
463 status = DNSServiceQueryRecord(&(device->ref),
464 kDNSServiceFlagsShareConnection,
465 0, device->fullName,
466 kDNSServiceType_TXT,
467 kDNSServiceClass_IN, query_callback,
468 device);
469 if (status != kDNSServiceErr_NoError)
470 fprintf(stderr,
471 "ERROR: Unable to query \"%s\" for TXT records: %d\n",
472 device->fullName, status);
473 /* Users never see this */
474 else
475 count ++;
476
477 #else
478 if ((device->ref = avahi_record_browser_new(client, AVAHI_IF_UNSPEC,
479 AVAHI_PROTO_UNSPEC,
480 device->fullName,
481 AVAHI_DNS_CLASS_IN,
482 AVAHI_DNS_TYPE_TXT,
483 0,
484 query_callback,
485 device)) == NULL)
486 fprintf(stderr,
487 "ERROR: Unable to query \"%s\" for TXT records: %s\n",
488 device->fullName,
489 avahi_strerror(avahi_client_errno(client)));
490 /* Users never see this */
491 else
492 count ++;
493 #endif /* HAVE_AVAHI */
494 }
495 }
496 else if (!device->sent)
497 {
498 #ifdef HAVE_DNSSD
499 /*
500 * Got the TXT records, now report the device...
501 */
502
503 DNSServiceRefDeallocate(device->ref);
504 #else
505 avahi_record_browser_free(device->ref);
506 #endif /* HAVE_DNSSD */
507
508 device->ref = NULL;
509
510 if (!best)
511 best = device;
512 else if (_cups_strcasecmp(best->name, device->name) ||
513 _cups_strcasecmp(best->domain, device->domain))
514 {
515 unquote(uriName, best->fullName, sizeof(uriName));
516
517 if (best->uuid)
518 httpAssembleURIf(HTTP_URI_CODING_ALL, device_uri,
519 sizeof(device_uri), "dnssd", NULL, uriName, 0,
520 best->cups_shared ? "/cups?uuid=%s" : "/?uuid=%s",
521 best->uuid);
522 else
523 httpAssembleURI(HTTP_URI_CODING_ALL, device_uri,
524 sizeof(device_uri), "dnssd", NULL, uriName, 0,
525 best->cups_shared ? "/cups" : "/");
526
527 cupsBackendReport("network", device_uri, best->make_and_model,
528 best->name, best->device_id, NULL);
529 best->sent = 1;
530 best = device;
531
532 sent ++;
533 }
534 else if (best->priority > device->priority ||
535 (best->priority == device->priority &&
536 best->type < device->type))
537 {
538 best->sent = 1;
539 best = device;
540
541 sent ++;
542 }
543 else
544 {
545 device->sent = 1;
546
547 sent ++;
548 }
549 }
550 }
551
552 if (best)
553 {
554 unquote(uriName, best->fullName, sizeof(uriName));
555
556 if (best->uuid)
557 httpAssembleURIf(HTTP_URI_CODING_ALL, device_uri,
558 sizeof(device_uri), "dnssd", NULL, uriName, 0,
559 best->cups_shared ? "/cups?uuid=%s" : "/?uuid=%s",
560 best->uuid);
561 else
562 httpAssembleURI(HTTP_URI_CODING_ALL, device_uri,
563 sizeof(device_uri), "dnssd", NULL, uriName, 0,
564 best->cups_shared ? "/cups" : "/");
565
566 cupsBackendReport("network", device_uri, best->make_and_model,
567 best->name, best->device_id, NULL);
568 best->sent = 1;
569 sent ++;
570 }
571
572 fprintf(stderr, "DEBUG: sent=%d, count=%d\n", sent, count);
573
574 if (sent == cupsArrayCount(devices))
575 break;
576 }
577 }
578
579 return (CUPS_BACKEND_OK);
580 }
581
582
583 #ifdef HAVE_DNSSD
584 /*
585 * 'browse_callback()' - Browse devices.
586 */
587
588 static void
589 browse_callback(
590 DNSServiceRef sdRef, /* I - Service reference */
591 DNSServiceFlags flags, /* I - Option flags */
592 uint32_t interfaceIndex, /* I - Interface number */
593 DNSServiceErrorType errorCode, /* I - Error, if any */
594 const char *serviceName, /* I - Name of service/device */
595 const char *regtype, /* I - Type of service */
596 const char *replyDomain, /* I - Service domain */
597 void *context) /* I - Devices array */
598 {
599 fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
600 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
601 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
602 sdRef, flags, interfaceIndex, errorCode,
603 serviceName, regtype, replyDomain, context);
604
605 /*
606 * Only process "add" data...
607 */
608
609 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
610 return;
611
612 /*
613 * Get the device...
614 */
615
616 get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
617 }
618
619
620 /*
621 * 'browse_local_callback()' - Browse local devices.
622 */
623
624 static void
625 browse_local_callback(
626 DNSServiceRef sdRef, /* I - Service reference */
627 DNSServiceFlags flags, /* I - Option flags */
628 uint32_t interfaceIndex, /* I - Interface number */
629 DNSServiceErrorType errorCode, /* I - Error, if any */
630 const char *serviceName, /* I - Name of service/device */
631 const char *regtype, /* I - Type of service */
632 const char *replyDomain, /* I - Service domain */
633 void *context) /* I - Devices array */
634 {
635 cups_device_t *device; /* Device */
636
637
638 fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
639 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
640 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
641 sdRef, flags, interfaceIndex, errorCode,
642 serviceName, regtype, replyDomain, context);
643
644 /*
645 * Only process "add" data...
646 */
647
648 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
649 return;
650
651 /*
652 * Get the device...
653 */
654
655 device = get_device((cups_array_t *)context, serviceName, regtype,
656 replyDomain);
657
658 /*
659 * Hide locally-registered devices...
660 */
661
662 fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
663 device->fullName);
664 device->sent = 1;
665 }
666 #endif /* HAVE_DNSSD */
667
668
669 #ifdef HAVE_AVAHI
670 /*
671 * 'browse_callback()' - Browse devices.
672 */
673
674 static void
675 browse_callback(
676 AvahiServiceBrowser *browser, /* I - Browser */
677 AvahiIfIndex interface, /* I - Interface index (unused) */
678 AvahiProtocol protocol, /* I - Network protocol (unused) */
679 AvahiBrowserEvent event, /* I - What happened */
680 const char *name, /* I - Service name */
681 const char *type, /* I - Registration type */
682 const char *domain, /* I - Domain */
683 AvahiLookupResultFlags flags, /* I - Flags */
684 void *context) /* I - Devices array */
685 {
686 AvahiClient *client = avahi_service_browser_get_client(browser);
687 /* Client information */
688
689
690 (void)interface;
691 (void)protocol;
692 (void)context;
693
694 switch (event)
695 {
696 case AVAHI_BROWSER_FAILURE:
697 fprintf(stderr, "DEBUG: browse_callback: %s\n",
698 avahi_strerror(avahi_client_errno(client)));
699 avahi_simple_poll_quit(simple_poll);
700 break;
701
702 case AVAHI_BROWSER_NEW:
703 /*
704 * This object is new on the network.
705 */
706
707 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
708 {
709 /*
710 * This comes from the local machine so ignore it.
711 */
712
713 fprintf(stderr, "DEBUG: Ignoring local service %s.\n", name);
714 }
715 else
716 {
717 /*
718 * Create a device entry for it if it doesn't yet exist.
719 */
720
721 get_device((cups_array_t *)context, name, type, domain);
722 }
723 break;
724
725 case AVAHI_BROWSER_REMOVE:
726 case AVAHI_BROWSER_ALL_FOR_NOW:
727 case AVAHI_BROWSER_CACHE_EXHAUSTED:
728 break;
729 }
730 }
731
732
733 /*
734 * 'client_callback()' - Avahi client callback function.
735 */
736
737 static void
738 client_callback(
739 AvahiClient *client, /* I - Client information (unused) */
740 AvahiClientState state, /* I - Current state */
741 void *context) /* I - User data (unused) */
742 {
743 (void)client;
744 (void)context;
745
746 /*
747 * If the connection drops, quit.
748 */
749
750 if (state == AVAHI_CLIENT_FAILURE)
751 {
752 fputs("DEBUG: Avahi connection failed.\n", stderr);
753 avahi_simple_poll_quit(simple_poll);
754 }
755 }
756 #endif /* HAVE_AVAHI */
757
758
759 /*
760 * 'compare_devices()' - Compare two devices.
761 */
762
763 static int /* O - Result of comparison */
764 compare_devices(cups_device_t *a, /* I - First device */
765 cups_device_t *b) /* I - Second device */
766 {
767 return (strcmp(a->name, b->name));
768 }
769
770
771 /*
772 * 'exec_backend()' - Execute the backend that corresponds to the
773 * resolved service name.
774 */
775
776 static void
777 exec_backend(char **argv) /* I - Command-line arguments */
778 {
779 const char *resolved_uri, /* Resolved device URI */
780 *cups_serverbin; /* Location of programs */
781 char scheme[1024], /* Scheme from URI */
782 *ptr, /* Pointer into scheme */
783 filename[1024]; /* Backend filename */
784
785
786 /*
787 * Resolve the device URI...
788 */
789
790 job_canceled = -1;
791
792 while ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
793 {
794 _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer."));
795 sleep(10);
796
797 if (getenv("CLASS") != NULL)
798 exit(CUPS_BACKEND_FAILED);
799 }
800
801 /*
802 * Extract the scheme from the URI...
803 */
804
805 strlcpy(scheme, resolved_uri, sizeof(scheme));
806 if ((ptr = strchr(scheme, ':')) != NULL)
807 *ptr = '\0';
808
809 /*
810 * Get the filename of the backend...
811 */
812
813 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
814 cups_serverbin = CUPS_SERVERBIN;
815
816 snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
817
818 /*
819 * Overwrite the device URI and run the new backend...
820 */
821
822 setenv("DEVICE_URI", resolved_uri, 1);
823
824 argv[0] = (char *)resolved_uri;
825
826 fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
827
828 execv(filename, argv);
829
830 fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
831 strerror(errno));
832 exit(CUPS_BACKEND_STOP);
833 }
834
835
836 /*
837 * 'device_type()' - Get DNS-SD type enumeration from string.
838 */
839
840 static int
841 device_type(const char *regtype)
842 {
843 #ifdef HAVE_AVAHI
844 if (!strcmp(regtype, "_ipp._tcp"))
845 return (CUPS_DEVICE_IPP);
846 else if (!strcmp(regtype, "_ipps._tcp") ||
847 !strcmp(regtype, "_ipp-tls._tcp"))
848 return (CUPS_DEVICE_IPPS);
849 else if (!strcmp(regtype, "_fax-ipp._tcp"))
850 return (CUPS_DEVICE_FAX_IPP);
851 else if (!strcmp(regtype, "_printer._tcp"))
852 return (CUPS_DEVICE_PDL_DATASTREAM);
853 #else
854 if (!strcmp(regtype, "_ipp._tcp."))
855 return (CUPS_DEVICE_IPP);
856 else if (!strcmp(regtype, "_ipps._tcp.") ||
857 !strcmp(regtype, "_ipp-tls._tcp."))
858 return (CUPS_DEVICE_IPPS);
859 else if (!strcmp(regtype, "_fax-ipp._tcp."))
860 return (CUPS_DEVICE_FAX_IPP);
861 else if (!strcmp(regtype, "_printer._tcp."))
862 return (CUPS_DEVICE_PRINTER);
863 else if (!strcmp(regtype, "_pdl-datastream._tcp."))
864 return (CUPS_DEVICE_PDL_DATASTREAM);
865 #endif /* HAVE_AVAHI */
866
867 return (CUPS_DEVICE_RIOUSBPRINT);
868 }
869
870
871 /*
872 * 'get_device()' - Create or update a device.
873 */
874
875 static cups_device_t * /* O - Device */
876 get_device(cups_array_t *devices, /* I - Device array */
877 const char *serviceName, /* I - Name of service/device */
878 const char *regtype, /* I - Type of service */
879 const char *replyDomain) /* I - Service domain */
880 {
881 cups_device_t key, /* Search key */
882 *device; /* Device */
883 char fullName[kDNSServiceMaxDomainName];
884 /* Full name for query */
885
886
887 /*
888 * See if this is a new device...
889 */
890
891 key.name = (char *)serviceName;
892 key.type = device_type(regtype);
893
894 for (device = cupsArrayFind(devices, &key);
895 device;
896 device = cupsArrayNext(devices))
897 if (_cups_strcasecmp(device->name, key.name))
898 break;
899 else if (device->type == key.type)
900 {
901 if (!_cups_strcasecmp(device->domain, "local.") &&
902 _cups_strcasecmp(device->domain, replyDomain))
903 {
904 /*
905 * Update the .local listing to use the "global" domain name instead.
906 * The backend will try local lookups first, then the global domain name.
907 */
908
909 free(device->domain);
910 device->domain = strdup(replyDomain);
911
912 #ifdef HAVE_DNSSD
913 DNSServiceConstructFullName(fullName, device->name, regtype,
914 replyDomain);
915 #else /* HAVE_AVAHI */
916 avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
917 serviceName, regtype, replyDomain);
918 #endif /* HAVE_DNSSD */
919
920 free(device->fullName);
921 device->fullName = strdup(fullName);
922 }
923
924 return (device);
925 }
926
927 /*
928 * Yes, add the device...
929 */
930
931 fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
932 replyDomain);
933
934 device = calloc(sizeof(cups_device_t), 1);
935 device->name = strdup(serviceName);
936 device->domain = strdup(replyDomain);
937 device->type = key.type;
938 device->priority = 50;
939
940 cupsArrayAdd(devices, device);
941
942 /*
943 * Set the "full name" of this service, which is used for queries...
944 */
945
946 #ifdef HAVE_DNSSD
947 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
948 #else /* HAVE_AVAHI */
949 avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
950 serviceName, regtype, replyDomain);
951 #endif /* HAVE_DNSSD */
952
953 device->fullName = strdup(fullName);
954
955 return (device);
956 }
957
958
959 #ifdef HAVE_AVAHI
960 /*
961 * 'poll_callback()' - Wait for input on the specified file descriptors.
962 *
963 * Note: This function is needed because avahi_simple_poll_iterate is broken
964 * and always uses a timeout of 0 (!) milliseconds.
965 * (Avahi Ticket #364)
966 */
967
968 static int /* O - Number of file descriptors matching */
969 poll_callback(
970 struct pollfd *pollfds, /* I - File descriptors */
971 unsigned int num_pollfds, /* I - Number of file descriptors */
972 int timeout, /* I - Timeout in milliseconds (unused) */
973 void *context) /* I - User data (unused) */
974 {
975 int val; /* Return value */
976
977
978 (void)timeout;
979 (void)context;
980
981 val = poll(pollfds, num_pollfds, 500);
982
983 if (val < 0)
984 fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno));
985 else if (val > 0)
986 got_data = 1;
987
988 return (val);
989 }
990 #endif /* HAVE_AVAHI */
991
992
993 #if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
994 # ifdef HAVE_DNSSD
995 /*
996 * 'query_callback()' - Process query data.
997 */
998
999 static void
1000 query_callback(
1001 DNSServiceRef sdRef, /* I - Service reference */
1002 DNSServiceFlags flags, /* I - Data flags */
1003 uint32_t interfaceIndex, /* I - Interface */
1004 DNSServiceErrorType errorCode, /* I - Error, if any */
1005 const char *fullName, /* I - Full service name */
1006 uint16_t rrtype, /* I - Record type */
1007 uint16_t rrclass, /* I - Record class */
1008 uint16_t rdlen, /* I - Length of record data */
1009 const void *rdata, /* I - Record data */
1010 uint32_t ttl, /* I - Time-to-live */
1011 void *context) /* I - Device */
1012 {
1013 # else
1014 /*
1015 * 'query_callback()' - Process query data.
1016 */
1017
1018 static void
1019 query_callback(
1020 AvahiRecordBrowser *browser, /* I - Record browser */
1021 AvahiIfIndex interfaceIndex,
1022 /* I - Interface index (unused) */
1023 AvahiProtocol protocol, /* I - Network protocol (unused) */
1024 AvahiBrowserEvent event, /* I - What happened? */
1025 const char *fullName, /* I - Service name */
1026 uint16_t rrclass, /* I - Record class */
1027 uint16_t rrtype, /* I - Record type */
1028 const void *rdata, /* I - TXT record */
1029 size_t rdlen, /* I - Length of TXT record */
1030 AvahiLookupResultFlags flags, /* I - Flags */
1031 void *context) /* I - Device */
1032 {
1033 AvahiClient *client = avahi_record_browser_get_client(browser);
1034 /* Client information */
1035 # endif /* HAVE_DNSSD */
1036 char *ptr; /* Pointer into string */
1037 cups_device_t *device = (cups_device_t *)context;
1038 /* Device */
1039 const uint8_t *data, /* Pointer into data */
1040 *datanext, /* Next key/value pair */
1041 *dataend; /* End of entire TXT record */
1042 uint8_t datalen; /* Length of current key/value pair */
1043 char key[256], /* Key string */
1044 value[256], /* Value string */
1045 make_and_model[512], /* Manufacturer and model */
1046 model[256], /* Model */
1047 pdl[256], /* PDL */
1048 device_id[2048]; /* 1284 device ID */
1049
1050
1051 # ifdef HAVE_DNSSD
1052 fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
1053 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
1054 "rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
1055 "context=%p)\n",
1056 sdRef, flags, interfaceIndex, errorCode,
1057 fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
1058 context);
1059
1060 /*
1061 * Only process "add" data...
1062 */
1063
1064 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
1065 return;
1066
1067 # else
1068 fprintf(stderr, "DEBUG2: query_callback(browser=%p, interfaceIndex=%d, "
1069 "protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, "
1070 "rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)\n",
1071 browser, interfaceIndex, protocol, event,
1072 fullName ? fullName : "(null)", rrclass, rrtype, rdata,
1073 (unsigned)rdlen, flags, context);
1074
1075 /*
1076 * Only process "add" data...
1077 */
1078
1079 if (event != AVAHI_BROWSER_NEW)
1080 {
1081 if (event == AVAHI_BROWSER_FAILURE)
1082 fprintf(stderr, "ERROR: %s\n",
1083 avahi_strerror(avahi_client_errno(client)));
1084
1085 return;
1086 }
1087 # endif /* HAVE_DNSSD */
1088
1089 /*
1090 * Pull out the priority and make and model from the TXT
1091 * record and save it...
1092 */
1093
1094 device_id[0] = '\0';
1095 make_and_model[0] = '\0';
1096 pdl[0] = '\0';
1097
1098 strlcpy(model, "Unknown", sizeof(model));
1099
1100 for (data = rdata, dataend = data + rdlen;
1101 data < dataend;
1102 data = datanext)
1103 {
1104 /*
1105 * Read a key/value pair starting with an 8-bit length. Since the
1106 * length is 8 bits and the size of the key/value buffers is 256, we
1107 * don't need to check for overflow...
1108 */
1109
1110 datalen = *data++;
1111
1112 if (!datalen || (data + datalen) > dataend)
1113 break;
1114
1115 datanext = data + datalen;
1116
1117 for (ptr = key; data < datanext && *data != '='; data ++)
1118 *ptr++ = *data;
1119 *ptr = '\0';
1120
1121 if (data < datanext && *data == '=')
1122 {
1123 data ++;
1124
1125 if (data < datanext)
1126 memcpy(value, data, datanext - data);
1127 value[datanext - data] = '\0';
1128
1129 fprintf(stderr, "DEBUG2: query_callback: \"%s=%s\".\n",
1130 key, value);
1131 }
1132 else
1133 {
1134 fprintf(stderr, "DEBUG2: query_callback: \"%s\" with no value.\n",
1135 key);
1136 continue;
1137 }
1138
1139 if (!_cups_strncasecmp(key, "usb_", 4))
1140 {
1141 /*
1142 * Add USB device ID information...
1143 */
1144
1145 ptr = device_id + strlen(device_id);
1146 snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s:%s;",
1147 key + 4, value);
1148 }
1149
1150 if (!_cups_strcasecmp(key, "usb_MFG") || !_cups_strcasecmp(key, "usb_MANU") ||
1151 !_cups_strcasecmp(key, "usb_MANUFACTURER"))
1152 strlcpy(make_and_model, value, sizeof(make_and_model));
1153 else if (!_cups_strcasecmp(key, "usb_MDL") || !_cups_strcasecmp(key, "usb_MODEL"))
1154 strlcpy(model, value, sizeof(model));
1155 else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
1156 {
1157 if (value[0] == '(')
1158 {
1159 /*
1160 * Strip parenthesis...
1161 */
1162
1163 if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
1164 *ptr = '\0';
1165
1166 strlcpy(model, value + 1, sizeof(model));
1167 }
1168 else
1169 strlcpy(model, value, sizeof(model));
1170 }
1171 else if (!_cups_strcasecmp(key, "ty"))
1172 {
1173 strlcpy(model, value, sizeof(model));
1174
1175 if ((ptr = strchr(model, ',')) != NULL)
1176 *ptr = '\0';
1177 }
1178 else if (!_cups_strcasecmp(key, "pdl"))
1179 strlcpy(pdl, value, sizeof(pdl));
1180 else if (!_cups_strcasecmp(key, "priority"))
1181 device->priority = atoi(value);
1182 else if ((device->type == CUPS_DEVICE_IPP ||
1183 device->type == CUPS_DEVICE_IPPS ||
1184 device->type == CUPS_DEVICE_PRINTER) &&
1185 !_cups_strcasecmp(key, "printer-type"))
1186 {
1187 /*
1188 * This is a CUPS printer!
1189 */
1190
1191 device->cups_shared = 1;
1192
1193 if (device->type == CUPS_DEVICE_PRINTER)
1194 device->sent = 1;
1195 }
1196 else if (!_cups_strcasecmp(key, "UUID"))
1197 device->uuid = strdup(value);
1198 }
1199
1200 if (device->device_id)
1201 free(device->device_id);
1202
1203 if (!device_id[0] && strcmp(model, "Unknown"))
1204 {
1205 if (make_and_model[0])
1206 snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;",
1207 make_and_model, model);
1208 else if (!_cups_strncasecmp(model, "designjet ", 10))
1209 snprintf(device_id, sizeof(device_id), "MFG:HP;MDL:%s", model + 10);
1210 else if (!_cups_strncasecmp(model, "stylus ", 7))
1211 snprintf(device_id, sizeof(device_id), "MFG:EPSON;MDL:%s", model + 7);
1212 else if ((ptr = strchr(model, ' ')) != NULL)
1213 {
1214 /*
1215 * Assume the first word is the make...
1216 */
1217
1218 memcpy(make_and_model, model, ptr - model);
1219 make_and_model[ptr - model] = '\0';
1220
1221 snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s",
1222 make_and_model, ptr + 1);
1223 }
1224 }
1225
1226 if (device_id[0] &&
1227 !strstr(device_id, "CMD:") &&
1228 !strstr(device_id, "COMMAND SET:") &&
1229 (strstr(pdl, "application/pdf") ||
1230 strstr(pdl, "application/postscript") ||
1231 strstr(pdl, "application/vnd.hp-PCL") ||
1232 strstr(pdl, "image/")))
1233 {
1234 value[0] = '\0';
1235 if (strstr(pdl, "application/pdf"))
1236 strlcat(value, ",PDF", sizeof(value));
1237 if (strstr(pdl, "application/postscript"))
1238 strlcat(value, ",PS", sizeof(value));
1239 if (strstr(pdl, "application/vnd.hp-PCL"))
1240 strlcat(value, ",PCL", sizeof(value));
1241 for (ptr = strstr(pdl, "image/"); ptr; ptr = strstr(ptr, "image/"))
1242 {
1243 char *valptr = value + strlen(value);
1244 /* Pointer into value */
1245
1246 if (valptr < (value + sizeof(value) - 1))
1247 *valptr++ = ',';
1248
1249 ptr += 6;
1250 while (isalnum(*ptr & 255) || *ptr == '-' || *ptr == '.')
1251 {
1252 if (isalnum(*ptr & 255) && valptr < (value + sizeof(value) - 1))
1253 *valptr++ = toupper(*ptr++ & 255);
1254 else
1255 break;
1256 }
1257
1258 *valptr = '\0';
1259 }
1260
1261 ptr = device_id + strlen(device_id);
1262 snprintf(ptr, sizeof(device_id) - (ptr - device_id), "CMD:%s;",
1263 value + 1);
1264 }
1265
1266 if (device_id[0])
1267 device->device_id = strdup(device_id);
1268 else
1269 device->device_id = NULL;
1270
1271 if (device->make_and_model)
1272 free(device->make_and_model);
1273
1274 if (make_and_model[0])
1275 {
1276 strlcat(make_and_model, " ", sizeof(make_and_model));
1277 strlcat(make_and_model, model, sizeof(make_and_model));
1278
1279 device->make_and_model = strdup(make_and_model);
1280 }
1281 else
1282 device->make_and_model = strdup(model);
1283 }
1284 #endif /* HAVE_DNSSD || HAVE_AVAHI */
1285
1286
1287 /*
1288 * 'sigterm_handler()' - Handle termination signals.
1289 */
1290
1291 static void
1292 sigterm_handler(int sig) /* I - Signal number (unused) */
1293 {
1294 (void)sig;
1295
1296 if (job_canceled)
1297 exit(CUPS_BACKEND_OK);
1298 else
1299 job_canceled = 1;
1300 }
1301
1302
1303 /*
1304 * 'unquote()' - Unquote a name string.
1305 */
1306
1307 static void
1308 unquote(char *dst, /* I - Destination buffer */
1309 const char *src, /* I - Source string */
1310 size_t dstsize) /* I - Size of destination buffer */
1311 {
1312 char *dstend = dst + dstsize - 1; /* End of destination buffer */
1313
1314
1315 while (*src && dst < dstend)
1316 {
1317 if (*src == '\\')
1318 {
1319 src ++;
1320 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
1321 isdigit(src[2] & 255))
1322 {
1323 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
1324 src += 3;
1325 }
1326 else
1327 *dst++ = *src++;
1328 }
1329 else
1330 *dst++ = *src ++;
1331 }
1332
1333 *dst = '\0';
1334 }
1335
1336
1337 /*
1338 * End of "$Id$".
1339 */