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