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