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