• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * User-defined destination (and option) support for CUPS.
3  *
4  * Copyright © 2020-2025 by OpenPrinting.
5  * Copyright © 2007-2019 by Apple Inc.
6  * Copyright © 1997-2007 by Easy Software Products.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
9  * information.
10  */
11 
12 #include "cups-private.h"
13 #include "debug-internal.h"
14 #include <sys/stat.h>
15 
16 #ifdef HAVE_NOTIFY_H
17 #  include <notify.h>
18 #endif /* HAVE_NOTIFY_H */
19 
20 #ifdef HAVE_POLL
21 #  include <poll.h>
22 #endif /* HAVE_POLL */
23 
24 #ifdef HAVE_MDNSRESPONDER
25 #  include <dns_sd.h>
26 #endif /* HAVE_MDNSRESPONDER */
27 
28 #ifdef HAVE_AVAHI
29 #  include <avahi-client/client.h>
30 #  include <avahi-client/lookup.h>
31 #  include <avahi-common/simple-watch.h>
32 #  include <avahi-common/domain.h>
33 #  include <avahi-common/error.h>
34 #  include <avahi-common/malloc.h>
35 #define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
36 #endif /* HAVE_AVAHI */
37 
38 
39 /*
40  * Constants...
41  */
42 
43 #ifdef __APPLE__
44 #  if HAVE_SCDYNAMICSTORECOPYCOMPUTERNAME
45 #    include <SystemConfiguration/SystemConfiguration.h>
46 #    define _CUPS_LOCATION_DEFAULTS 1
47 #  endif /* HAVE_SCDYNAMICSTORECOPYCOMPUTERNAME */
48 #  define kCUPSPrintingPrefs	CFSTR("org.cups.PrintingPrefs")
49 #  define kDefaultPaperIDKey	CFSTR("DefaultPaperID")
50 #  define kLastUsedPrintersKey	CFSTR("LastUsedPrinters")
51 #  define kLocationNetworkKey	CFSTR("Network")
52 #  define kLocationPrinterIDKey	CFSTR("PrinterID")
53 #  define kUseLastPrinter	CFSTR("UseLastPrinter")
54 #endif /* __APPLE__ */
55 
56 #ifdef HAVE_DNSSD
57 #  define _CUPS_DNSSD_GET_DESTS 1000     /* Milliseconds for cupsGetDests */
58 #  define _CUPS_DNSSD_MAXTIME	50	/* Milliseconds for maximum quantum of time */
59 #else
60 #  define _CUPS_DNSSD_GET_DESTS 0       /* Milliseconds for cupsGetDests */
61 #endif /* HAVE_DNSSD */
62 
63 
64 /*
65  * Types...
66  */
67 
68 #ifdef HAVE_DNSSD
69 typedef enum _cups_dnssd_state_e	/* Enumerated device state */
70 {
71   _CUPS_DNSSD_NEW,
72   _CUPS_DNSSD_QUERY,
73   _CUPS_DNSSD_PENDING,
74   _CUPS_DNSSD_ACTIVE,
75   _CUPS_DNSSD_INCOMPATIBLE,
76   _CUPS_DNSSD_ERROR
77 } _cups_dnssd_state_t;
78 
79 typedef struct _cups_dnssd_data_s	/* Enumeration data */
80 {
81 #  ifdef HAVE_MDNSRESPONDER
82   DNSServiceRef		main_ref;	/* Main service reference */
83 #  else /* HAVE_AVAHI */
84   AvahiSimplePoll	*simple_poll;	/* Polling interface */
85   AvahiClient		*client;	/* Client information */
86   int			got_data;	/* Did we get data? */
87   int			browsers;	/* How many browsers are running? */
88 #  endif /* HAVE_MDNSRESPONDER */
89   cups_dest_cb_t	cb;		/* Callback */
90   void			*user_data;	/* User data pointer */
91   cups_ptype_t		type,		/* Printer type filter */
92 			mask;		/* Printer type mask */
93   cups_array_t		*devices;	/* Devices found so far */
94   int			num_dests;	/* Number of lpoptions destinations */
95   cups_dest_t		*dests;		/* lpoptions destinations */
96   char			def_name[1024],	/* Default printer name, if any */
97 			*def_instance;	/* Default printer instance, if any */
98 } _cups_dnssd_data_t;
99 
100 typedef struct _cups_dnssd_device_s	/* Enumerated device */
101 {
102   _cups_dnssd_state_t	state;		/* State of device listing */
103 #  ifdef HAVE_MDNSRESPONDER
104   DNSServiceRef		ref;		/* Service reference for query */
105 #  else /* HAVE_AVAHI */
106   AvahiRecordBrowser	*ref;		/* Browser for query */
107 #  endif /* HAVE_MDNSRESPONDER */
108   char			*fullName,	/* Full name */
109 			*regtype,	/* Registration type */
110 			*domain;	/* Domain name */
111   cups_ptype_t		type;		/* Device registration type */
112   cups_dest_t		dest;		/* Destination record */
113 } _cups_dnssd_device_t;
114 
115 typedef struct _cups_dnssd_resolve_s	/* Data for resolving URI */
116 {
117   int			*cancel;	/* Pointer to "cancel" variable */
118   double		end_time;	/* Ending time */
119 } _cups_dnssd_resolve_t;
120 #endif /* HAVE_DNSSD */
121 
122 typedef struct _cups_getdata_s
123 {
124   int		num_dests;		/* Number of destinations */
125   cups_dest_t	*dests;			/* Destinations */
126   char		def_name[1024],		/* Default printer name, if any */
127 		*def_instance;		/* Default printer instance, if any */
128 } _cups_getdata_t;
129 
130 typedef struct _cups_namedata_s
131 {
132   const char  *name;                    /* Named destination */
133   cups_dest_t *dest;                    /* Destination */
134 } _cups_namedata_t;
135 
136 
137 /*
138  * Local functions...
139  */
140 
141 #if _CUPS_LOCATION_DEFAULTS
142 static CFArrayRef	appleCopyLocations(void);
143 static CFStringRef	appleCopyNetwork(void);
144 #endif /* _CUPS_LOCATION_DEFAULTS */
145 #ifdef __APPLE__
146 static char		*appleGetPaperSize(char *name, size_t namesize);
147 #endif /* __APPLE__ */
148 #if _CUPS_LOCATION_DEFAULTS
149 static CFStringRef	appleGetPrinter(CFArrayRef locations,
150 			                CFStringRef network, CFIndex *locindex);
151 #endif /* _CUPS_LOCATION_DEFAULTS */
152 static cups_dest_t	*cups_add_dest(const char *name, const char *instance,
153 				       int *num_dests, cups_dest_t **dests);
154 #ifdef __BLOCKS__
155 static int		cups_block_cb(cups_dest_block_t block, unsigned flags,
156 			              cups_dest_t *dest);
157 #endif /* __BLOCKS__ */
158 static int		cups_compare_dests(cups_dest_t *a, cups_dest_t *b);
159 #ifdef HAVE_DNSSD
160 #  ifdef HAVE_MDNSRESPONDER
161 static void		cups_dnssd_browse_cb(DNSServiceRef sdRef,
162 					     DNSServiceFlags flags,
163 					     uint32_t interfaceIndex,
164 					     DNSServiceErrorType errorCode,
165 					     const char *serviceName,
166 					     const char *regtype,
167 					     const char *replyDomain,
168 					     void *context);
169 #  else /* HAVE_AVAHI */
170 static void		cups_dnssd_browse_cb(AvahiServiceBrowser *browser,
171 					     AvahiIfIndex interface,
172 					     AvahiProtocol protocol,
173 					     AvahiBrowserEvent event,
174 					     const char *serviceName,
175 					     const char *regtype,
176 					     const char *replyDomain,
177 					     AvahiLookupResultFlags flags,
178 					     void *context);
179 static void		cups_dnssd_client_cb(AvahiClient *client,
180 					     AvahiClientState state,
181 					     void *context);
182 #  endif /* HAVE_MDNSRESPONDER */
183 static int		cups_dnssd_compare_devices(_cups_dnssd_device_t *a,
184 			                           _cups_dnssd_device_t *b);
185 static void		cups_dnssd_free_device(_cups_dnssd_device_t *device,
186 			                       _cups_dnssd_data_t *data);
187 static _cups_dnssd_device_t *
188 			cups_dnssd_get_device(_cups_dnssd_data_t *data,
189 					      const char *serviceName,
190 					      const char *regtype,
191 					      const char *replyDomain);
192 #  ifdef HAVE_MDNSRESPONDER
193 static void		cups_dnssd_query_cb(DNSServiceRef sdRef,
194 					    DNSServiceFlags flags,
195 					    uint32_t interfaceIndex,
196 					    DNSServiceErrorType errorCode,
197 					    const char *fullName,
198 					    uint16_t rrtype, uint16_t rrclass,
199 					    uint16_t rdlen, const void *rdata,
200 					    uint32_t ttl, void *context);
201 #  else /* HAVE_AVAHI */
202 static int		cups_dnssd_poll_cb(struct pollfd *pollfds,
203 					   unsigned int num_pollfds,
204 					   int timeout, void *context);
205 static void		cups_dnssd_query_cb(AvahiRecordBrowser *browser,
206 					    AvahiIfIndex interface,
207 					    AvahiProtocol protocol,
208 					    AvahiBrowserEvent event,
209 					    const char *name, uint16_t rrclass,
210 					    uint16_t rrtype, const void *rdata,
211 					    size_t rdlen,
212 					    AvahiLookupResultFlags flags,
213 					    void *context);
214 #  endif /* HAVE_MDNSRESPONDER */
215 static const char	*cups_dnssd_resolve(cups_dest_t *dest, const char *uri,
216 					    int msec, int *cancel,
217 					    cups_dest_cb_t cb, void *user_data);
218 static int		cups_dnssd_resolve_cb(void *context);
219 static void		cups_dnssd_unquote(char *dst, const char *src,
220 			                   size_t dstsize);
221 static int		cups_elapsed(double *t);
222 #endif /* HAVE_DNSSD */
223 static int              cups_enum_dests(http_t *http, unsigned flags, int msec, int *cancel, cups_ptype_t type, cups_ptype_t mask, cups_dest_cb_t cb, void *user_data);
224 static int		cups_find_dest(const char *name, const char *instance,
225 				       int num_dests, cups_dest_t *dests, int prev,
226 				       int *rdiff);
227 static int              cups_get_cb(_cups_getdata_t *data, unsigned flags, cups_dest_t *dest);
228 static char		*cups_get_default(const char *filename, char *namebuf,
229 					  size_t namesize, const char **instance);
230 static int		cups_get_dests(const char *filename, const char *match_name, const char *match_inst, int load_all, int user_default_set, int num_dests, cups_dest_t **dests);
231 static char		*cups_make_string(ipp_attribute_t *attr, char *buffer,
232 			                  size_t bufsize);
233 static int              cups_name_cb(_cups_namedata_t *data, unsigned flags, cups_dest_t *dest);
234 static void		cups_queue_name(char *name, const char *serviceName, size_t namesize);
235 
236 
237 /*
238  * 'cupsAddDest()' - Add a destination to the list of destinations.
239  *
240  * This function cannot be used to add a new class or printer queue,
241  * it only adds a new container of saved options for the named
242  * destination or instance.
243  *
244  * If the named destination already exists, the destination list is
245  * returned unchanged.  Adding a new instance of a destination creates
246  * a copy of that destination's options.
247  *
248  * Use the @link cupsSaveDests@ function to save the updated list of
249  * destinations to the user's lpoptions file.
250  */
251 
252 int					/* O  - New number of destinations */
cupsAddDest(const char * name,const char * instance,int num_dests,cups_dest_t ** dests)253 cupsAddDest(const char  *name,		/* I  - Destination name */
254             const char	*instance,	/* I  - Instance name or @code NULL@ for none/primary */
255             int         num_dests,	/* I  - Number of destinations */
256             cups_dest_t **dests)	/* IO - Destinations */
257 {
258   int		i;			/* Looping var */
259   cups_dest_t	*dest;			/* Destination pointer */
260   cups_dest_t	*parent = NULL;		/* Parent destination */
261   cups_option_t	*doption,		/* Current destination option */
262 		*poption;		/* Current parent option */
263 
264 
265   if (!name || !dests)
266     return (0);
267 
268   if (!cupsGetDest(name, instance, num_dests, *dests))
269   {
270     if (instance && !cupsGetDest(name, NULL, num_dests, *dests))
271     {
272       // Add destination first...
273       if (!cups_add_dest(name, NULL, &num_dests, dests))
274         return (num_dests);
275     }
276 
277     if ((dest = cups_add_dest(name, instance, &num_dests, dests)) == NULL)
278       return (num_dests);
279 
280    /*
281     * Find the base dest again now the array has been realloc'd.
282     */
283 
284     parent = cupsGetDest(name, NULL, num_dests, *dests);
285 
286     if (instance && parent && parent->num_options > 0)
287     {
288      /*
289       * Copy options from parent...
290       */
291 
292       dest->options = calloc((size_t)parent->num_options, sizeof(cups_option_t));
293 
294       if (dest->options)
295       {
296         dest->num_options = parent->num_options;
297 
298 	for (i = dest->num_options, doption = dest->options,
299 	         poption = parent->options;
300 	     i > 0;
301 	     i --, doption ++, poption ++)
302 	{
303 	  doption->name  = _cupsStrRetain(poption->name);
304 	  doption->value = _cupsStrRetain(poption->value);
305 	}
306       }
307     }
308   }
309 
310   return (num_dests);
311 }
312 
313 
314 #ifdef __APPLE__
315 /*
316  * '_cupsAppleCopyDefaultPaperID()' - Get the default paper ID.
317  */
318 
319 CFStringRef				/* O - Default paper ID */
_cupsAppleCopyDefaultPaperID(void)320 _cupsAppleCopyDefaultPaperID(void)
321 {
322   return (CFPreferencesCopyAppValue(kDefaultPaperIDKey,
323                                     kCUPSPrintingPrefs));
324 }
325 
326 
327 /*
328  * '_cupsAppleCopyDefaultPrinter()' - Get the default printer at this location.
329  */
330 
331 CFStringRef				/* O - Default printer name */
_cupsAppleCopyDefaultPrinter(void)332 _cupsAppleCopyDefaultPrinter(void)
333 {
334 #  if _CUPS_LOCATION_DEFAULTS
335   CFStringRef	network;		/* Network location */
336   CFArrayRef	locations;		/* Location array */
337   CFStringRef	locprinter;		/* Current printer */
338 
339 
340  /*
341   * Use location-based defaults only if "use last printer" is selected in the
342   * system preferences...
343   */
344 
345   if (!_cupsAppleGetUseLastPrinter())
346   {
347     DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Not using last printer as "
348 	       "default.");
349     return (NULL);
350   }
351 
352  /*
353   * Get the current location...
354   */
355 
356   if ((network = appleCopyNetwork()) == NULL)
357   {
358     DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Unable to get current "
359                "network.");
360     return (NULL);
361   }
362 
363  /*
364   * Lookup the network in the preferences...
365   */
366 
367   if ((locations = appleCopyLocations()) == NULL)
368   {
369    /*
370     * Missing or bad location array, so no location-based default...
371     */
372 
373     DEBUG_puts("1_cupsAppleCopyDefaultPrinter: Missing or bad last used "
374 	       "printer array.");
375 
376     CFRelease(network);
377 
378     return (NULL);
379   }
380 
381   DEBUG_printf(("1_cupsAppleCopyDefaultPrinter: Got locations, %d entries.",
382                 (int)CFArrayGetCount(locations)));
383 
384   if ((locprinter = appleGetPrinter(locations, network, NULL)) != NULL)
385     CFRetain(locprinter);
386 
387   CFRelease(network);
388   CFRelease(locations);
389 
390   return (locprinter);
391 
392 #  else
393   return (NULL);
394 #  endif /* _CUPS_LOCATION_DEFAULTS */
395 }
396 
397 
398 /*
399  * '_cupsAppleGetUseLastPrinter()' - Get whether to use the last used printer.
400  */
401 
402 int					/* O - 1 to use last printer, 0 otherwise */
_cupsAppleGetUseLastPrinter(void)403 _cupsAppleGetUseLastPrinter(void)
404 {
405   Boolean	uselast,		/* Use last printer preference value */
406 		uselast_set;		/* Valid is set? */
407 
408 
409   if (getenv("CUPS_DISABLE_APPLE_DEFAULT"))
410     return (0);
411 
412   uselast = CFPreferencesGetAppBooleanValue(kUseLastPrinter,
413                                             kCUPSPrintingPrefs,
414 					    &uselast_set);
415   if (!uselast_set)
416     return (1);
417   else
418     return (uselast);
419 }
420 
421 
422 /*
423  * '_cupsAppleSetDefaultPaperID()' - Set the default paper id.
424  */
425 
426 void
_cupsAppleSetDefaultPaperID(CFStringRef name)427 _cupsAppleSetDefaultPaperID(
428     CFStringRef name)			/* I - New paper ID */
429 {
430   CFPreferencesSetAppValue(kDefaultPaperIDKey, name, kCUPSPrintingPrefs);
431   CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
432 
433 #  ifdef HAVE_NOTIFY_POST
434   notify_post("com.apple.printerPrefsChange");
435 #  endif /* HAVE_NOTIFY_POST */
436 }
437 
438 
439 /*
440  * '_cupsAppleSetDefaultPrinter()' - Set the default printer for this location.
441  */
442 
443 void
_cupsAppleSetDefaultPrinter(CFStringRef name)444 _cupsAppleSetDefaultPrinter(
445     CFStringRef name)			/* I - Default printer/class name */
446 {
447 #  if _CUPS_LOCATION_DEFAULTS
448   CFStringRef		network;	/* Current network */
449   CFArrayRef		locations;	/* Old locations array */
450   CFIndex		locindex;	/* Index in locations array */
451   CFStringRef		locprinter;	/* Current printer */
452   CFMutableArrayRef	newlocations;	/* New locations array */
453   CFMutableDictionaryRef newlocation;	/* New location */
454 
455 
456  /*
457   * Get the current location...
458   */
459 
460   if ((network = appleCopyNetwork()) == NULL)
461   {
462     DEBUG_puts("1_cupsAppleSetDefaultPrinter: Unable to get current network...");
463     return;
464   }
465 
466  /*
467   * Lookup the network in the preferences...
468   */
469 
470   if ((locations = appleCopyLocations()) != NULL)
471     locprinter = appleGetPrinter(locations, network, &locindex);
472   else
473   {
474     locprinter = NULL;
475     locindex   = -1;
476   }
477 
478   if (!locprinter || CFStringCompare(locprinter, name, 0) != kCFCompareEqualTo)
479   {
480    /*
481     * Need to change the locations array...
482     */
483 
484     if (locations)
485     {
486       newlocations = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0,
487                                               locations);
488 
489       if (locprinter)
490         CFArrayRemoveValueAtIndex(newlocations, locindex);
491     }
492     else
493       newlocations = CFArrayCreateMutable(kCFAllocatorDefault, 0,
494 					  &kCFTypeArrayCallBacks);
495 
496     newlocation = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
497 					    &kCFTypeDictionaryKeyCallBacks,
498 					    &kCFTypeDictionaryValueCallBacks);
499 
500     if (newlocation && newlocations)
501     {
502      /*
503       * Put the new location at the front of the array...
504       */
505 
506       CFDictionaryAddValue(newlocation, kLocationNetworkKey, network);
507       CFDictionaryAddValue(newlocation, kLocationPrinterIDKey, name);
508       CFArrayInsertValueAtIndex(newlocations, 0, newlocation);
509 
510      /*
511       * Limit the number of locations to 10...
512       */
513 
514       while (CFArrayGetCount(newlocations) > 10)
515         CFArrayRemoveValueAtIndex(newlocations, 10);
516 
517      /*
518       * Push the changes out...
519       */
520 
521       CFPreferencesSetAppValue(kLastUsedPrintersKey, newlocations,
522                                kCUPSPrintingPrefs);
523       CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
524 
525 #  ifdef HAVE_NOTIFY_POST
526       notify_post("com.apple.printerPrefsChange");
527 #  endif /* HAVE_NOTIFY_POST */
528     }
529 
530     if (newlocations)
531       CFRelease(newlocations);
532 
533     if (newlocation)
534       CFRelease(newlocation);
535   }
536 
537   if (locations)
538     CFRelease(locations);
539 
540   CFRelease(network);
541 
542 #  else
543   (void)name;
544 #  endif /* _CUPS_LOCATION_DEFAULTS */
545 }
546 
547 
548 /*
549  * '_cupsAppleSetUseLastPrinter()' - Set whether to use the last used printer.
550  */
551 
552 void
_cupsAppleSetUseLastPrinter(int uselast)553 _cupsAppleSetUseLastPrinter(
554     int uselast)			/* O - 1 to use last printer, 0 otherwise */
555 {
556   CFPreferencesSetAppValue(kUseLastPrinter,
557 			   uselast ? kCFBooleanTrue : kCFBooleanFalse,
558 			   kCUPSPrintingPrefs);
559   CFPreferencesAppSynchronize(kCUPSPrintingPrefs);
560 
561 #  ifdef HAVE_NOTIFY_POST
562   notify_post("com.apple.printerPrefsChange");
563 #  endif /* HAVE_NOTIFY_POST */
564 }
565 #endif /* __APPLE__ */
566 
567 
568 /*
569  * 'cupsConnectDest()' - Open a connection to the destination.
570  *
571  * Connect to the destination, returning a new @code http_t@ connection object
572  * and optionally the resource path to use for the destination.  These calls
573  * will block until a connection is made, the timeout expires, the integer
574  * pointed to by "cancel" is non-zero, or the callback function (or block)
575  * returns 0.  The caller is responsible for calling @link httpClose@ on the
576  * returned connection.
577  *
578  * Starting with CUPS 2.2.4, the caller can pass @code CUPS_DEST_FLAGS_DEVICE@
579  * for the "flags" argument to connect directly to the device associated with
580  * the destination.  Otherwise, the connection is made to the CUPS scheduler
581  * associated with the destination.
582  *
583  * @since CUPS 1.6/macOS 10.8@
584  */
585 
586 http_t *				/* O - Connection to destination or @code NULL@ */
cupsConnectDest(cups_dest_t * dest,unsigned flags,int msec,int * cancel,char * resource,size_t resourcesize,cups_dest_cb_t cb,void * user_data)587 cupsConnectDest(
588     cups_dest_t    *dest,		/* I - Destination */
589     unsigned       flags,		/* I - Connection flags */
590     int            msec,		/* I - Timeout in milliseconds */
591     int            *cancel,		/* I - Pointer to "cancel" variable */
592     char           *resource,		/* I - Resource buffer */
593     size_t         resourcesize,	/* I - Size of resource buffer */
594     cups_dest_cb_t cb,			/* I - Callback function */
595     void           *user_data)		/* I - User data pointer */
596 {
597   const char	*uri;			/* Printer URI */
598   char		scheme[32],		/* URI scheme */
599 		userpass[256],		/* Username and password (unused) */
600 		hostname[256],		/* Hostname */
601 		tempresource[1024];	/* Temporary resource buffer */
602   int		port;			/* Port number */
603   char		portstr[16];		/* Port number string */
604   http_encryption_t encryption;		/* Encryption to use */
605   http_addrlist_t *addrlist;		/* Address list for server */
606   http_t	*http;			/* Connection to server */
607 
608 
609   DEBUG_printf(("cupsConnectDest(dest=%p, flags=0x%x, msec=%d, cancel=%p(%d), resource=\"%s\", resourcesize=" CUPS_LLFMT ", cb=%p, user_data=%p)", (void *)dest, flags, msec, (void *)cancel, cancel ? *cancel : -1, resource, CUPS_LLCAST resourcesize, (void *)cb, user_data));
610 
611  /*
612   * Range check input...
613   */
614 
615   if (!dest)
616   {
617     if (resource)
618       *resource = '\0';
619 
620     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
621     return (NULL);
622   }
623 
624   if (!resource || resourcesize < 1)
625   {
626     resource     = tempresource;
627     resourcesize = sizeof(tempresource);
628   }
629 
630  /*
631   * Grab the printer URI...
632   */
633 
634   if (flags & CUPS_DEST_FLAGS_DEVICE)
635   {
636     if ((uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL)
637     {
638 #ifdef HAVE_DNSSD
639       if (strstr(uri, "._tcp"))
640         uri = cups_dnssd_resolve(dest, uri, msec, cancel, cb, user_data);
641 #endif /* HAVE_DNSSD */
642     }
643   }
644   else if ((uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options)) == NULL)
645   {
646     if ((uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL)
647     {
648 #ifdef HAVE_DNSSD
649       if (strstr(uri, "._tcp"))
650         uri = cups_dnssd_resolve(dest, uri, msec, cancel, cb, user_data);
651 #endif /* HAVE_DNSSD */
652     }
653 
654     if (uri)
655       uri = _cupsCreateDest(dest->name, cupsGetOption("printer-info", dest->num_options, dest->options), NULL, uri, tempresource, sizeof(tempresource));
656 
657     if (uri)
658     {
659       dest->num_options = cupsAddOption("printer-uri-supported", uri, dest->num_options, &dest->options);
660 
661       uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options);
662     }
663   }
664 
665   if (!uri)
666   {
667     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0);
668 
669     if (cb)
670       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest);
671 
672     return (NULL);
673   }
674 
675   if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme),
676                       userpass, sizeof(userpass), hostname, sizeof(hostname),
677                       &port, resource, (int)resourcesize) < HTTP_URI_STATUS_OK)
678   {
679     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1);
680 
681     if (cb)
682       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR,
683             dest);
684 
685     return (NULL);
686   }
687 
688  /*
689   * Lookup the address for the server...
690   */
691 
692   if (cb)
693     (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, dest);
694 
695   snprintf(portstr, sizeof(portstr), "%d", port);
696 
697   if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portstr)) == NULL)
698   {
699     if (cb)
700       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest);
701 
702     return (NULL);
703   }
704 
705   if (cancel && *cancel)
706   {
707     httpAddrFreeList(addrlist);
708 
709     if (cb)
710       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CANCELED, dest);
711 
712     return (NULL);
713   }
714 
715  /*
716   * Create the HTTP object pointing to the server referenced by the URI...
717   */
718 
719   if (!strcmp(scheme, "ipps") || port == 443)
720     encryption = HTTP_ENCRYPTION_ALWAYS;
721   else
722     encryption = HTTP_ENCRYPTION_IF_REQUESTED;
723 
724   http = httpConnect2(hostname, port, addrlist, AF_UNSPEC, encryption, 1, 0, NULL);
725   httpAddrFreeList(addrlist);
726 
727  /*
728   * Connect if requested...
729   */
730 
731   if (flags & CUPS_DEST_FLAGS_UNCONNECTED)
732   {
733     if (cb)
734       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED, dest);
735   }
736   else
737   {
738     if (cb)
739       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING, dest);
740 
741     if (!httpReconnect2(http, msec, cancel) && cb)
742     {
743       if (cancel && *cancel)
744 	(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_CONNECTING, dest);
745       else
746 	(*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest);
747     }
748     else if (cb)
749       (*cb)(user_data, CUPS_DEST_FLAGS_NONE, dest);
750   }
751 
752   return (http);
753 }
754 
755 
756 #ifdef __BLOCKS__
757 /*
758  * 'cupsConnectDestBlock()' - Open a connection to the destination.
759  *
760  * Connect to the destination, returning a new @code http_t@ connection object
761  * and optionally the resource path to use for the destination.  These calls
762  * will block until a connection is made, the timeout expires, the integer
763  * pointed to by "cancel" is non-zero, or the block returns 0.  The caller is
764  * responsible for calling @link httpClose@ on the returned connection.
765  *
766  * Starting with CUPS 2.2.4, the caller can pass  @code CUPS_DEST_FLAGS_DEVICE@
767  * for the "flags" argument to connect directly to the device associated with
768  * the destination.  Otherwise, the connection is made to the CUPS scheduler
769  * associated with the destination.
770  *
771  * @since CUPS 1.6/macOS 10.8@ @exclude all@
772  */
773 
774 http_t *				/* O - Connection to destination or @code NULL@ */
cupsConnectDestBlock(cups_dest_t * dest,unsigned flags,int msec,int * cancel,char * resource,size_t resourcesize,cups_dest_block_t block)775 cupsConnectDestBlock(
776     cups_dest_t       *dest,		/* I - Destination */
777     unsigned          flags,		/* I - Connection flags */
778     int               msec,		/* I - Timeout in milliseconds */
779     int               *cancel,		/* I - Pointer to "cancel" variable */
780     char              *resource,	/* I - Resource buffer */
781     size_t            resourcesize,	/* I - Size of resource buffer */
782     cups_dest_block_t block)		/* I - Callback block */
783 {
784   return (cupsConnectDest(dest, flags, msec, cancel, resource, resourcesize,
785                           (cups_dest_cb_t)cups_block_cb, (void *)block));
786 }
787 #endif /* __BLOCKS__ */
788 
789 
790 /*
791  * 'cupsCopyDest()' - Copy a destination.
792  *
793  * Make a copy of the destination to an array of destinations (or just a single
794  * copy) - for use with the cupsEnumDests* functions. The caller is responsible
795  * for calling cupsFreeDests() on the returned object(s).
796  *
797  * @since CUPS 1.6/macOS 10.8@
798  */
799 
800 int                                     /* O  - New number of destinations */
cupsCopyDest(cups_dest_t * dest,int num_dests,cups_dest_t ** dests)801 cupsCopyDest(cups_dest_t *dest,         /* I  - Destination to copy */
802              int         num_dests,     /* I  - Number of destinations */
803              cups_dest_t **dests)       /* IO - Destination array */
804 {
805   int		i;			/* Looping var */
806   cups_dest_t	*new_dest;		/* New destination pointer */
807   cups_option_t	*new_option,		/* Current destination option */
808 		*option;		/* Current parent option */
809 
810 
811  /*
812   * Range check input...
813   */
814 
815   if (!dest || num_dests < 0 || !dests)
816     return (num_dests);
817 
818  /*
819   * See if the destination already exists...
820   */
821 
822   if ((new_dest = cupsGetDest(dest->name, dest->instance, num_dests,
823                               *dests)) != NULL)
824   {
825    /*
826     * Protect against copying destination to itself...
827     */
828 
829     if (new_dest == dest)
830       return (num_dests);
831 
832    /*
833     * Otherwise, free the options...
834     */
835 
836     cupsFreeOptions(new_dest->num_options, new_dest->options);
837 
838     new_dest->num_options = 0;
839     new_dest->options     = NULL;
840   }
841   else
842     new_dest = cups_add_dest(dest->name, dest->instance, &num_dests, dests);
843 
844   if (new_dest)
845   {
846     new_dest->is_default = dest->is_default;
847 
848     if ((new_dest->options = calloc((size_t)dest->num_options, sizeof(cups_option_t))) == NULL)
849       return (cupsRemoveDest(dest->name, dest->instance, num_dests, dests));
850 
851     new_dest->num_options = dest->num_options;
852 
853     for (i = dest->num_options, option = dest->options,
854 	     new_option = new_dest->options;
855 	 i > 0;
856 	 i --, option ++, new_option ++)
857     {
858       new_option->name  = _cupsStrRetain(option->name);
859       new_option->value = _cupsStrRetain(option->value);
860     }
861   }
862 
863   return (num_dests);
864 }
865 
866 
867 /*
868  * '_cupsCreateDest()' - Create a local (temporary) queue.
869  */
870 
871 char *					/* O - Printer URI or @code NULL@ on error */
_cupsCreateDest(const char * name,const char * info,const char * device_id,const char * device_uri,char * uri,size_t urisize)872 _cupsCreateDest(const char *name,	/* I - Printer name */
873                 const char *info,	/* I - Printer description of @code NULL@ */
874 		const char *device_id,	/* I - 1284 Device ID or @code NULL@ */
875 		const char *device_uri,	/* I - Device URI */
876 		char       *uri,	/* I - Printer URI buffer */
877 		size_t     urisize)	/* I - Size of URI buffer */
878 {
879   http_t	*http;			/* Connection to server */
880   ipp_t		*request,		/* CUPS-Create-Local-Printer request */
881 		*response;		/* CUPS-Create-Local-Printer response */
882   ipp_attribute_t *attr;		/* printer-uri-supported attribute */
883   ipp_pstate_t	state = IPP_PSTATE_STOPPED;
884 					/* printer-state value */
885 
886 
887   if (!name || !device_uri || !uri || urisize < 32)
888     return (NULL);
889 
890   if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL)) == NULL)
891     return (NULL);
892 
893   request = ippNewRequest(IPP_OP_CUPS_CREATE_LOCAL_PRINTER);
894 
895   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "ipp://localhost/");
896   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
897 
898   ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_URI, "device-uri", NULL, device_uri);
899   ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name);
900   if (info)
901     ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, info);
902   if (device_id)
903     ippAddString(request, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
904 
905   response = cupsDoRequest(http, request, "/");
906 
907   if ((attr = ippFindAttribute(response, "printer-uri-supported", IPP_TAG_URI)) != NULL)
908     strlcpy(uri, ippGetString(attr, 0, NULL), urisize);
909   else
910   {
911     ippDelete(response);
912     httpClose(http);
913     return (NULL);
914   }
915 
916   if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL)
917     state = (ipp_pstate_t)ippGetInteger(attr, 0);
918 
919   while (state == IPP_PSTATE_STOPPED && cupsLastError() == IPP_STATUS_OK)
920   {
921     sleep(1);
922     ippDelete(response);
923 
924     request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
925 
926     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
927     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
928     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "printer-state");
929 
930     response = cupsDoRequest(http, request, "/");
931 
932     if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL)
933       state = (ipp_pstate_t)ippGetInteger(attr, 0);
934   }
935 
936   ippDelete(response);
937 
938   httpClose(http);
939 
940   return (uri);
941 }
942 
943 
944 /*
945  * 'cupsEnumDests()' - Enumerate available destinations with a callback function.
946  *
947  * Destinations are enumerated from one or more sources.  The callback function
948  * receives the @code user_data@ pointer and the destination pointer which can
949  * be used as input to the @link cupsCopyDest@ function.  The function must
950  * return 1 to continue enumeration or 0 to stop.
951  *
952  * The @code type@ and @code mask@ arguments allow the caller to filter the
953  * destinations that are enumerated.  Passing 0 for both will enumerate all
954  * printers.  The constant @code CUPS_PRINTER_DISCOVERED@ is used to filter on
955  * destinations that are available but have not yet been added locally.
956  *
957  * Enumeration happens on the current thread and does not return until all
958  * destinations have been enumerated or the callback function returns 0.
959  *
960  * Note: The callback function will likely receive multiple updates for the same
961  * destinations - it is up to the caller to suppress any duplicate destinations.
962  *
963  * @since CUPS 1.6/macOS 10.8@
964  */
965 
966 int					/* O - 1 on success, 0 on failure */
cupsEnumDests(unsigned flags,int msec,int * cancel,cups_ptype_t type,cups_ptype_t mask,cups_dest_cb_t cb,void * user_data)967 cupsEnumDests(
968   unsigned       flags,			/* I - Enumeration flags */
969   int            msec,			/* I - Timeout in milliseconds, -1 for indefinite */
970   int            *cancel,		/* I - Pointer to "cancel" variable */
971   cups_ptype_t   type,			/* I - Printer type bits */
972   cups_ptype_t   mask,			/* I - Mask for printer type bits */
973   cups_dest_cb_t cb,			/* I - Callback function */
974   void           *user_data)		/* I - User data */
975 {
976   return (cups_enum_dests(CUPS_HTTP_DEFAULT, flags, msec, cancel, type, mask, cb, user_data));
977 }
978 
979 
980 #  ifdef __BLOCKS__
981 /*
982  * 'cupsEnumDestsBlock()' - Enumerate available destinations with a block.
983  *
984  * Destinations are enumerated from one or more sources.  The block receives the
985  * @code user_data@ pointer and the destination pointer which can be used as
986  * input to the @link cupsCopyDest@ function.  The block must return 1 to
987  * continue enumeration or 0 to stop.
988  *
989  * The @code type@ and @code mask@ arguments allow the caller to filter the
990  * destinations that are enumerated.  Passing 0 for both will enumerate all
991  * printers.  The constant @code CUPS_PRINTER_DISCOVERED@ is used to filter on
992  * destinations that are available but have not yet been added locally.
993  *
994  * Enumeration happens on the current thread and does not return until all
995  * destinations have been enumerated or the block returns 0.
996  *
997  * Note: The block will likely receive multiple updates for the same
998  * destinations - it is up to the caller to suppress any duplicate destinations.
999  *
1000  * @since CUPS 1.6/macOS 10.8@ @exclude all@
1001  */
1002 
1003 int					/* O - 1 on success, 0 on failure */
cupsEnumDestsBlock(unsigned flags,int timeout,int * cancel,cups_ptype_t type,cups_ptype_t mask,cups_dest_block_t block)1004 cupsEnumDestsBlock(
1005     unsigned          flags,		/* I - Enumeration flags */
1006     int               timeout,		/* I - Timeout in milliseconds, 0 for indefinite */
1007     int               *cancel,		/* I - Pointer to "cancel" variable */
1008     cups_ptype_t      type,		/* I - Printer type bits */
1009     cups_ptype_t      mask,		/* I - Mask for printer type bits */
1010     cups_dest_block_t block)		/* I - Block */
1011 {
1012   return (cupsEnumDests(flags, timeout, cancel, type, mask,
1013                         (cups_dest_cb_t)cups_block_cb, (void *)block));
1014 }
1015 #  endif /* __BLOCKS__ */
1016 
1017 
1018 /*
1019  * 'cupsFreeDests()' - Free the memory used by the list of destinations.
1020  */
1021 
1022 void
cupsFreeDests(int num_dests,cups_dest_t * dests)1023 cupsFreeDests(int         num_dests,	/* I - Number of destinations */
1024               cups_dest_t *dests)	/* I - Destinations */
1025 {
1026   int		i;			/* Looping var */
1027   cups_dest_t	*dest;			/* Current destination */
1028 
1029 
1030   if (num_dests == 0 || dests == NULL)
1031     return;
1032 
1033   for (i = num_dests, dest = dests; i > 0; i --, dest ++)
1034   {
1035     _cupsStrFree(dest->name);
1036     _cupsStrFree(dest->instance);
1037 
1038     cupsFreeOptions(dest->num_options, dest->options);
1039   }
1040 
1041   free(dests);
1042 }
1043 
1044 
1045 /*
1046  * 'cupsGetDest()' - Get the named destination from the list.
1047  *
1048  * Use the @link cupsEnumDests@ or @link cupsGetDests2@ functions to get a
1049  * list of supported destinations for the current user.
1050  */
1051 
1052 cups_dest_t *				/* O - Destination pointer or @code NULL@ */
cupsGetDest(const char * name,const char * instance,int num_dests,cups_dest_t * dests)1053 cupsGetDest(const char  *name,		/* I - Destination name or @code NULL@ for the default destination */
1054             const char	*instance,	/* I - Instance name or @code NULL@ */
1055             int         num_dests,	/* I - Number of destinations */
1056             cups_dest_t *dests)		/* I - Destinations */
1057 {
1058   int	diff,				/* Result of comparison */
1059 	match;				/* Matching index */
1060 
1061 
1062   if (num_dests <= 0 || !dests)
1063     return (NULL);
1064 
1065   if (!name)
1066   {
1067    /*
1068     * NULL name for default printer.
1069     */
1070 
1071     while (num_dests > 0)
1072     {
1073       if (dests->is_default)
1074         return (dests);
1075 
1076       num_dests --;
1077       dests ++;
1078     }
1079   }
1080   else
1081   {
1082    /*
1083     * Lookup name and optionally the instance...
1084     */
1085 
1086     match = cups_find_dest(name, instance, num_dests, dests, -1, &diff);
1087 
1088     if (!diff)
1089       return (dests + match);
1090   }
1091 
1092   return (NULL);
1093 }
1094 
1095 
1096 /*
1097  * '_cupsGetDestResource()' - Get the resource path and URI for a destination.
1098  */
1099 
1100 const char *				/* O - URI */
_cupsGetDestResource(cups_dest_t * dest,unsigned flags,char * resource,size_t resourcesize)1101 _cupsGetDestResource(
1102     cups_dest_t *dest,			/* I - Destination */
1103     unsigned    flags,			/* I - Destination flags */
1104     char        *resource,		/* I - Resource buffer */
1105     size_t      resourcesize)		/* I - Size of resource buffer */
1106 {
1107   const char	*uri,			/* URI */
1108 		*device_uri,		/* Device URI */
1109 		*printer_uri;		/* Printer URI */
1110   char		scheme[32],		/* URI scheme */
1111 		userpass[256],		/* Username and password (unused) */
1112 		hostname[256];		/* Hostname */
1113   int		port;			/* Port number */
1114 
1115 
1116   DEBUG_printf(("_cupsGetDestResource(dest=%p(%s), flags=%u, resource=%p, resourcesize=%d)", (void *)dest, dest->name, flags, (void *)resource, (int)resourcesize));
1117 
1118  /*
1119   * Range check input...
1120   */
1121 
1122   if (!dest || !resource || resourcesize < 1)
1123   {
1124     if (resource)
1125       *resource = '\0';
1126 
1127     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1128     return (NULL);
1129   }
1130 
1131  /*
1132   * Grab the printer and device URIs...
1133   */
1134 
1135   device_uri  = cupsGetOption("device-uri", dest->num_options, dest->options);
1136   printer_uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options);
1137 
1138   DEBUG_printf(("1_cupsGetDestResource: device-uri=\"%s\", printer-uri-supported=\"%s\".", device_uri, printer_uri));
1139 
1140 #ifdef HAVE_DNSSD
1141   if (((flags & CUPS_DEST_FLAGS_DEVICE) || !printer_uri) && device_uri && strstr(device_uri, "._tcp"))
1142   {
1143     if ((device_uri = cups_dnssd_resolve(dest, device_uri, 5000, NULL, NULL, NULL)) != NULL)
1144     {
1145       DEBUG_printf(("1_cupsGetDestResource: Resolved device-uri=\"%s\".", device_uri));
1146     }
1147     else
1148     {
1149       DEBUG_puts("1_cupsGetDestResource: Unable to resolve device.");
1150 
1151       if (resource)
1152 	*resource = '\0';
1153 
1154       _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0);
1155 
1156       return (NULL);
1157     }
1158   }
1159 #endif /* HAVE_DNSSD */
1160 
1161   if (flags & CUPS_DEST_FLAGS_DEVICE)
1162   {
1163     uri = device_uri;
1164   }
1165   else if (printer_uri)
1166   {
1167     uri = printer_uri;
1168   }
1169   else
1170   {
1171     uri = _cupsCreateDest(dest->name, cupsGetOption("printer-info", dest->num_options, dest->options), NULL, device_uri, resource, resourcesize);
1172 
1173     if (uri)
1174     {
1175       DEBUG_printf(("1_cupsGetDestResource: Local printer-uri-supported=\"%s\"", uri));
1176 
1177       dest->num_options = cupsAddOption("printer-uri-supported", uri, dest->num_options, &dest->options);
1178 
1179       uri = cupsGetOption("printer-uri-supported", dest->num_options, dest->options);
1180     }
1181   }
1182 
1183   if (!uri)
1184   {
1185     DEBUG_puts("1_cupsGetDestResource: No printer-uri-supported or device-uri found.");
1186 
1187     if (resource)
1188       *resource = '\0';
1189 
1190     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOENT), 0);
1191 
1192     return (NULL);
1193   }
1194   else if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, (int)resourcesize) < HTTP_URI_STATUS_OK)
1195   {
1196     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad URI."), 1);
1197 
1198     return (NULL);
1199   }
1200 
1201   DEBUG_printf(("1_cupsGetDestResource: resource=\"%s\"", resource));
1202 
1203   return (uri);
1204 }
1205 
1206 
1207 /*
1208  * 'cupsGetDestWithURI()' - Get a destination associated with a URI.
1209  *
1210  * "name" is the desired name for the printer. If @code NULL@, a name will be
1211  * created using the URI.
1212  *
1213  * "uri" is the "ipp" or "ipps" URI for the printer.
1214  *
1215  * @since CUPS 2.0/macOS 10.10@
1216  */
1217 
1218 cups_dest_t *				/* O - Destination or @code NULL@ */
cupsGetDestWithURI(const char * name,const char * uri)1219 cupsGetDestWithURI(const char *name,	/* I - Desired printer name or @code NULL@ */
1220                    const char *uri)	/* I - URI for the printer */
1221 {
1222   cups_dest_t	*dest;			/* New destination */
1223   char		temp[1024],		/* Temporary string */
1224 		scheme[256],		/* Scheme from URI */
1225 		userpass[256],		/* Username:password from URI */
1226 		hostname[256],		/* Hostname from URI */
1227 		resource[1024],		/* Resource path from URI */
1228 		*ptr;			/* Pointer into string */
1229   const char	*info;			/* printer-info string */
1230   int		port;			/* Port number from URI */
1231 
1232 
1233  /*
1234   * Range check input...
1235   */
1236 
1237   if (!uri)
1238   {
1239     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1240     return (NULL);
1241   }
1242 
1243   if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK ||
1244       (strncmp(uri, "ipp://", 6) && strncmp(uri, "ipps://", 7)))
1245   {
1246     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad printer-uri."), 1);
1247 
1248     return (NULL);
1249   }
1250 
1251   if (name)
1252   {
1253     info = name;
1254   }
1255   else
1256   {
1257    /*
1258     * Create the name from the URI...
1259     */
1260 
1261     if (strstr(hostname, "._tcp"))
1262     {
1263      /*
1264       * Use the service instance name...
1265       */
1266 
1267       if ((ptr = strstr(hostname, "._")) != NULL)
1268         *ptr = '\0';
1269 
1270       cups_queue_name(temp, hostname, sizeof(temp));
1271       name = temp;
1272       info = hostname;
1273     }
1274     else if (!strncmp(resource, "/classes/", 9))
1275     {
1276       snprintf(temp, sizeof(temp), "%s @ %s", resource + 9, hostname);
1277       name = resource + 9;
1278       info = temp;
1279     }
1280     else if (!strncmp(resource, "/printers/", 10))
1281     {
1282       snprintf(temp, sizeof(temp), "%s @ %s", resource + 10, hostname);
1283       name = resource + 10;
1284       info = temp;
1285     }
1286     else if (!strncmp(resource, "/ipp/print/", 11))
1287     {
1288       snprintf(temp, sizeof(temp), "%s @ %s", resource + 11, hostname);
1289       name = resource + 11;
1290       info = temp;
1291     }
1292     else
1293     {
1294       name = hostname;
1295       info = hostname;
1296     }
1297   }
1298 
1299  /*
1300   * Create the destination...
1301   */
1302 
1303   if ((dest = calloc(1, sizeof(cups_dest_t))) == NULL)
1304   {
1305     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
1306     return (NULL);
1307   }
1308 
1309   dest->name        = _cupsStrAlloc(name);
1310   dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &(dest->options));
1311   dest->num_options = cupsAddOption("printer-info", info, dest->num_options, &(dest->options));
1312 
1313   return (dest);
1314 }
1315 
1316 
1317 /*
1318  * '_cupsGetDests()' - Get destinations from a server.
1319  *
1320  * "op" is IPP_OP_CUPS_GET_PRINTERS to get a full list, IPP_OP_CUPS_GET_DEFAULT
1321  * to get the system-wide default printer, or IPP_OP_GET_PRINTER_ATTRIBUTES for
1322  * a known printer.
1323  *
1324  * "name" is the name of an existing printer and is only used when "op" is
1325  * IPP_OP_GET_PRINTER_ATTRIBUTES.
1326  *
1327  * "dest" is initialized to point to the array of destinations.
1328  *
1329  * 0 is returned if there are no printers, no default printer, or the named
1330  * printer does not exist, respectively.
1331  *
1332  * Free the memory used by the destination array using the @link cupsFreeDests@
1333  * function.
1334  *
1335  * Note: On macOS this function also gets the default paper from the system
1336  * preferences (~/L/P/org.cups.PrintingPrefs.plist) and includes it in the
1337  * options array for each destination that supports it.
1338  */
1339 
1340 int					/* O  - Number of destinations */
_cupsGetDests(http_t * http,ipp_op_t op,const char * name,cups_dest_t ** dests,cups_ptype_t type,cups_ptype_t mask)1341 _cupsGetDests(http_t       *http,	/* I  - Connection to server or
1342 					 *      @code CUPS_HTTP_DEFAULT@ */
1343 	      ipp_op_t     op,		/* I  - IPP operation */
1344 	      const char   *name,	/* I  - Name of destination */
1345 	      cups_dest_t  **dests,	/* IO - Destinations */
1346 	      cups_ptype_t type,	/* I  - Printer type bits */
1347 	      cups_ptype_t mask)	/* I  - Printer type mask */
1348 {
1349   int		num_dests = 0;		/* Number of destinations */
1350   cups_dest_t	*dest;			/* Current destination */
1351   ipp_t		*request,		/* IPP Request */
1352 		*response;		/* IPP Response */
1353   ipp_attribute_t *attr;		/* Current attribute */
1354   const char	*printer_name;		/* printer-name attribute */
1355   char		uri[1024];		/* printer-uri value */
1356   int		num_options;		/* Number of options */
1357   cups_option_t	*options;		/* Options */
1358 #ifdef __APPLE__
1359   char		media_default[41];	/* Default paper size */
1360 #endif /* __APPLE__ */
1361   char		optname[1024],		/* Option name */
1362 		value[2048],		/* Option value */
1363 		*ptr;			/* Pointer into name/value */
1364   static const char * const pattrs[] =	/* Attributes we're interested in */
1365 		{
1366 		  "auth-info-required",
1367 		  "device-uri",
1368 		  "job-sheets-default",
1369 		  "marker-change-time",
1370 		  "marker-colors",
1371 		  "marker-high-levels",
1372 		  "marker-levels",
1373 		  "marker-low-levels",
1374 		  "marker-message",
1375 		  "marker-names",
1376 		  "marker-types",
1377 #ifdef __APPLE__
1378 		  "media-supported",
1379 #endif /* __APPLE__ */
1380 		  "printer-commands",
1381 		  "printer-defaults",
1382 		  "printer-info",
1383 		  "printer-is-accepting-jobs",
1384 		  "printer-is-shared",
1385                   "printer-is-temporary",
1386 		  "printer-location",
1387 		  "printer-make-and-model",
1388 		  "printer-mandatory-job-attributes",
1389 		  "printer-name",
1390 		  "printer-state",
1391 		  "printer-state-change-time",
1392 		  "printer-state-reasons",
1393 		  "printer-type",
1394 		  "printer-uri-supported"
1395 		};
1396 
1397 
1398   DEBUG_printf(("_cupsGetDests(http=%p, op=%x(%s), name=\"%s\", dests=%p, type=%x, mask=%x)", (void *)http, op, ippOpString(op), name, (void *)dests, type, mask));
1399 
1400 #ifdef __APPLE__
1401  /*
1402   * Get the default paper size...
1403   */
1404 
1405   appleGetPaperSize(media_default, sizeof(media_default));
1406   DEBUG_printf(("1_cupsGetDests: Default media is '%s'.", media_default));
1407 #endif /* __APPLE__ */
1408 
1409  /*
1410   * Build a IPP_OP_CUPS_GET_PRINTERS or IPP_OP_GET_PRINTER_ATTRIBUTES request, which
1411   * require the following attributes:
1412   *
1413   *    attributes-charset
1414   *    attributes-natural-language
1415   *    requesting-user-name
1416   *    printer-uri [for IPP_OP_GET_PRINTER_ATTRIBUTES]
1417   */
1418 
1419   request = ippNewRequest(op);
1420 
1421   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
1422                 "requested-attributes", sizeof(pattrs) / sizeof(pattrs[0]),
1423 		NULL, pattrs);
1424 
1425   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
1426                "requesting-user-name", NULL, cupsUser());
1427 
1428   if (name && op != IPP_OP_CUPS_GET_DEFAULT)
1429   {
1430     httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
1431                      "localhost", ippPort(), "/printers/%s", name);
1432     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
1433                  uri);
1434   }
1435   else if (mask)
1436   {
1437     ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type", (int)type);
1438     ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM, "printer-type-mask", (int)mask);
1439   }
1440 
1441  /*
1442   * Do the request and get back a response...
1443   */
1444 
1445   if ((response = cupsDoRequest(http, request, "/")) != NULL)
1446   {
1447     for (attr = response->attrs; attr != NULL; attr = attr->next)
1448     {
1449      /*
1450       * Skip leading attributes until we hit a printer...
1451       */
1452 
1453       while (attr != NULL && attr->group_tag != IPP_TAG_PRINTER)
1454         attr = attr->next;
1455 
1456       if (attr == NULL)
1457         break;
1458 
1459      /*
1460       * Pull the needed attributes from this printer...
1461       */
1462 
1463       printer_name = NULL;
1464       num_options  = 0;
1465       options      = NULL;
1466 
1467       for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next)
1468       {
1469 	if (attr->value_tag != IPP_TAG_INTEGER &&
1470 	    attr->value_tag != IPP_TAG_ENUM &&
1471 	    attr->value_tag != IPP_TAG_BOOLEAN &&
1472 	    attr->value_tag != IPP_TAG_TEXT &&
1473 	    attr->value_tag != IPP_TAG_TEXTLANG &&
1474 	    attr->value_tag != IPP_TAG_NAME &&
1475 	    attr->value_tag != IPP_TAG_NAMELANG &&
1476 	    attr->value_tag != IPP_TAG_KEYWORD &&
1477 	    attr->value_tag != IPP_TAG_RANGE &&
1478 	    attr->value_tag != IPP_TAG_URI)
1479           continue;
1480 
1481         if (!strcmp(attr->name, "auth-info-required") ||
1482 	    !strcmp(attr->name, "device-uri") ||
1483 	    !strcmp(attr->name, "marker-change-time") ||
1484 	    !strcmp(attr->name, "marker-colors") ||
1485 	    !strcmp(attr->name, "marker-high-levels") ||
1486 	    !strcmp(attr->name, "marker-levels") ||
1487 	    !strcmp(attr->name, "marker-low-levels") ||
1488 	    !strcmp(attr->name, "marker-message") ||
1489 	    !strcmp(attr->name, "marker-names") ||
1490 	    !strcmp(attr->name, "marker-types") ||
1491 	    !strcmp(attr->name, "printer-commands") ||
1492 	    !strcmp(attr->name, "printer-info") ||
1493             !strcmp(attr->name, "printer-is-shared") ||
1494             !strcmp(attr->name, "printer-is-temporary") ||
1495 	    !strcmp(attr->name, "printer-make-and-model") ||
1496 	    !strcmp(attr->name, "printer-mandatory-job-attributes") ||
1497 	    !strcmp(attr->name, "printer-state") ||
1498 	    !strcmp(attr->name, "printer-state-change-time") ||
1499 	    !strcmp(attr->name, "printer-type") ||
1500             !strcmp(attr->name, "printer-is-accepting-jobs") ||
1501             !strcmp(attr->name, "printer-location") ||
1502             !strcmp(attr->name, "printer-state-reasons") ||
1503 	    !strcmp(attr->name, "printer-uri-supported"))
1504         {
1505 	 /*
1506 	  * Add a printer description attribute...
1507 	  */
1508 
1509           num_options = cupsAddOption(attr->name,
1510 	                              cups_make_string(attr, value,
1511 				                       sizeof(value)),
1512 				      num_options, &options);
1513 	}
1514 #ifdef __APPLE__
1515 	else if (!strcmp(attr->name, "media-supported") && media_default[0])
1516 	{
1517 	 /*
1518 	  * See if we can set a default media size...
1519 	  */
1520 
1521           int	i;			/* Looping var */
1522 
1523 	  for (i = 0; i < attr->num_values; i ++)
1524 	    if (!_cups_strcasecmp(media_default, attr->values[i].string.text))
1525 	    {
1526               DEBUG_printf(("1_cupsGetDests: Setting media to '%s'.", media_default));
1527 	      num_options = cupsAddOption("media", media_default, num_options, &options);
1528               break;
1529 	    }
1530 	}
1531 #endif /* __APPLE__ */
1532         else if (!strcmp(attr->name, "printer-name") &&
1533 	         attr->value_tag == IPP_TAG_NAME)
1534 	  printer_name = attr->values[0].string.text;
1535         else if (strncmp(attr->name, "notify-", 7) &&
1536                  strncmp(attr->name, "print-quality-", 14) &&
1537                  (attr->value_tag == IPP_TAG_BOOLEAN ||
1538 		  attr->value_tag == IPP_TAG_ENUM ||
1539 		  attr->value_tag == IPP_TAG_INTEGER ||
1540 		  attr->value_tag == IPP_TAG_KEYWORD ||
1541 		  attr->value_tag == IPP_TAG_NAME ||
1542 		  attr->value_tag == IPP_TAG_RANGE) &&
1543 		 (ptr = strstr(attr->name, "-default")) != NULL)
1544 	{
1545 	 /*
1546 	  * Add a default option...
1547 	  */
1548 
1549           strlcpy(optname, attr->name, sizeof(optname));
1550 	  optname[ptr - attr->name] = '\0';
1551 
1552 	  if (_cups_strcasecmp(optname, "media") || !cupsGetOption("media", num_options, options))
1553 	    num_options = cupsAddOption(optname, cups_make_string(attr, value, sizeof(value)), num_options, &options);
1554 	}
1555       }
1556 
1557      /*
1558       * See if we have everything needed...
1559       */
1560 
1561       if (!printer_name)
1562       {
1563         cupsFreeOptions(num_options, options);
1564 
1565         if (attr == NULL)
1566 	  break;
1567 	else
1568           continue;
1569       }
1570 
1571       if ((dest = cups_add_dest(printer_name, NULL, &num_dests, dests)) != NULL)
1572       {
1573         dest->num_options = num_options;
1574 	dest->options     = options;
1575       }
1576       else
1577         cupsFreeOptions(num_options, options);
1578 
1579       if (attr == NULL)
1580 	break;
1581     }
1582 
1583     ippDelete(response);
1584   }
1585 
1586  /*
1587   * Return the count...
1588   */
1589 
1590   return (num_dests);
1591 }
1592 
1593 
1594 /*
1595  * 'cupsGetDests()' - Get the list of destinations from the default server.
1596  *
1597  * Starting with CUPS 1.2, the returned list of destinations include the
1598  * "printer-info", "printer-is-accepting-jobs", "printer-is-shared",
1599  * "printer-make-and-model", "printer-state", "printer-state-change-time",
1600  * "printer-state-reasons", "printer-type", and "printer-uri-supported"
1601  * attributes as options.
1602  *
1603  * CUPS 1.4 adds the "marker-change-time", "marker-colors",
1604  * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message",
1605  * "marker-names", "marker-types", and "printer-commands" attributes as options.
1606  *
1607  * CUPS 2.2 adds accessible IPP printers to the list of destinations that can
1608  * be used.  The "printer-uri-supported" option will be present for those IPP
1609  * printers that have been recently used.
1610  *
1611  * Use the @link cupsFreeDests@ function to free the destination list and
1612  * the @link cupsGetDest@ function to find a particular destination.
1613  *
1614  * @exclude all@
1615  */
1616 
1617 int					/* O - Number of destinations */
cupsGetDests(cups_dest_t ** dests)1618 cupsGetDests(cups_dest_t **dests)	/* O - Destinations */
1619 {
1620   return (cupsGetDests2(CUPS_HTTP_DEFAULT, dests));
1621 }
1622 
1623 
1624 /*
1625  * 'cupsGetDests2()' - Get the list of destinations from the specified server.
1626  *
1627  * Starting with CUPS 1.2, the returned list of destinations include the
1628  * "printer-info", "printer-is-accepting-jobs", "printer-is-shared",
1629  * "printer-make-and-model", "printer-state", "printer-state-change-time",
1630  * "printer-state-reasons", "printer-type", and "printer-uri-supported"
1631  * attributes as options.
1632  *
1633  * CUPS 1.4 adds the "marker-change-time", "marker-colors",
1634  * "marker-high-levels", "marker-levels", "marker-low-levels", "marker-message",
1635  * "marker-names", "marker-types", and "printer-commands" attributes as options.
1636  *
1637  * CUPS 2.2 adds accessible IPP printers to the list of destinations that can
1638  * be used.  The "printer-uri-supported" option will be present for those IPP
1639  * printers that have been recently used.
1640  *
1641  * Use the @link cupsFreeDests@ function to free the destination list and
1642  * the @link cupsGetDest@ function to find a particular destination.
1643  *
1644  * @since CUPS 1.1.21/macOS 10.4@
1645  */
1646 
1647 int					/* O - Number of destinations */
cupsGetDests2(http_t * http,cups_dest_t ** dests)1648 cupsGetDests2(http_t      *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
1649               cups_dest_t **dests)	/* O - Destinations */
1650 {
1651   _cups_getdata_t data;                 /* Enumeration data */
1652 
1653 
1654   DEBUG_printf(("cupsGetDests2(http=%p, dests=%p)", (void *)http, (void *)dests));
1655 
1656 /*
1657   * Range check the input...
1658   */
1659 
1660   if (!dests)
1661   {
1662     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad NULL dests pointer"), 1);
1663     DEBUG_puts("1cupsGetDests2: NULL dests pointer, returning 0.");
1664     return (0);
1665   }
1666 
1667  /*
1668   * Connect to the server as needed...
1669   */
1670 
1671   if (!http)
1672   {
1673     if ((http = _cupsConnect()) == NULL)
1674     {
1675       *dests = NULL;
1676 
1677       return (0);
1678     }
1679   }
1680 
1681  /*
1682   * Grab the printers and classes...
1683   */
1684 
1685   data.num_dests = 0;
1686   data.dests     = NULL;
1687 
1688   if (!httpAddrLocalhost(httpGetAddress(http)))
1689   {
1690    /*
1691     * When talking to a remote cupsd, just enumerate printers on the remote
1692     * cupsd.
1693     */
1694 
1695     cups_enum_dests(http, 0, _CUPS_DNSSD_GET_DESTS, NULL, 0, CUPS_PRINTER_DISCOVERED, (cups_dest_cb_t)cups_get_cb, &data);
1696   }
1697   else
1698   {
1699    /*
1700     * When talking to a local cupsd, enumerate both local printers and ones we
1701     * can find on the network...
1702     */
1703 
1704     cups_enum_dests(http, 0, _CUPS_DNSSD_GET_DESTS, NULL, 0, 0, (cups_dest_cb_t)cups_get_cb, &data);
1705   }
1706 
1707  /*
1708   * Return the number of destinations...
1709   */
1710 
1711   *dests = data.dests;
1712 
1713   if (data.num_dests > 0)
1714     _cupsSetError(IPP_STATUS_OK, NULL, 0);
1715 
1716   DEBUG_printf(("1cupsGetDests2: Returning %d destinations.", data.num_dests));
1717 
1718   return (data.num_dests);
1719 }
1720 
1721 
1722 /*
1723  * 'cupsGetNamedDest()' - Get options for the named destination.
1724  *
1725  * This function is optimized for retrieving a single destination and should
1726  * be used instead of @link cupsGetDests2@ and @link cupsGetDest@ when you
1727  * either know the name of the destination or want to print to the default
1728  * destination.  If @code NULL@ is returned, the destination does not exist or
1729  * there is no default destination.
1730  *
1731  * If "http" is @code CUPS_HTTP_DEFAULT@, the connection to the default print
1732  * server will be used.
1733  *
1734  * If "name" is @code NULL@, the default printer for the current user will be
1735  * returned.
1736  *
1737  * The returned destination must be freed using @link cupsFreeDests@ with a
1738  * "num_dests" value of 1.
1739  *
1740  * @since CUPS 1.4/macOS 10.6@
1741  */
1742 
1743 cups_dest_t *				/* O - Destination or @code NULL@ */
cupsGetNamedDest(http_t * http,const char * name,const char * instance)1744 cupsGetNamedDest(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
1745                  const char *name,	/* I - Destination name or @code NULL@ for the default destination */
1746                  const char *instance)	/* I - Instance name or @code NULL@ */
1747 {
1748   const char    *dest_name;             /* Working destination name */
1749   cups_dest_t	*dest;			/* Destination */
1750   char		filename[1024],		/* Path to lpoptions */
1751 		defname[256];		/* Default printer name */
1752   int		set_as_default = 0;	/* Set returned destination as default */
1753   ipp_op_t	op = IPP_OP_GET_PRINTER_ATTRIBUTES;
1754 					/* IPP operation to get server ops */
1755   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
1756 
1757 
1758   DEBUG_printf(("cupsGetNamedDest(http=%p, name=\"%s\", instance=\"%s\")", (void *)http, name, instance));
1759 
1760  /*
1761   * If "name" is NULL, find the default destination...
1762   */
1763 
1764   dest_name = name;
1765 
1766   if (!dest_name)
1767   {
1768     set_as_default = 1;
1769     dest_name      = _cupsUserDefault(defname, sizeof(defname));
1770 
1771     if (dest_name)
1772     {
1773       char	*ptr;			/* Temporary pointer... */
1774 
1775       if ((ptr = strchr(defname, '/')) != NULL)
1776       {
1777         *ptr++   = '\0';
1778 	instance = ptr;
1779       }
1780       else
1781         instance = NULL;
1782     }
1783 #if _WIN32
1784     else if (cg->home)
1785 #else
1786     else if (cg->home && getuid() != 0)
1787 #endif
1788     {
1789      /*
1790       * No default in the environment, try the user's lpoptions files...
1791       */
1792 
1793 #if _WIN32
1794       snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home);
1795 #else
1796       snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home);
1797 #endif // _WIN32
1798 
1799       dest_name = cups_get_default(filename, defname, sizeof(defname), &instance);
1800 
1801       if (dest_name)
1802         set_as_default = 2;
1803     }
1804 
1805     if (!dest_name)
1806     {
1807      /*
1808       * Still not there?  Try the system lpoptions file...
1809       */
1810 
1811       snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
1812       dest_name = cups_get_default(filename, defname, sizeof(defname), &instance);
1813 
1814       if (dest_name)
1815         set_as_default = 3;
1816     }
1817 
1818     if (!dest_name)
1819     {
1820      /*
1821       * No locally-set default destination, ask the server...
1822       */
1823 
1824       op             = IPP_OP_CUPS_GET_DEFAULT;
1825       set_as_default = 4;
1826 
1827       DEBUG_puts("1cupsGetNamedDest: Asking server for default printer...");
1828     }
1829     else
1830       DEBUG_printf(("1cupsGetNamedDest: Using dest_name=\"%s\"...", dest_name));
1831   }
1832 
1833  /*
1834   * Get the printer's attributes...
1835   */
1836 
1837   if (!_cupsGetDests(http, op, dest_name, &dest, 0, 0))
1838   {
1839     _cups_namedata_t  data;           /* Callback data */
1840 
1841     data.name = dest_name;
1842     data.dest = NULL;
1843 
1844     if (data.name)
1845     {
1846       DEBUG_puts("1cupsGetNamedDest: No queue found for printer, looking on network...");
1847 
1848       cupsEnumDests(0, 1000, NULL, 0, 0, (cups_dest_cb_t)cups_name_cb, &data);
1849     }
1850 
1851     if (!data.dest)
1852     {
1853       switch (set_as_default)
1854       {
1855 	default :
1856 	    _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("The printer or class does not exist."), 1);
1857 	    break;
1858 
1859 	case 1 : /* Set from env vars */
1860 	    if (getenv("LPDEST"))
1861 	      _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("LPDEST environment variable names default destination that does not exist."), 1);
1862 	    else if (getenv("PRINTER"))
1863 	      _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("PRINTER environment variable names default destination that does not exist."), 1);
1864 	    else
1865 	      _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("No default destination."), 1);
1866 	    break;
1867 
1868 	case 2 : /* Set from ~/.cups/lpoptions */
1869 	    _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("~/.cups/lpoptions file names default destination that does not exist."), 1);
1870 	    break;
1871 
1872 	case 3 : /* Set from /etc/cups/lpoptions */
1873 	    _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("/etc/cups/lpoptions file names default destination that does not exist."), 1);
1874 	    break;
1875 
1876 	case 4 : /* Set from server */
1877 	    _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, _("No default destination."), 1);
1878 	    break;
1879       }
1880 
1881       return (NULL);
1882     }
1883 
1884     dest = data.dest;
1885   }
1886 
1887   DEBUG_printf(("1cupsGetNamedDest: Got dest=%p", (void *)dest));
1888 
1889   if (instance)
1890     dest->instance = _cupsStrAlloc(instance);
1891 
1892   if (set_as_default)
1893     dest->is_default = 1;
1894 
1895  /*
1896   * Then add local options...
1897   */
1898 
1899   snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
1900   cups_get_dests(filename, dest_name, instance, 0, 1, 1, &dest);
1901 
1902 #if _WIN32
1903   if (cg->home)
1904 #else
1905   if (cg->home && getuid() != 0)
1906 #endif // _WIN32
1907   {
1908 #if _WIN32
1909     snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home);
1910 #else
1911     snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home);
1912 #endif // _WIN32
1913 
1914     cups_get_dests(filename, dest_name, instance, 0, 1, 1, &dest);
1915   }
1916 
1917  /*
1918   * Return the result...
1919   */
1920 
1921   return (dest);
1922 }
1923 
1924 
1925 /*
1926  * 'cupsRemoveDest()' - Remove a destination from the destination list.
1927  *
1928  * Removing a destination/instance does not delete the class or printer
1929  * queue, merely the lpoptions for that destination/instance.  Use the
1930  * @link cupsSetDests@ or @link cupsSetDests2@ functions to save the new
1931  * options for the user.
1932  *
1933  * @since CUPS 1.3/macOS 10.5@
1934  */
1935 
1936 int					/* O  - New number of destinations */
cupsRemoveDest(const char * name,const char * instance,int num_dests,cups_dest_t ** dests)1937 cupsRemoveDest(const char  *name,	/* I  - Destination name */
1938                const char  *instance,	/* I  - Instance name or @code NULL@ */
1939 	       int         num_dests,	/* I  - Number of destinations */
1940 	       cups_dest_t **dests)	/* IO - Destinations */
1941 {
1942   int		i;			/* Index into destinations */
1943   cups_dest_t	*dest;			/* Pointer to destination */
1944 
1945 
1946  /*
1947   * Find the destination...
1948   */
1949 
1950   if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL)
1951     return (num_dests);
1952 
1953  /*
1954   * Free memory...
1955   */
1956 
1957   _cupsStrFree(dest->name);
1958   _cupsStrFree(dest->instance);
1959   cupsFreeOptions(dest->num_options, dest->options);
1960 
1961  /*
1962   * Remove the destination from the array...
1963   */
1964 
1965   num_dests --;
1966 
1967   i = (int)(dest - *dests);
1968 
1969   if (i < num_dests)
1970     memmove(dest, dest + 1, (size_t)(num_dests - i) * sizeof(cups_dest_t));
1971 
1972   return (num_dests);
1973 }
1974 
1975 
1976 /*
1977  * 'cupsSetDefaultDest()' - Set the default destination.
1978  *
1979  * @since CUPS 1.3/macOS 10.5@
1980  */
1981 
1982 void
cupsSetDefaultDest(const char * name,const char * instance,int num_dests,cups_dest_t * dests)1983 cupsSetDefaultDest(
1984     const char  *name,			/* I - Destination name */
1985     const char  *instance,		/* I - Instance name or @code NULL@ */
1986     int         num_dests,		/* I - Number of destinations */
1987     cups_dest_t *dests)			/* I - Destinations */
1988 {
1989   int		i;			/* Looping var */
1990   cups_dest_t	*dest;			/* Current destination */
1991 
1992 
1993  /*
1994   * Range check input...
1995   */
1996 
1997   if (!name || num_dests <= 0 || !dests)
1998     return;
1999 
2000  /*
2001   * Loop through the array and set the "is_default" flag for the matching
2002   * destination...
2003   */
2004 
2005   for (i = num_dests, dest = dests; i > 0; i --, dest ++)
2006     dest->is_default = !_cups_strcasecmp(name, dest->name) &&
2007                        ((!instance && !dest->instance) ||
2008 		        (instance && dest->instance &&
2009 			 !_cups_strcasecmp(instance, dest->instance)));
2010 }
2011 
2012 
2013 /*
2014  * 'cupsSetDests()' - Save the list of destinations for the default server.
2015  *
2016  * This function saves the destinations to /etc/cups/lpoptions when run
2017  * as root and ~/.cups/lpoptions when run as a normal user.
2018  *
2019  * @exclude all@
2020  */
2021 
2022 void
cupsSetDests(int num_dests,cups_dest_t * dests)2023 cupsSetDests(int         num_dests,	/* I - Number of destinations */
2024              cups_dest_t *dests)	/* I - Destinations */
2025 {
2026   cupsSetDests2(CUPS_HTTP_DEFAULT, num_dests, dests);
2027 }
2028 
2029 
2030 /*
2031  * 'cupsSetDests2()' - Save the list of destinations for the specified server.
2032  *
2033  * This function saves the destinations to /etc/cups/lpoptions when run
2034  * as root and ~/.cups/lpoptions when run as a normal user.
2035  *
2036  * @since CUPS 1.1.21/macOS 10.4@
2037  */
2038 
2039 int					/* O - 0 on success, -1 on error */
cupsSetDests2(http_t * http,int num_dests,cups_dest_t * dests)2040 cupsSetDests2(http_t      *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
2041               int         num_dests,	/* I - Number of destinations */
2042               cups_dest_t *dests)	/* I - Destinations */
2043 {
2044   int		i, j;			/* Looping vars */
2045   int		wrote;			/* Wrote definition? */
2046   cups_dest_t	*dest;			/* Current destination */
2047   cups_option_t	*option;		/* Current option */
2048   _ipp_option_t	*match;			/* Matching attribute for option */
2049   FILE		*fp;			/* File pointer */
2050   char		filename[1024];		/* lpoptions file */
2051   int		num_temps;		/* Number of temporary destinations */
2052   cups_dest_t	*temps = NULL,		/* Temporary destinations */
2053 		*temp;			/* Current temporary dest */
2054   const char	*val;			/* Value of temporary option */
2055   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
2056 
2057 
2058  /*
2059   * Range check the input...
2060   */
2061 
2062   if (!num_dests || !dests)
2063     return (-1);
2064 
2065  /*
2066   * See if the default destination has a printer URI associated with it...
2067   */
2068 
2069   if ((dest = cupsGetDest(/*name*/NULL, /*instance*/NULL, num_dests, dests)) != NULL && !cupsGetOption("printer-uri-supported", dest->num_options, dest->options))
2070   {
2071    /*
2072     * No, try adding it...
2073     */
2074 
2075     const char	*uri;			/* Device/printer URI */
2076 
2077     if ((uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL)
2078     {
2079       char	tempresource[1024];	/* Temporary resource path */
2080 
2081 #ifdef HAVE_DNSSD
2082       if (strstr(uri, "._tcp"))
2083         uri = cups_dnssd_resolve(dest, uri, /*msec*/30000, /*cancel*/NULL, /*cb*/NULL, /*user_data*/NULL);
2084 #endif /* HAVE_DNSSD */
2085 
2086       if (uri)
2087 	uri = _cupsCreateDest(dest->name, cupsGetOption("printer-info", dest->num_options, dest->options), NULL, uri, tempresource, sizeof(tempresource));
2088 
2089       if (uri)
2090 	dest->num_options = cupsAddOption("printer-uri-supported", uri, dest->num_options, &dest->options);
2091     }
2092   }
2093 
2094  /*
2095   * Get the server destinations...
2096   */
2097 
2098   num_temps = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, &temps, 0, 0);
2099 
2100   if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE)
2101   {
2102     cupsFreeDests(num_temps, temps);
2103     return (-1);
2104   }
2105 
2106  /*
2107   * Figure out which file to write to...
2108   */
2109 
2110   snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
2111 
2112   if (cg->home
2113 #ifndef _WIN32
2114       && getuid() != 0
2115 #endif /* !_WIN32 */
2116       )
2117   {
2118    /*
2119     * Create ~/.cups subdirectory...
2120     */
2121 
2122 #if _WIN32
2123     snprintf(filename, sizeof(filename), "%s/AppData/Local/cups", cg->home);
2124 #else
2125     snprintf(filename, sizeof(filename), "%s/.cups", cg->home);
2126 #endif // _WIN32
2127     if (access(filename, 0))
2128       mkdir(filename, 0700);
2129 
2130 #if _WIN32
2131     snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home);
2132 #else
2133     snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home);
2134 #endif // _WIN32
2135   }
2136 
2137  /*
2138   * Try to open the file...
2139   */
2140 
2141   if ((fp = fopen(filename, "w")) == NULL)
2142   {
2143     cupsFreeDests(num_temps, temps);
2144     return (-1);
2145   }
2146 
2147 #ifndef _WIN32
2148  /*
2149   * Set the permissions to 0644 when saving to the /etc/cups/lpoptions
2150   * file...
2151   */
2152 
2153   if (!getuid())
2154     fchmod(fileno(fp), 0644);
2155 #endif /* !_WIN32 */
2156 
2157  /*
2158   * Write each printer; each line looks like:
2159   *
2160   *    Dest name[/instance] options
2161   *    Default name[/instance] options
2162   */
2163 
2164   for (i = num_dests, dest = dests; i > 0; i --, dest ++)
2165     if (dest->instance != NULL || dest->num_options != 0 || dest->is_default)
2166     {
2167       if (dest->is_default)
2168       {
2169 	fprintf(fp, "Default %s", dest->name);
2170 	if (dest->instance)
2171 	  fprintf(fp, "/%s", dest->instance);
2172 
2173         wrote = 1;
2174       }
2175       else
2176         wrote = 0;
2177 
2178       temp = cupsGetDest(dest->name, NULL, num_temps, temps);
2179 
2180       for (j = dest->num_options, option = dest->options; j > 0; j --, option ++)
2181       {
2182        /*
2183         * See if this option is a printer attribute; if so, skip it...
2184 	*/
2185 
2186         if ((match = _ippFindOption(option->name)) != NULL && match->group_tag == IPP_TAG_PRINTER)
2187 	  continue;
2188 
2189        /*
2190 	* See if the server options match these; if so, don't write 'em.
2191 	*/
2192 
2193         if (temp && (val = cupsGetOption(option->name, temp->num_options, temp->options)) != NULL && !_cups_strcasecmp(val, option->value))
2194 	  continue;
2195 
2196        /*
2197         * Options don't match, write to the file...
2198 	*/
2199 
2200         if (!wrote)
2201 	{
2202 	  fprintf(fp, "Dest %s", dest->name);
2203 	  if (dest->instance)
2204 	    fprintf(fp, "/%s", dest->instance);
2205           wrote = 1;
2206 	}
2207 
2208         if (option->value[0])
2209 	{
2210 	  if (strchr(option->value, ' ') || strchr(option->value, '\\') || strchr(option->value, '\"') || strchr(option->value, '\''))
2211 	  {
2212 	   /*
2213 	    * Quote the value...
2214 	    */
2215 
2216 	    fprintf(fp, " %s=\"", option->name);
2217 
2218 	    for (val = option->value; *val; val ++)
2219 	    {
2220 	      if (strchr("\"\'\\", *val))
2221 	        putc('\\', fp);
2222 
2223               putc(*val, fp);
2224 	    }
2225 
2226 	    putc('\"', fp);
2227           }
2228 	  else
2229 	  {
2230 	   /*
2231 	    * Store the literal value...
2232 	    */
2233 
2234 	    fprintf(fp, " %s=%s", option->name, option->value);
2235           }
2236 	}
2237 	else
2238 	  fprintf(fp, " %s", option->name);
2239       }
2240 
2241       if (wrote)
2242         fputs("\n", fp);
2243     }
2244 
2245  /*
2246   * Free the temporary destinations and close the file...
2247   */
2248 
2249   cupsFreeDests(num_temps, temps);
2250 
2251   fclose(fp);
2252 
2253 #ifdef __APPLE__
2254  /*
2255   * Set the default printer for this location - this allows command-line
2256   * and GUI applications to share the same default destination...
2257   */
2258 
2259   if ((dest = cupsGetDest(NULL, NULL, num_dests, dests)) != NULL)
2260   {
2261     CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, dest->name, kCFStringEncodingUTF8);
2262 					/* Default printer name */
2263 
2264     if (name)
2265     {
2266       _cupsAppleSetDefaultPrinter(name);
2267       CFRelease(name);
2268     }
2269   }
2270 #endif /* __APPLE__ */
2271 
2272 #ifdef HAVE_NOTIFY_POST
2273  /*
2274   * Send a notification so that macOS applications can know about the
2275   * change, too.
2276   */
2277 
2278   notify_post("com.apple.printerListChange");
2279 #endif /* HAVE_NOTIFY_POST */
2280 
2281   return (0);
2282 }
2283 
2284 
2285 /*
2286  * '_cupsUserDefault()' - Get the user default printer from environment
2287  *                        variables and location information.
2288  */
2289 
2290 char *					/* O - Default printer or NULL */
_cupsUserDefault(char * name,size_t namesize)2291 _cupsUserDefault(char   *name,		/* I - Name buffer */
2292                  size_t namesize)	/* I - Size of name buffer */
2293 {
2294   const char	*env;			/* LPDEST or PRINTER env variable */
2295 #ifdef __APPLE__
2296   CFStringRef	locprinter;		/* Last printer as this location */
2297 #endif /* __APPLE__ */
2298 
2299 
2300   if ((env = getenv("LPDEST")) == NULL)
2301     if ((env = getenv("PRINTER")) != NULL && !strcmp(env, "lp"))
2302       env = NULL;
2303 
2304   if (env)
2305   {
2306     strlcpy(name, env, namesize);
2307     return (name);
2308   }
2309 
2310 #ifdef __APPLE__
2311  /*
2312   * Use location-based defaults if "use last printer" is selected in the
2313   * system preferences...
2314   */
2315 
2316   if (!getenv("CUPS_NO_APPLE_DEFAULT") && (locprinter = _cupsAppleCopyDefaultPrinter()) != NULL)
2317   {
2318     CFStringGetCString(locprinter, name, (CFIndex)namesize, kCFStringEncodingUTF8);
2319     CFRelease(locprinter);
2320   }
2321   else
2322     name[0] = '\0';
2323 
2324   DEBUG_printf(("1_cupsUserDefault: Returning \"%s\".", name));
2325 
2326   return (*name ? name : NULL);
2327 
2328 #else
2329  /*
2330   * No location-based defaults on this platform...
2331   */
2332 
2333   name[0] = '\0';
2334   return (NULL);
2335 #endif /* __APPLE__ */
2336 }
2337 
2338 
2339 #if _CUPS_LOCATION_DEFAULTS
2340 /*
2341  * 'appleCopyLocations()' - Copy the location history array.
2342  */
2343 
2344 static CFArrayRef			/* O - Location array or NULL */
appleCopyLocations(void)2345 appleCopyLocations(void)
2346 {
2347   CFArrayRef	locations;		/* Location array */
2348 
2349 
2350  /*
2351   * Look up the location array in the preferences...
2352   */
2353 
2354   if ((locations = CFPreferencesCopyAppValue(kLastUsedPrintersKey,
2355                                              kCUPSPrintingPrefs)) == NULL)
2356     return (NULL);
2357 
2358   if (CFGetTypeID(locations) != CFArrayGetTypeID())
2359   {
2360     CFRelease(locations);
2361     return (NULL);
2362   }
2363 
2364   return (locations);
2365 }
2366 
2367 
2368 /*
2369  * 'appleCopyNetwork()' - Get the network ID for the current location.
2370  */
2371 
2372 static CFStringRef			/* O - Network ID */
appleCopyNetwork(void)2373 appleCopyNetwork(void)
2374 {
2375   SCDynamicStoreRef	dynamicStore;	/* System configuration data */
2376   CFStringRef		key;		/* Current network configuration key */
2377   CFDictionaryRef	ip_dict;	/* Network configuration data */
2378   CFStringRef		network = NULL;	/* Current network ID */
2379 
2380 
2381   if ((dynamicStore = SCDynamicStoreCreate(NULL, CFSTR("libcups"), NULL,
2382                                            NULL)) != NULL)
2383   {
2384    /*
2385     * First use the IPv6 router address, if available, since that will generally
2386     * be a globally-unique link-local address.
2387     */
2388 
2389     if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity(
2390                    NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)) != NULL)
2391     {
2392       if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL)
2393       {
2394 	if ((network = CFDictionaryGetValue(ip_dict,
2395 	                                    kSCPropNetIPv6Router)) != NULL)
2396           CFRetain(network);
2397 
2398         CFRelease(ip_dict);
2399       }
2400 
2401       CFRelease(key);
2402     }
2403 
2404    /*
2405     * If that doesn't work, try the IPv4 router address. This isn't as unique
2406     * and will likely be a 10.x.y.z or 192.168.y.z address...
2407     */
2408 
2409     if (!network)
2410     {
2411       if ((key = SCDynamicStoreKeyCreateNetworkGlobalEntity(
2412 		     NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)) != NULL)
2413       {
2414 	if ((ip_dict = SCDynamicStoreCopyValue(dynamicStore, key)) != NULL)
2415 	{
2416 	  if ((network = CFDictionaryGetValue(ip_dict,
2417 					      kSCPropNetIPv4Router)) != NULL)
2418 	    CFRetain(network);
2419 
2420 	  CFRelease(ip_dict);
2421 	}
2422 
2423 	CFRelease(key);
2424       }
2425     }
2426 
2427     CFRelease(dynamicStore);
2428   }
2429 
2430   return (network);
2431 }
2432 #endif /* _CUPS_LOCATION_DEFAULTS */
2433 
2434 
2435 #ifdef __APPLE__
2436 /*
2437  * 'appleGetPaperSize()' - Get the default paper size.
2438  */
2439 
2440 static char *				/* O - Default paper size */
appleGetPaperSize(char * name,size_t namesize)2441 appleGetPaperSize(char   *name,		/* I - Paper size name buffer */
2442                   size_t namesize)	/* I - Size of buffer */
2443 {
2444   CFStringRef	defaultPaperID;		/* Default paper ID */
2445   pwg_media_t	*pwgmedia;		/* PWG media size */
2446 
2447 
2448   defaultPaperID = _cupsAppleCopyDefaultPaperID();
2449   if (!defaultPaperID ||
2450       CFGetTypeID(defaultPaperID) != CFStringGetTypeID() ||
2451       !CFStringGetCString(defaultPaperID, name, (CFIndex)namesize, kCFStringEncodingUTF8))
2452     name[0] = '\0';
2453   else if ((pwgmedia = pwgMediaForLegacy(name)) != NULL)
2454     strlcpy(name, pwgmedia->pwg, namesize);
2455 
2456   if (defaultPaperID)
2457     CFRelease(defaultPaperID);
2458 
2459   return (name);
2460 }
2461 #endif /* __APPLE__ */
2462 
2463 
2464 #if _CUPS_LOCATION_DEFAULTS
2465 /*
2466  * 'appleGetPrinter()' - Get a printer from the history array.
2467  */
2468 
2469 static CFStringRef			/* O - Printer name or NULL */
appleGetPrinter(CFArrayRef locations,CFStringRef network,CFIndex * locindex)2470 appleGetPrinter(CFArrayRef  locations,	/* I - Location array */
2471                 CFStringRef network,	/* I - Network name */
2472 		CFIndex     *locindex)	/* O - Index in array */
2473 {
2474   CFIndex		i,		/* Looping var */
2475 			count;		/* Number of locations */
2476   CFDictionaryRef	location;	/* Current location */
2477   CFStringRef		locnetwork,	/* Current network */
2478 			locprinter;	/* Current printer */
2479 
2480 
2481   for (i = 0, count = CFArrayGetCount(locations); i < count; i ++)
2482     if ((location = CFArrayGetValueAtIndex(locations, i)) != NULL &&
2483         CFGetTypeID(location) == CFDictionaryGetTypeID())
2484     {
2485       if ((locnetwork = CFDictionaryGetValue(location,
2486                                              kLocationNetworkKey)) != NULL &&
2487           CFGetTypeID(locnetwork) == CFStringGetTypeID() &&
2488 	  CFStringCompare(network, locnetwork, 0) == kCFCompareEqualTo &&
2489 	  (locprinter = CFDictionaryGetValue(location,
2490 	                                     kLocationPrinterIDKey)) != NULL &&
2491 	  CFGetTypeID(locprinter) == CFStringGetTypeID())
2492       {
2493         if (locindex)
2494 	  *locindex = i;
2495 
2496 	return (locprinter);
2497       }
2498     }
2499 
2500   return (NULL);
2501 }
2502 #endif /* _CUPS_LOCATION_DEFAULTS */
2503 
2504 
2505 /*
2506  * 'cups_add_dest()' - Add a destination to the array.
2507  *
2508  * Unlike cupsAddDest(), this function does not check for duplicates.
2509  */
2510 
2511 static cups_dest_t *			/* O  - New destination */
cups_add_dest(const char * name,const char * instance,int * num_dests,cups_dest_t ** dests)2512 cups_add_dest(const char  *name,	/* I  - Name of destination */
2513               const char  *instance,	/* I  - Instance or NULL */
2514               int         *num_dests,	/* IO - Number of destinations */
2515 	      cups_dest_t **dests)	/* IO - Destinations */
2516 {
2517   int		insert,			/* Insertion point */
2518 		diff;			/* Result of comparison */
2519   cups_dest_t	*dest;			/* Destination pointer */
2520 
2521 
2522  /*
2523   * Add new destination...
2524   */
2525 
2526   if (*num_dests == 0)
2527     dest = malloc(sizeof(cups_dest_t));
2528   else
2529     dest = realloc(*dests, sizeof(cups_dest_t) * (size_t)(*num_dests + 1));
2530 
2531   if (!dest)
2532     return (NULL);
2533 
2534   *dests = dest;
2535 
2536  /*
2537   * Find where to insert the destination...
2538   */
2539 
2540   if (*num_dests == 0)
2541     insert = 0;
2542   else
2543   {
2544     insert = cups_find_dest(name, instance, *num_dests, *dests, *num_dests - 1,
2545                             &diff);
2546 
2547     if (diff > 0)
2548       insert ++;
2549   }
2550 
2551  /*
2552   * Move the array elements as needed...
2553   */
2554 
2555   if (insert < *num_dests)
2556     memmove(*dests + insert + 1, *dests + insert, (size_t)(*num_dests - insert) * sizeof(cups_dest_t));
2557 
2558   (*num_dests) ++;
2559 
2560  /*
2561   * Initialize the destination...
2562   */
2563 
2564   dest              = *dests + insert;
2565   dest->name        = _cupsStrAlloc(name);
2566   dest->instance    = _cupsStrAlloc(instance);
2567   dest->is_default  = 0;
2568   dest->num_options = 0;
2569   dest->options     = (cups_option_t *)0;
2570 
2571   return (dest);
2572 }
2573 
2574 
2575 #  ifdef __BLOCKS__
2576 /*
2577  * 'cups_block_cb()' - Enumeration callback for block API.
2578  */
2579 
2580 static int				/* O - 1 to continue, 0 to stop */
cups_block_cb(cups_dest_block_t block,unsigned flags,cups_dest_t * dest)2581 cups_block_cb(
2582     cups_dest_block_t block,		/* I - Block */
2583     unsigned          flags,		/* I - Destination flags */
2584     cups_dest_t       *dest)		/* I - Destination */
2585 {
2586   return ((block)(flags, dest));
2587 }
2588 #  endif /* __BLOCKS__ */
2589 
2590 
2591 /*
2592  * 'cups_compare_dests()' - Compare two destinations.
2593  */
2594 
2595 static int				/* O - Result of comparison */
cups_compare_dests(cups_dest_t * a,cups_dest_t * b)2596 cups_compare_dests(cups_dest_t *a,	/* I - First destination */
2597                    cups_dest_t *b)	/* I - Second destination */
2598 {
2599   int	diff;				/* Difference */
2600 
2601 
2602   if ((diff = _cups_strcasecmp(a->name, b->name)) != 0)
2603     return (diff);
2604   else if (a->instance && b->instance)
2605     return (_cups_strcasecmp(a->instance, b->instance));
2606   else
2607     return ((a->instance && !b->instance) - (!a->instance && b->instance));
2608 }
2609 
2610 
2611 #ifdef HAVE_DNSSD
2612 #  ifdef HAVE_MDNSRESPONDER
2613 /*
2614  * 'cups_dnssd_browse_cb()' - Browse for printers.
2615  */
2616 
2617 static void
cups_dnssd_browse_cb(DNSServiceRef sdRef,DNSServiceFlags flags,uint32_t interfaceIndex,DNSServiceErrorType errorCode,const char * serviceName,const char * regtype,const char * replyDomain,void * context)2618 cups_dnssd_browse_cb(
2619     DNSServiceRef       sdRef,		/* I - Service reference */
2620     DNSServiceFlags     flags,		/* I - Option flags */
2621     uint32_t            interfaceIndex,	/* I - Interface number */
2622     DNSServiceErrorType errorCode,	/* I - Error, if any */
2623     const char          *serviceName,	/* I - Name of service/device */
2624     const char          *regtype,	/* I - Type of service */
2625     const char          *replyDomain,	/* I - Service domain */
2626     void                *context)	/* I - Enumeration data */
2627 {
2628   _cups_dnssd_data_t	*data = (_cups_dnssd_data_t *)context;
2629 					/* Enumeration data */
2630 
2631 
2632   DEBUG_printf(("5cups_dnssd_browse_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\", context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain, context));
2633 
2634  /*
2635   * Don't do anything on error...
2636   */
2637 
2638   if (errorCode != kDNSServiceErr_NoError)
2639     return;
2640 
2641  /*
2642   * Get the device...
2643   */
2644 
2645   cups_dnssd_get_device(data, serviceName, regtype, replyDomain);
2646 }
2647 
2648 
2649 #  else /* HAVE_AVAHI */
2650 /*
2651  * 'cups_dnssd_browse_cb()' - Browse for printers.
2652  */
2653 
2654 static void
cups_dnssd_browse_cb(AvahiServiceBrowser * browser,AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AvahiLookupResultFlags flags,void * context)2655 cups_dnssd_browse_cb(
2656     AvahiServiceBrowser    *browser,	/* I - Browser */
2657     AvahiIfIndex           interface,	/* I - Interface index (unused) */
2658     AvahiProtocol          protocol,	/* I - Network protocol (unused) */
2659     AvahiBrowserEvent      event,	/* I - What happened */
2660     const char             *name,	/* I - Service name */
2661     const char             *type,	/* I - Registration type */
2662     const char             *domain,	/* I - Domain */
2663     AvahiLookupResultFlags flags,	/* I - Flags */
2664     void                   *context)	/* I - Devices array */
2665 {
2666 #ifdef DEBUG
2667   AvahiClient		*client = avahi_service_browser_get_client(browser);
2668 					/* Client information */
2669 #endif /* DEBUG */
2670   _cups_dnssd_data_t	*data = (_cups_dnssd_data_t *)context;
2671 					/* Enumeration data */
2672 
2673 
2674   (void)interface;
2675   (void)protocol;
2676   (void)context;
2677 
2678   DEBUG_printf(("cups_dnssd_browse_cb(..., name=\"%s\", type=\"%s\", domain=\"%s\", ...);", name, type, domain));
2679 
2680   switch (event)
2681   {
2682     case AVAHI_BROWSER_FAILURE:
2683 	DEBUG_printf(("cups_dnssd_browse_cb: %s", avahi_strerror(avahi_client_errno(client))));
2684 	avahi_simple_poll_quit(data->simple_poll);
2685 	break;
2686 
2687     case AVAHI_BROWSER_NEW:
2688        /*
2689 	* This object is new on the network.
2690 	*/
2691 
2692 	cups_dnssd_get_device(data, name, type, domain);
2693 	break;
2694 
2695     case AVAHI_BROWSER_REMOVE :
2696     case AVAHI_BROWSER_CACHE_EXHAUSTED :
2697         break;
2698 
2699     case AVAHI_BROWSER_ALL_FOR_NOW :
2700         DEBUG_puts("cups_dnssd_browse_cb: ALL_FOR_NOW");
2701         data->browsers --;
2702         break;
2703   }
2704 }
2705 
2706 
2707 /*
2708  * 'cups_dnssd_client_cb()' - Avahi client callback function.
2709  */
2710 
2711 static void
cups_dnssd_client_cb(AvahiClient * client,AvahiClientState state,void * context)2712 cups_dnssd_client_cb(
2713     AvahiClient      *client,		/* I - Client information (unused) */
2714     AvahiClientState state,		/* I - Current state */
2715     void             *context)		/* I - User data (unused) */
2716 {
2717   _cups_dnssd_data_t	*data = (_cups_dnssd_data_t *)context;
2718 					/* Enumeration data */
2719 
2720 
2721   (void)client;
2722 
2723   DEBUG_printf(("cups_dnssd_client_cb(client=%p, state=%d, context=%p)", client, state, context));
2724 
2725  /*
2726   * If the connection drops, quit.
2727   */
2728 
2729   if (state == AVAHI_CLIENT_FAILURE)
2730   {
2731     DEBUG_puts("cups_dnssd_client_cb: Avahi connection failed.");
2732     avahi_simple_poll_quit(data->simple_poll);
2733   }
2734 }
2735 #  endif /* HAVE_MDNSRESPONDER */
2736 
2737 
2738 /*
2739  * 'cups_dnssd_compare_device()' - Compare two devices.
2740  */
2741 
2742 static int				/* O - Result of comparison */
cups_dnssd_compare_devices(_cups_dnssd_device_t * a,_cups_dnssd_device_t * b)2743 cups_dnssd_compare_devices(
2744     _cups_dnssd_device_t *a,		/* I - First device */
2745     _cups_dnssd_device_t *b)		/* I - Second device */
2746 {
2747   return (strcmp(a->dest.name, b->dest.name));
2748 }
2749 
2750 
2751 /*
2752  * 'cups_dnssd_free_device()' - Free the memory used by a device.
2753  */
2754 
2755 static void
cups_dnssd_free_device(_cups_dnssd_device_t * device,_cups_dnssd_data_t * data)2756 cups_dnssd_free_device(
2757     _cups_dnssd_device_t *device,	/* I - Device */
2758     _cups_dnssd_data_t   *data)		/* I - Enumeration data */
2759 {
2760   DEBUG_printf(("5cups_dnssd_free_device(device=%p(%s), data=%p)", (void *)device, device->dest.name, (void *)data));
2761 
2762 #  ifdef HAVE_MDNSRESPONDER
2763   if (device->ref)
2764     DNSServiceRefDeallocate(device->ref);
2765 #  else /* HAVE_AVAHI */
2766   if (device->ref)
2767     avahi_record_browser_free(device->ref);
2768 #  endif /* HAVE_MDNSRESPONDER */
2769 
2770   _cupsStrFree(device->domain);
2771   _cupsStrFree(device->fullName);
2772   _cupsStrFree(device->regtype);
2773   _cupsStrFree(device->dest.name);
2774 
2775   cupsFreeOptions(device->dest.num_options, device->dest.options);
2776 
2777   free(device);
2778 }
2779 
2780 
2781 /*
2782  * 'cups_dnssd_get_device()' - Lookup a device and create it as needed.
2783  */
2784 
2785 static _cups_dnssd_device_t *		/* O - Device */
cups_dnssd_get_device(_cups_dnssd_data_t * data,const char * serviceName,const char * regtype,const char * replyDomain)2786 cups_dnssd_get_device(
2787     _cups_dnssd_data_t *data,		/* I - Enumeration data */
2788     const char         *serviceName,	/* I - Service name */
2789     const char         *regtype,	/* I - Registration type */
2790     const char         *replyDomain)	/* I - Domain name */
2791 {
2792   _cups_dnssd_device_t	key,		/* Search key */
2793 			*device;	/* Device */
2794   char			fullName[kDNSServiceMaxDomainName],
2795 					/* Full name for query */
2796 			name[128];	/* Queue name */
2797 
2798 
2799   DEBUG_printf(("5cups_dnssd_get_device(data=%p, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\")", (void *)data, serviceName, regtype, replyDomain));
2800 
2801  /*
2802   * See if this is an existing device...
2803   */
2804 
2805   cups_queue_name(name, serviceName, sizeof(name));
2806 
2807   key.dest.name = name;
2808 
2809   if ((device = cupsArrayFind(data->devices, &key)) != NULL)
2810   {
2811    /*
2812     * Yes, see if we need to do anything with this...
2813     */
2814 
2815     int	update = 0;			/* Non-zero if we need to update */
2816 
2817     if (!_cups_strcasecmp(replyDomain, "local.") &&
2818 	_cups_strcasecmp(device->domain, replyDomain))
2819     {
2820      /*
2821       * Update the "global" listing to use the .local domain name instead.
2822       */
2823 
2824       _cupsStrFree(device->domain);
2825       device->domain = _cupsStrAlloc(replyDomain);
2826 
2827       DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use local "
2828                     "domain.", device->dest.name));
2829 
2830       update = 1;
2831     }
2832 
2833     if (!_cups_strcasecmp(regtype, "_ipps._tcp") &&
2834 	_cups_strcasecmp(device->regtype, regtype))
2835     {
2836      /*
2837       * Prefer IPPS over IPP.
2838       */
2839 
2840       _cupsStrFree(device->regtype);
2841       device->regtype = _cupsStrAlloc(regtype);
2842 
2843       DEBUG_printf(("6cups_dnssd_get_device: Updating '%s' to use IPPS.",
2844 		    device->dest.name));
2845 
2846       update = 1;
2847     }
2848 
2849     if (!update)
2850     {
2851       DEBUG_printf(("6cups_dnssd_get_device: No changes to '%s'.",
2852                     device->dest.name));
2853       return (device);
2854     }
2855   }
2856   else
2857   {
2858    /*
2859     * No, add the device...
2860     */
2861 
2862     DEBUG_printf(("6cups_dnssd_get_device: Adding '%s' for %s with domain "
2863                   "'%s'.", serviceName,
2864                   !strcmp(regtype, "_ipps._tcp") ? "IPPS" : "IPP",
2865                   replyDomain));
2866 
2867     if ((device = calloc(1, sizeof(_cups_dnssd_device_t))) == NULL)
2868       return (NULL);
2869 
2870     device->dest.name = _cupsStrAlloc(name);
2871     device->domain    = _cupsStrAlloc(replyDomain);
2872     device->regtype   = _cupsStrAlloc(regtype);
2873 
2874     device->dest.num_options = cupsAddOption("printer-info", serviceName, 0, &device->dest.options);
2875 
2876     cupsArrayAdd(data->devices, device);
2877   }
2878 
2879  /*
2880   * Set the "full name" of this service, which is used for queries...
2881   */
2882 
2883 #  ifdef HAVE_MDNSRESPONDER
2884   DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
2885 #  else /* HAVE_AVAHI */
2886   avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, regtype, replyDomain);
2887 #  endif /* HAVE_MDNSRESPONDER */
2888 
2889   _cupsStrFree(device->fullName);
2890   device->fullName = _cupsStrAlloc(fullName);
2891 
2892   if (device->ref)
2893   {
2894 #  ifdef HAVE_MDNSRESPONDER
2895     DNSServiceRefDeallocate(device->ref);
2896 #  else /* HAVE_AVAHI */
2897     avahi_record_browser_free(device->ref);
2898 #  endif /* HAVE_MDNSRESPONDER */
2899 
2900     device->ref = 0;
2901   }
2902 
2903   if (device->state == _CUPS_DNSSD_ACTIVE)
2904   {
2905     DEBUG_printf(("6cups_dnssd_get_device: Remove callback for \"%s\".", device->dest.name));
2906 
2907     (*data->cb)(data->user_data, CUPS_DEST_FLAGS_REMOVED, &device->dest);
2908     device->state = _CUPS_DNSSD_NEW;
2909   }
2910 
2911   return (device);
2912 }
2913 
2914 
2915 #  ifdef HAVE_AVAHI
2916 /*
2917  * 'cups_dnssd_poll_cb()' - Wait for input on the specified file descriptors.
2918  *
2919  * Note: This function is needed because avahi_simple_poll_iterate is broken
2920  *       and always uses a timeout of 0 (!) milliseconds.
2921  *       (https://github.com/lathiat/avahi/issues/127)
2922  *
2923  * @private@
2924  */
2925 
2926 static int				/* O - Number of file descriptors matching */
cups_dnssd_poll_cb(struct pollfd * pollfds,unsigned int num_pollfds,int timeout,void * context)2927 cups_dnssd_poll_cb(
2928     struct pollfd *pollfds,		/* I - File descriptors */
2929     unsigned int  num_pollfds,		/* I - Number of file descriptors */
2930     int           timeout,		/* I - Timeout in milliseconds (unused) */
2931     void          *context)		/* I - User data (unused) */
2932 {
2933   _cups_dnssd_data_t	*data = (_cups_dnssd_data_t *)context;
2934 					/* Enumeration data */
2935   int			val;		/* Return value */
2936 
2937 
2938   DEBUG_printf(("cups_dnssd_poll_cb(pollfds=%p, num_pollfds=%d, timeout=%d, context=%p)", pollfds, num_pollfds, timeout, context));
2939 
2940   (void)timeout;
2941 
2942   val = poll(pollfds, num_pollfds, _CUPS_DNSSD_MAXTIME);
2943 
2944   DEBUG_printf(("cups_dnssd_poll_cb: poll() returned %d", val));
2945 
2946   if (val < 0)
2947   {
2948     DEBUG_printf(("cups_dnssd_poll_cb: %s", strerror(errno)));
2949   }
2950   else if (val > 0)
2951   {
2952     data->got_data = 1;
2953   }
2954 
2955   return (val);
2956 }
2957 #  endif /* HAVE_AVAHI */
2958 
2959 
2960 /*
2961  * 'cups_dnssd_query_cb()' - Process query data.
2962  */
2963 
2964 static void
cups_dnssd_query_cb(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)2965 cups_dnssd_query_cb(
2966 #  ifdef HAVE_MDNSRESPONDER
2967     DNSServiceRef       sdRef,		/* I - Service reference */
2968     DNSServiceFlags     flags,		/* I - Data flags */
2969     uint32_t            interfaceIndex,	/* I - Interface */
2970     DNSServiceErrorType errorCode,	/* I - Error, if any */
2971     const char          *fullName,	/* I - Full service name */
2972     uint16_t            rrtype,		/* I - Record type */
2973     uint16_t            rrclass,	/* I - Record class */
2974     uint16_t            rdlen,		/* I - Length of record data */
2975     const void          *rdata,		/* I - Record data */
2976     uint32_t            ttl,		/* I - Time-to-live */
2977 #  else /* HAVE_AVAHI */
2978     AvahiRecordBrowser     *browser,	/* I - Record browser */
2979     AvahiIfIndex           interfaceIndex,
2980 					/* I - Interface index (unused) */
2981     AvahiProtocol          protocol,	/* I - Network protocol (unused) */
2982     AvahiBrowserEvent      event,	/* I - What happened? */
2983     const char             *fullName,	/* I - Service name */
2984     uint16_t               rrclass,	/* I - Record class */
2985     uint16_t               rrtype,	/* I - Record type */
2986     const void             *rdata,	/* I - TXT record */
2987     size_t                 rdlen,	/* I - Length of TXT record */
2988     AvahiLookupResultFlags flags,	/* I - Flags */
2989 #  endif /* HAVE_MDNSRESPONDER */
2990     void                *context)	/* I - Enumeration data */
2991 {
2992 #  if defined(DEBUG) && defined(HAVE_AVAHI)
2993   AvahiClient		*client = avahi_record_browser_get_client(browser);
2994 					/* Client information */
2995 #  endif /* DEBUG && HAVE_AVAHI */
2996   _cups_dnssd_data_t	*data = (_cups_dnssd_data_t *)context;
2997 					/* Enumeration data */
2998   char			serviceName[256],/* Service name */
2999 			name[128],	/* Queue name */
3000 			*ptr;		/* Pointer into string */
3001   _cups_dnssd_device_t	dkey,		/* Search key */
3002 			*device;	/* Device */
3003 
3004 
3005 #  ifdef HAVE_MDNSRESPONDER
3006   DEBUG_printf(("5cups_dnssd_query_cb(sdRef=%p, flags=%x, interfaceIndex=%d, errorCode=%d, fullName=\"%s\", rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, context=%p)", (void *)sdRef, flags, interfaceIndex, errorCode, fullName, rrtype, rrclass, rdlen, rdata, ttl, context));
3007 
3008  /*
3009   * Only process "add" data...
3010   */
3011 
3012   if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
3013     return;
3014 
3015 #  else /* HAVE_AVAHI */
3016   DEBUG_printf(("cups_dnssd_query_cb(browser=%p, interfaceIndex=%d, protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)", browser, interfaceIndex, protocol, event, fullName, rrclass, rrtype, rdata, (unsigned)rdlen, flags, context));
3017 
3018  /*
3019   * Only process "add" data...
3020   */
3021 
3022   if (event != AVAHI_BROWSER_NEW)
3023   {
3024     if (event == AVAHI_BROWSER_FAILURE)
3025       DEBUG_printf(("cups_dnssd_query_cb: %s", avahi_strerror(avahi_client_errno(client))));
3026 
3027     return;
3028   }
3029 #  endif /* HAVE_MDNSRESPONDER */
3030 
3031  /*
3032   * Lookup the service in the devices array.
3033   */
3034 
3035   cups_dnssd_unquote(serviceName, fullName, sizeof(serviceName));
3036 
3037   if ((ptr = strstr(serviceName, "._")) != NULL)
3038     *ptr = '\0';
3039 
3040   cups_queue_name(name, serviceName, sizeof(name));
3041 
3042   dkey.dest.name = name;
3043 
3044   if ((device = cupsArrayFind(data->devices, &dkey)) != NULL && device->state == _CUPS_DNSSD_NEW)
3045   {
3046    /*
3047     * Found it, pull out the make and model from the TXT record and save it...
3048     */
3049 
3050     const uint8_t	*txt,		/* Pointer into data */
3051 			*txtnext,	/* Next key/value pair */
3052 			*txtend;	/* End of entire TXT record */
3053     uint8_t		txtlen;		/* Length of current key/value pair */
3054     char		key[256],	/* Key string */
3055 			value[256],	/* Value string */
3056 			make_and_model[512],
3057 					/* Manufacturer and model */
3058 			model[256],	/* Model */
3059 			uriname[1024],	/* Name for URI */
3060 			uri[1024];	/* Printer URI */
3061     cups_ptype_t	type = CUPS_PRINTER_DISCOVERED | CUPS_PRINTER_BW;
3062 					/* Printer type */
3063     int			saw_printer_type = 0;
3064 					/* Did we see a printer-type key? */
3065 
3066     device->state     = _CUPS_DNSSD_PENDING;
3067     make_and_model[0] = '\0';
3068 
3069     strlcpy(model, "Unknown", sizeof(model));
3070 
3071     for (txt = rdata, txtend = txt + rdlen;
3072 	 txt < txtend;
3073 	 txt = txtnext)
3074     {
3075      /*
3076       * Read a key/value pair starting with an 8-bit length.  Since the
3077       * length is 8 bits and the size of the key/value buffers is 256, we
3078       * don't need to check for overflow...
3079       */
3080 
3081       txtlen = *txt++;
3082 
3083       if (!txtlen || (txt + txtlen) > txtend)
3084 	break;
3085 
3086       txtnext = txt + txtlen;
3087 
3088       for (ptr = key; txt < txtnext && *txt != '='; txt ++)
3089 	*ptr++ = (char)*txt;
3090       *ptr = '\0';
3091 
3092       if (txt < txtnext && *txt == '=')
3093       {
3094 	txt ++;
3095 
3096 	if (txt < txtnext)
3097 	  memcpy(value, txt, (size_t)(txtnext - txt));
3098 	value[txtnext - txt] = '\0';
3099 
3100 	DEBUG_printf(("6cups_dnssd_query_cb: %s=%s", key, value));
3101       }
3102       else
3103       {
3104 	DEBUG_printf(("6cups_dnssd_query_cb: '%s' with no value.", key));
3105 	continue;
3106       }
3107 
3108       if (!_cups_strcasecmp(key, "usb_MFG") ||
3109           !_cups_strcasecmp(key, "usb_MANU") ||
3110 	  !_cups_strcasecmp(key, "usb_MANUFACTURER"))
3111 	strlcpy(make_and_model, value, sizeof(make_and_model));
3112       else if (!_cups_strcasecmp(key, "usb_MDL") ||
3113                !_cups_strcasecmp(key, "usb_MODEL"))
3114 	strlcpy(model, value, sizeof(model));
3115       else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
3116       {
3117 	if (value[0] == '(')
3118 	{
3119 	 /*
3120 	  * Strip parenthesis...
3121 	  */
3122 
3123 	  if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
3124 	    *ptr = '\0';
3125 
3126 	  strlcpy(model, value + 1, sizeof(model));
3127 	}
3128 	else
3129 	  strlcpy(model, value, sizeof(model));
3130       }
3131       else if (!_cups_strcasecmp(key, "ty"))
3132       {
3133 	strlcpy(model, value, sizeof(model));
3134 
3135 	if ((ptr = strchr(model, ',')) != NULL)
3136 	  *ptr = '\0';
3137       }
3138       else if (!_cups_strcasecmp(key, "note"))
3139         device->dest.num_options = cupsAddOption("printer-location", value,
3140 						 device->dest.num_options,
3141 						 &device->dest.options);
3142       else if (!_cups_strcasecmp(key, "pdl"))
3143       {
3144        /*
3145         * Look for PDF-capable printers; only PDF-capable printers are shown.
3146         */
3147 
3148         const char	*start, *next;	/* Pointer into value */
3149         int		have_pdf = 0,	/* Have PDF? */
3150 			have_raster = 0;/* Have raster format support? */
3151 
3152         for (start = value; start && *start; start = next)
3153         {
3154           if (!_cups_strncasecmp(start, "application/pdf", 15) && (!start[15] || start[15] == ','))
3155           {
3156             have_pdf = 1;
3157             break;
3158           }
3159           else if ((!_cups_strncasecmp(start, "image/pwg-raster", 16) && (!start[16] || start[16] == ',')) ||
3160 		   (!_cups_strncasecmp(start, "image/urf", 9) && (!start[9] || start[9] == ',')))
3161           {
3162             have_raster = 1;
3163             break;
3164           }
3165 
3166           if ((next = strchr(start, ',')) != NULL)
3167             next ++;
3168         }
3169 
3170         if (!have_pdf && !have_raster)
3171           device->state = _CUPS_DNSSD_INCOMPATIBLE;
3172       }
3173       else if (!_cups_strcasecmp(key, "printer-type"))
3174       {
3175        /*
3176         * Value is either NNNN or 0xXXXX
3177         */
3178 
3179 	saw_printer_type = 1;
3180         type             = (cups_ptype_t)strtol(value, NULL, 0) | CUPS_PRINTER_DISCOVERED;
3181       }
3182       else if (!saw_printer_type)
3183       {
3184 	if (!_cups_strcasecmp(key, "air") && _cups_strcasecmp(value, "none"))
3185 	  type |= CUPS_PRINTER_AUTHENTICATED;
3186 	else if (!_cups_strcasecmp(key, "bind") &&
3187 		 !_cups_strcasecmp(value, "t"))
3188 	  type |= CUPS_PRINTER_BIND;
3189 	else if (!_cups_strcasecmp(key, "collate") &&
3190 		 !_cups_strcasecmp(value, "t"))
3191 	  type |= CUPS_PRINTER_COLLATE;
3192 	else if (!_cups_strcasecmp(key, "color") &&
3193 		 !_cups_strcasecmp(value, "t"))
3194 	  type |= CUPS_PRINTER_COLOR;
3195 	else if (!_cups_strcasecmp(key, "copies") &&
3196 		 !_cups_strcasecmp(value, "t"))
3197 	  type |= CUPS_PRINTER_COPIES;
3198 	else if (!_cups_strcasecmp(key, "duplex") &&
3199 		 !_cups_strcasecmp(value, "t"))
3200 	  type |= CUPS_PRINTER_DUPLEX;
3201 	else if (!_cups_strcasecmp(key, "fax") &&
3202 		 !_cups_strcasecmp(value, "t"))
3203 	  type |= CUPS_PRINTER_MFP;
3204 	else if (!_cups_strcasecmp(key, "papercustom") &&
3205 		 !_cups_strcasecmp(value, "t"))
3206 	  type |= CUPS_PRINTER_VARIABLE;
3207 	else if (!_cups_strcasecmp(key, "papermax"))
3208 	{
3209 	  if (!_cups_strcasecmp(value, "legal-a4"))
3210 	    type |= CUPS_PRINTER_SMALL;
3211 	  else if (!_cups_strcasecmp(value, "isoc-a2"))
3212 	    type |= CUPS_PRINTER_MEDIUM;
3213 	  else if (!_cups_strcasecmp(value, ">isoc-a2"))
3214 	    type |= CUPS_PRINTER_LARGE;
3215 	}
3216 	else if (!_cups_strcasecmp(key, "punch") &&
3217 		 !_cups_strcasecmp(value, "t"))
3218 	  type |= CUPS_PRINTER_PUNCH;
3219 	else if (!_cups_strcasecmp(key, "scan") &&
3220 		 !_cups_strcasecmp(value, "t"))
3221 	  type |= CUPS_PRINTER_MFP;
3222 	else if (!_cups_strcasecmp(key, "sort") &&
3223 		 !_cups_strcasecmp(value, "t"))
3224 	  type |= CUPS_PRINTER_SORT;
3225 	else if (!_cups_strcasecmp(key, "staple") &&
3226 		 !_cups_strcasecmp(value, "t"))
3227 	  type |= CUPS_PRINTER_STAPLE;
3228       }
3229     }
3230 
3231    /*
3232     * Save the printer-xxx values...
3233     */
3234 
3235     if (make_and_model[0])
3236     {
3237       strlcat(make_and_model, " ", sizeof(make_and_model));
3238       strlcat(make_and_model, model, sizeof(make_and_model));
3239 
3240       device->dest.num_options = cupsAddOption("printer-make-and-model", make_and_model, device->dest.num_options, &device->dest.options);
3241     }
3242     else
3243       device->dest.num_options = cupsAddOption("printer-make-and-model", model, device->dest.num_options, &device->dest.options);
3244 
3245     device->type = type;
3246     snprintf(value, sizeof(value), "%u", type);
3247     device->dest.num_options = cupsAddOption("printer-type", value, device->dest.num_options, &device->dest.options);
3248 
3249    /*
3250     * Save the URI...
3251     */
3252 
3253     cups_dnssd_unquote(uriname, device->fullName, sizeof(uriname));
3254     httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri),
3255                     !strcmp(device->regtype, "_ipps._tcp") ? "ipps" : "ipp",
3256                     NULL, uriname, 0, saw_printer_type ? "/cups" : "/");
3257 
3258     DEBUG_printf(("6cups_dnssd_query: device-uri=\"%s\"", uri));
3259 
3260     device->dest.num_options = cupsAddOption("device-uri", uri, device->dest.num_options, &device->dest.options);
3261   }
3262   else
3263     DEBUG_printf(("6cups_dnssd_query: Ignoring TXT record for '%s'.",
3264                   fullName));
3265 }
3266 
3267 
3268 /*
3269  * 'cups_dnssd_resolve()' - Resolve a Bonjour printer URI.
3270  */
3271 
3272 static const char *			/* O - Resolved URI or NULL */
cups_dnssd_resolve(cups_dest_t * dest,const char * uri,int msec,int * cancel,cups_dest_cb_t cb,void * user_data)3273 cups_dnssd_resolve(
3274     cups_dest_t    *dest,		/* I - Destination */
3275     const char     *uri,		/* I - Current printer URI */
3276     int            msec,		/* I - Time in milliseconds */
3277     int            *cancel,		/* I - Pointer to "cancel" variable */
3278     cups_dest_cb_t cb,			/* I - Callback */
3279     void           *user_data)		/* I - User data for callback */
3280 {
3281   char			tempuri[1024];	/* Temporary URI buffer */
3282   _cups_dnssd_resolve_t	resolve;	/* Resolve data */
3283 
3284 
3285  /*
3286   * Resolve the URI...
3287   */
3288 
3289   resolve.cancel   = cancel;
3290   resolve.end_time = _cupsGetClock();
3291   if (msec > 0)
3292     resolve.end_time += 0.001 * msec;
3293   else
3294     resolve.end_time += 75.0;
3295 
3296   if (cb)
3297     (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_RESOLVING, dest);
3298 
3299   if ((uri = _httpResolveURI(uri, tempuri, sizeof(tempuri), _HTTP_RESOLVE_DEFAULT, cups_dnssd_resolve_cb, &resolve)) == NULL)
3300   {
3301     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to resolve printer-uri."), 1);
3302 
3303     if (cb)
3304       (*cb)(user_data, CUPS_DEST_FLAGS_UNCONNECTED | CUPS_DEST_FLAGS_ERROR, dest);
3305 
3306     return (NULL);
3307   }
3308 
3309  /*
3310   * Save the resolved URI...
3311   */
3312 
3313   dest->num_options = cupsAddOption("device-uri", uri, dest->num_options, &dest->options);
3314 
3315   return (cupsGetOption("device-uri", dest->num_options, dest->options));
3316 }
3317 
3318 
3319 /*
3320  * 'cups_dnssd_resolve_cb()' - See if we should continue resolving.
3321  */
3322 
3323 static int				/* O - 1 to continue, 0 to stop */
cups_dnssd_resolve_cb(void * context)3324 cups_dnssd_resolve_cb(void *context)	/* I - Resolve data */
3325 {
3326   _cups_dnssd_resolve_t	*resolve = (_cups_dnssd_resolve_t *)context;
3327 					/* Resolve data */
3328   double	curtime;		/* Current time */
3329 
3330 
3331  /*
3332   * If the cancel variable is set, return immediately.
3333   */
3334 
3335   if (resolve->cancel && *(resolve->cancel))
3336   {
3337     DEBUG_puts("4cups_dnssd_resolve_cb: Canceled.");
3338     return (0);
3339   }
3340 
3341  /*
3342   * Otherwise check the end time...
3343   */
3344 
3345   curtime = _cupsGetClock();
3346 
3347   DEBUG_printf(("4cups_dnssd_resolve_cb: curtime=%.6f, end_time=%.6f", curtime, resolve->end_time));
3348 
3349   return (curtime < resolve->end_time);
3350 }
3351 
3352 
3353 /*
3354  * 'cups_dnssd_unquote()' - Unquote a name string.
3355  */
3356 
3357 static void
cups_dnssd_unquote(char * dst,const char * src,size_t dstsize)3358 cups_dnssd_unquote(char       *dst,	/* I - Destination buffer */
3359                    const char *src,	/* I - Source string */
3360 		   size_t     dstsize)	/* I - Size of destination buffer */
3361 {
3362   char	*dstend = dst + dstsize - 1;	/* End of destination buffer */
3363 
3364 
3365   while (*src && dst < dstend)
3366   {
3367     if (*src == '\\')
3368     {
3369       src ++;
3370       if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
3371           isdigit(src[2] & 255))
3372       {
3373         *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
3374 	src += 3;
3375       }
3376       else
3377         *dst++ = *src++;
3378     }
3379     else
3380       *dst++ = *src ++;
3381   }
3382 
3383   *dst = '\0';
3384 }
3385 
3386 
3387 /*
3388  * 'cups_elapsed()' - Return the elapsed time in milliseconds.
3389  */
3390 
3391 static int				/* O  - Elapsed time in milliseconds */
cups_elapsed(double * t)3392 cups_elapsed(double *t)			/* IO - Previous time */
3393 {
3394   int		msecs;			/* Milliseconds */
3395   double	nt;			/* New time */
3396 
3397 
3398   nt = _cupsGetClock();
3399 
3400   msecs = (int)(1000.0 * (nt - *t));
3401 
3402   *t = nt;
3403 
3404   return (msecs);
3405 }
3406 #endif /* HAVE_DNSSD */
3407 
3408 
3409 /*
3410  * 'cups_enum_dests()' - Enumerate destinations from a specific server.
3411  */
3412 
3413 static int                              /* O - 1 on success, 0 on failure */
cups_enum_dests(http_t * http,unsigned flags,int msec,int * cancel,cups_ptype_t type,cups_ptype_t mask,cups_dest_cb_t cb,void * user_data)3414 cups_enum_dests(
3415   http_t         *http,                 /* I - Connection to scheduler */
3416   unsigned       flags,                 /* I - Enumeration flags */
3417   int            msec,                  /* I - Timeout in milliseconds, -1 for indefinite */
3418   int            *cancel,               /* I - Pointer to "cancel" variable */
3419   cups_ptype_t   type,                  /* I - Printer type bits */
3420   cups_ptype_t   mask,                  /* I - Mask for printer type bits */
3421   cups_dest_cb_t cb,                    /* I - Callback function */
3422   void           *user_data)            /* I - User data */
3423 {
3424   int           i, j, k,		/* Looping vars */
3425                 num_dests;              /* Number of destinations */
3426   cups_dest_t   *dests = NULL,          /* Destinations */
3427                 *dest;			/* Current destination */
3428   cups_option_t	*option;		/* Current option */
3429   const char	*user_default;		/* Default printer from environment */
3430 #ifdef HAVE_DNSSD
3431   int           count,                  /* Number of queries started */
3432                 completed,              /* Number of completed queries */
3433                 remaining;              /* Remainder of timeout */
3434   double	curtime;		/* Current time */
3435   _cups_dnssd_data_t data;		/* Data for callback */
3436   _cups_dnssd_device_t *device;         /* Current device */
3437 #  ifdef HAVE_MDNSRESPONDER
3438   int           nfds,                   /* Number of files responded */
3439                 main_fd;                /* File descriptor for lookups */
3440   DNSServiceRef ipp_ref = NULL;		/* IPP browser */
3441 #    ifdef HAVE_TLS
3442   DNSServiceRef ipps_ref = NULL;	/* IPPS browser */
3443 #    endif /* HAVE_TLS */
3444 #    ifdef HAVE_POLL
3445   struct pollfd pfd;                    /* Polling data */
3446 #    else
3447   fd_set        input;                  /* Input set for select() */
3448   struct timeval timeout;               /* Timeout for select() */
3449 #    endif /* HAVE_POLL */
3450 #  else /* HAVE_AVAHI */
3451   int           error;                  /* Error value */
3452   AvahiServiceBrowser *ipp_ref = NULL;  /* IPP browser */
3453 #    ifdef HAVE_TLS
3454   AvahiServiceBrowser *ipps_ref = NULL; /* IPPS browser */
3455 #    endif /* HAVE_TLS */
3456 #  endif /* HAVE_MDNSRESPONDER */
3457 #else
3458   _cups_getdata_t data;			/* Data for callback */
3459 #endif /* HAVE_DNSSD */
3460   char		filename[1024];		/* Local lpoptions file */
3461   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
3462 
3463 
3464   DEBUG_printf(("cups_enum_dests(flags=%x, msec=%d, cancel=%p, type=%x, mask=%x, cb=%p, user_data=%p)", flags, msec, (void *)cancel, type, mask, (void *)cb, (void *)user_data));
3465 
3466  /*
3467   * Range check input...
3468   */
3469 
3470   (void)flags;
3471 
3472   if (!cb)
3473   {
3474     DEBUG_puts("1cups_enum_dests: No callback, returning 0.");
3475     return (0);
3476   }
3477 
3478  /*
3479   * Load the /etc/cups/lpoptions and ~/.cups/lpoptions files...
3480   */
3481 
3482   memset(&data, 0, sizeof(data));
3483 
3484   user_default = _cupsUserDefault(data.def_name, sizeof(data.def_name));
3485 
3486   snprintf(filename, sizeof(filename), "%s/lpoptions", cg->cups_serverroot);
3487   data.num_dests = cups_get_dests(filename, NULL, NULL, 1, user_default != NULL, data.num_dests, &data.dests);
3488 
3489 #if _WIN32
3490   if (cg->home)
3491 #else
3492   if (cg->home && getuid() != 0)
3493 #endif // _WIN32
3494   {
3495 #if _WIN32
3496     snprintf(filename, sizeof(filename), "%s/AppData/Local/cups/lpoptions", cg->home);
3497 #else
3498     snprintf(filename, sizeof(filename), "%s/.cups/lpoptions", cg->home);
3499 #endif // _WIN32
3500 
3501     data.num_dests = cups_get_dests(filename, NULL, NULL, 1, user_default != NULL, data.num_dests, &data.dests);
3502   }
3503 
3504   if (!user_default && (dest = cupsGetDest(NULL, NULL, data.num_dests, data.dests)) != NULL)
3505   {
3506    /*
3507     * Use an lpoptions default printer...
3508     */
3509 
3510     if (dest->instance)
3511       snprintf(data.def_name, sizeof(data.def_name), "%s/%s", dest->name, dest->instance);
3512     else
3513       strlcpy(data.def_name, dest->name, sizeof(data.def_name));
3514   }
3515   else
3516   {
3517     const char	*default_printer;	/* Server default printer */
3518 
3519     if ((default_printer = cupsGetDefault2(http)) != NULL)
3520       strlcpy(data.def_name, default_printer, sizeof(data.def_name));
3521   }
3522 
3523   if (data.def_name[0])
3524   {
3525    /*
3526     * Separate printer and instance name...
3527     */
3528 
3529     if ((data.def_instance = strchr(data.def_name, '/')) != NULL)
3530       *data.def_instance++ = '\0';
3531   }
3532 
3533   DEBUG_printf(("1cups_enum_dests: def_name=\"%s\", def_instance=\"%s\"", data.def_name, data.def_instance));
3534 
3535  /*
3536   * Get ready to enumerate...
3537   */
3538 
3539 #ifdef HAVE_DNSSD
3540   data.type      = type;
3541   data.mask      = mask;
3542   data.cb        = cb;
3543   data.user_data = user_data;
3544   data.devices   = cupsArrayNew3((cups_array_func_t)cups_dnssd_compare_devices, NULL, NULL, 0, NULL, (cups_afree_func_t)cups_dnssd_free_device);
3545 #endif /* HAVE_DNSSD */
3546 
3547   if (!(mask & CUPS_PRINTER_DISCOVERED) || !(type & CUPS_PRINTER_DISCOVERED))
3548   {
3549    /*
3550     * Get the list of local printers and pass them to the callback function...
3551     */
3552 
3553     num_dests = _cupsGetDests(http, IPP_OP_CUPS_GET_PRINTERS, NULL, &dests, type, mask);
3554 
3555     if (data.def_name[0])
3556     {
3557      /*
3558       * Lookup the named default printer and instance and make it the default...
3559       */
3560 
3561       if ((dest = cupsGetDest(data.def_name, data.def_instance, num_dests, dests)) != NULL)
3562       {
3563 	DEBUG_printf(("1cups_enum_dests: Setting is_default on \"%s/%s\".", dest->name, dest->instance));
3564         dest->is_default = 1;
3565       }
3566     }
3567 
3568     for (i = num_dests, dest = dests;
3569          i > 0 && (!cancel || !*cancel);
3570          i --, dest ++)
3571     {
3572       cups_dest_t	*user_dest;	/* Destination from lpoptions */
3573 #ifdef HAVE_DNSSD
3574       const char	*device_uri;	/* Device URI */
3575 #endif /* HAVE_DNSSD */
3576 
3577       if ((user_dest = cupsGetDest(dest->name, NULL, data.num_dests, data.dests)) != NULL)
3578       {
3579        /*
3580         * Apply user defaults to this destination for all instances...
3581         */
3582 
3583         for (j = user_dest - data.dests; j < data.num_dests; j ++, user_dest ++)
3584         {
3585           if (_cups_strcasecmp(user_dest->name, dest->name))
3586           {
3587             j = data.num_dests;
3588             break;
3589           }
3590 
3591 	  for (k = dest->num_options, option = dest->options; k > 0; k --, option ++)
3592 	  {
3593 	    if (!cupsGetOption(option->name, user_dest->num_options, user_dest->options))
3594 	      user_dest->num_options = cupsAddOption(option->name, option->value, user_dest->num_options, &user_dest->options);
3595 	  }
3596 
3597           if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, user_dest))
3598             break;
3599         }
3600 
3601         if (j < data.num_dests)
3602           break;
3603       }
3604       else if (!(*cb)(user_data, i > 1 ? CUPS_DEST_FLAGS_MORE : CUPS_DEST_FLAGS_NONE, dest))
3605         break;
3606 
3607 #ifdef HAVE_DNSSD
3608       if (!dest->instance && (device_uri = cupsGetOption("device-uri", dest->num_options, dest->options)) != NULL && !strncmp(device_uri, "dnssd://", 8))
3609       {
3610        /*
3611         * Add existing queue using service name, etc. so we don't list it again...
3612         */
3613 
3614         char    scheme[32],             /* URI scheme */
3615                 userpass[32],           /* Username:password */
3616                 serviceName[256],       /* Service name (host field) */
3617                 resource[256],          /* Resource (options) */
3618                 *regtype,               /* Registration type */
3619                 *replyDomain;           /* Registration domain */
3620         int     port;                   /* Port number (not used) */
3621 
3622         if (httpSeparateURI(HTTP_URI_CODING_ALL, device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), serviceName, sizeof(serviceName), &port, resource, sizeof(resource)) >= HTTP_URI_STATUS_OK)
3623         {
3624           if ((regtype = strstr(serviceName, "._ipp")) != NULL)
3625           {
3626             *regtype++ = '\0';
3627 
3628             if ((replyDomain = strstr(regtype, "._tcp.")) != NULL)
3629             {
3630               replyDomain[5] = '\0';
3631               replyDomain += 6;
3632 
3633               if ((device = cups_dnssd_get_device(&data, serviceName, regtype, replyDomain)) != NULL)
3634                 device->state = _CUPS_DNSSD_ACTIVE;
3635             }
3636           }
3637         }
3638       }
3639 #endif /* HAVE_DNSSD */
3640     }
3641 
3642     cupsFreeDests(num_dests, dests);
3643 
3644     if (i > 0 || msec == 0)
3645       goto enum_finished;
3646   }
3647 
3648  /*
3649   * Return early if the caller doesn't want to do discovery...
3650   */
3651 
3652   if ((mask & CUPS_PRINTER_DISCOVERED) && !(type & CUPS_PRINTER_DISCOVERED))
3653     goto enum_finished;
3654 
3655 #ifdef HAVE_DNSSD
3656  /*
3657   * Get Bonjour-shared printers...
3658   */
3659 
3660   curtime = _cupsGetClock();
3661 
3662 #  ifdef HAVE_MDNSRESPONDER
3663   if (DNSServiceCreateConnection(&data.main_ref) != kDNSServiceErr_NoError)
3664   {
3665     DEBUG_puts("1cups_enum_dests: Unable to create service browser, returning 0.");
3666 
3667     cupsFreeDests(data.num_dests, data.dests);
3668     cupsArrayDelete(data.devices);
3669 
3670     return (0);
3671   }
3672 
3673   main_fd = DNSServiceRefSockFD(data.main_ref);
3674 
3675   ipp_ref = data.main_ref;
3676   if (DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, "_ipp._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError)
3677   {
3678     DEBUG_puts("1cups_enum_dests: Unable to create IPP browser, returning 0.");
3679     DNSServiceRefDeallocate(data.main_ref);
3680 
3681     cupsFreeDests(data.num_dests, data.dests);
3682     cupsArrayDelete(data.devices);
3683 
3684     return (0);
3685   }
3686 
3687 #    ifdef HAVE_TLS
3688   ipps_ref = data.main_ref;
3689   if (DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0, "_ipps._tcp", NULL, (DNSServiceBrowseReply)cups_dnssd_browse_cb, &data) != kDNSServiceErr_NoError)
3690   {
3691     DEBUG_puts("1cups_enum_dests: Unable to create IPPS browser, returning 0.");
3692     DNSServiceRefDeallocate(data.main_ref);
3693 
3694     cupsFreeDests(data.num_dests, data.dests);
3695     cupsArrayDelete(data.devices);
3696 
3697     return (0);
3698   }
3699 #    endif /* HAVE_TLS */
3700 
3701 #  else /* HAVE_AVAHI */
3702   if ((data.simple_poll = avahi_simple_poll_new()) == NULL)
3703   {
3704     DEBUG_puts("1cups_enum_dests: Unable to create Avahi poll, returning 0.");
3705 
3706     cupsFreeDests(data.num_dests, data.dests);
3707     cupsArrayDelete(data.devices);
3708 
3709     return (0);
3710   }
3711 
3712   avahi_simple_poll_set_func(data.simple_poll, cups_dnssd_poll_cb, &data);
3713 
3714   data.client = avahi_client_new(avahi_simple_poll_get(data.simple_poll),
3715          0, cups_dnssd_client_cb, &data,
3716          &error);
3717   if (!data.client)
3718   {
3719     DEBUG_puts("1cups_enum_dests: Unable to create Avahi client, returning 0.");
3720     avahi_simple_poll_free(data.simple_poll);
3721 
3722     cupsFreeDests(data.num_dests, data.dests);
3723     cupsArrayDelete(data.devices);
3724 
3725     return (0);
3726   }
3727 
3728   data.browsers = 1;
3729   if ((ipp_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipp._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL)
3730   {
3731     DEBUG_puts("1cups_enum_dests: Unable to create Avahi IPP browser, returning 0.");
3732 
3733     avahi_client_free(data.client);
3734     avahi_simple_poll_free(data.simple_poll);
3735 
3736     cupsFreeDests(data.num_dests, data.dests);
3737     cupsArrayDelete(data.devices);
3738 
3739     return (0);
3740   }
3741 
3742 #    ifdef HAVE_TLS
3743   data.browsers ++;
3744   if ((ipps_ref = avahi_service_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ipps._tcp", NULL, 0, cups_dnssd_browse_cb, &data)) == NULL)
3745   {
3746     DEBUG_puts("1cups_enum_dests: Unable to create Avahi IPPS browser, returning 0.");
3747 
3748     avahi_service_browser_free(ipp_ref);
3749     avahi_client_free(data.client);
3750     avahi_simple_poll_free(data.simple_poll);
3751 
3752     cupsFreeDests(data.num_dests, data.dests);
3753     cupsArrayDelete(data.devices);
3754 
3755     return (0);
3756   }
3757 #    endif /* HAVE_TLS */
3758 #  endif /* HAVE_MDNSRESPONDER */
3759 
3760   if (msec < 0)
3761     remaining = INT_MAX;
3762   else
3763     remaining = msec;
3764 
3765   while (remaining > 0 && (!cancel || !*cancel))
3766   {
3767    /*
3768     * Check for input...
3769     */
3770 
3771     DEBUG_printf(("1cups_enum_dests: remaining=%d", remaining));
3772 
3773     cups_elapsed(&curtime);
3774 
3775 #  ifdef HAVE_MDNSRESPONDER
3776 #    ifdef HAVE_POLL
3777     pfd.fd     = main_fd;
3778     pfd.events = POLLIN;
3779 
3780     nfds = poll(&pfd, 1, remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining);
3781 
3782 #    else
3783     FD_ZERO(&input);
3784     FD_SET(main_fd, &input);
3785 
3786     timeout.tv_sec  = 0;
3787     timeout.tv_usec = 1000 * (remaining > _CUPS_DNSSD_MAXTIME ? _CUPS_DNSSD_MAXTIME : remaining);
3788 
3789     nfds = select(main_fd + 1, &input, NULL, NULL, &timeout);
3790 #    endif /* HAVE_POLL */
3791 
3792     if (nfds > 0)
3793       DNSServiceProcessResult(data.main_ref);
3794     else if (nfds < 0 && errno != EINTR && errno != EAGAIN)
3795       break;
3796 
3797 #  else /* HAVE_AVAHI */
3798     data.got_data = 0;
3799 
3800     if ((error = avahi_simple_poll_iterate(data.simple_poll, _CUPS_DNSSD_MAXTIME)) > 0)
3801     {
3802      /*
3803       * We've been told to exit the loop.  Perhaps the connection to
3804       * Avahi failed.
3805       */
3806 
3807       break;
3808     }
3809 
3810     DEBUG_printf(("1cups_enum_dests: got_data=%d", data.got_data));
3811 #  endif /* HAVE_MDNSRESPONDER */
3812 
3813     remaining -= cups_elapsed(&curtime);
3814 
3815     for (device = (_cups_dnssd_device_t *)cupsArrayFirst(data.devices),
3816              count = 0, completed = 0;
3817          device;
3818          device = (_cups_dnssd_device_t *)cupsArrayNext(data.devices))
3819     {
3820       if (device->ref)
3821         count ++;
3822 
3823       if (device->state == _CUPS_DNSSD_ACTIVE)
3824         completed ++;
3825 
3826       if (!device->ref && device->state == _CUPS_DNSSD_NEW)
3827       {
3828         DEBUG_printf(("1cups_enum_dests: Querying '%s'.", device->fullName));
3829 
3830 #  ifdef HAVE_MDNSRESPONDER
3831         device->ref = data.main_ref;
3832 
3833         if (DNSServiceQueryRecord(&(device->ref), kDNSServiceFlagsShareConnection, 0, device->fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, (DNSServiceQueryRecordReply)cups_dnssd_query_cb, &data) == kDNSServiceErr_NoError)
3834         {
3835           count ++;
3836         }
3837         else
3838         {
3839           device->ref   = 0;
3840           device->state = _CUPS_DNSSD_ERROR;
3841 
3842           DEBUG_puts("1cups_enum_dests: Query failed.");
3843         }
3844 
3845 #  else /* HAVE_AVAHI */
3846         if ((device->ref = avahi_record_browser_new(data.client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, device->fullName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, 0, cups_dnssd_query_cb, &data)) != NULL)
3847         {
3848           DEBUG_printf(("1cups_enum_dests: Query ref=%p", device->ref));
3849           count ++;
3850         }
3851         else
3852         {
3853           device->state = _CUPS_DNSSD_ERROR;
3854 
3855           DEBUG_printf(("1cups_enum_dests: Query failed: %s", avahi_strerror(avahi_client_errno(data.client))));
3856         }
3857 #  endif /* HAVE_MDNSRESPONDER */
3858       }
3859       else if (device->ref && device->state == _CUPS_DNSSD_PENDING)
3860       {
3861         completed ++;
3862 
3863         DEBUG_printf(("1cups_enum_dests: Query for \"%s\" is complete.", device->fullName));
3864 
3865         if ((device->type & mask) == type)
3866         {
3867           cups_dest_t	*user_dest;	/* Destination from lpoptions */
3868 
3869           dest = &device->dest;
3870 
3871 	  if ((user_dest = cupsGetDest(dest->name, dest->instance, data.num_dests, data.dests)) != NULL)
3872 	  {
3873 	   /*
3874 	    * Apply user defaults to this destination for all instances...
3875 	    */
3876 
3877 	    for (j = user_dest - data.dests; j < data.num_dests; j ++, user_dest ++)
3878 	    {
3879 	      if (_cups_strcasecmp(user_dest->name, dest->name))
3880 	      {
3881 		j = data.num_dests;
3882 		break;
3883 	      }
3884 
3885 	      for (k = dest->num_options, option = dest->options; k > 0; k --, option ++)
3886 	      {
3887 	        if (!cupsGetOption(option->name, user_dest->num_options, user_dest->options))
3888 		  user_dest->num_options = cupsAddOption(option->name, option->value, user_dest->num_options, &user_dest->options);
3889 	      }
3890 
3891 	      if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, user_dest))
3892 		break;
3893 	    }
3894 
3895 	    if (j < data.num_dests)
3896 	    {
3897 	      remaining = -1;
3898 	      break;
3899 	    }
3900 	  }
3901 	  else
3902 	  {
3903 	    if (!strcasecmp(dest->name, data.def_name) && !data.def_instance)
3904 	    {
3905 	      DEBUG_printf(("1cups_enum_dests: Setting is_default on discovered \"%s\".", dest->name));
3906 	      dest->is_default = 1;
3907 	    }
3908 
3909 	    DEBUG_printf(("1cups_enum_dests: Add callback for \"%s\".", device->dest.name));
3910 	    if (!(*cb)(user_data, CUPS_DEST_FLAGS_NONE, dest))
3911 	    {
3912 	      remaining = -1;
3913 	      break;
3914 	    }
3915 	  }
3916         }
3917 
3918         device->state = _CUPS_DNSSD_ACTIVE;
3919       }
3920     }
3921 
3922 #  ifdef HAVE_AVAHI
3923     DEBUG_printf(("1cups_enum_dests: remaining=%d, browsers=%d, completed=%d, count=%d, devices count=%d", remaining, data.browsers, completed, count, cupsArrayCount(data.devices)));
3924 
3925     if (data.browsers == 0 && completed == cupsArrayCount(data.devices))
3926       break;
3927 #  else
3928     DEBUG_printf(("1cups_enum_dests: remaining=%d, completed=%d, count=%d, devices count=%d", remaining, completed, count, cupsArrayCount(data.devices)));
3929 
3930     if (completed == cupsArrayCount(data.devices))
3931       break;
3932 #  endif /* HAVE_AVAHI */
3933   }
3934 #endif /* HAVE_DNSSD */
3935 
3936  /*
3937   * Return...
3938   */
3939 
3940   enum_finished:
3941 
3942   cupsFreeDests(data.num_dests, data.dests);
3943 
3944 #ifdef HAVE_DNSSD
3945   cupsArrayDelete(data.devices);
3946 
3947 #  ifdef HAVE_MDNSRESPONDER
3948   if (ipp_ref)
3949     DNSServiceRefDeallocate(ipp_ref);
3950 
3951 #    ifdef HAVE_TLS
3952   if (ipps_ref)
3953     DNSServiceRefDeallocate(ipps_ref);
3954 #    endif /* HAVE_TLS */
3955 
3956   if (data.main_ref)
3957     DNSServiceRefDeallocate(data.main_ref);
3958 
3959 #  else /* HAVE_AVAHI */
3960   if (ipp_ref)
3961     avahi_service_browser_free(ipp_ref);
3962 #    ifdef HAVE_TLS
3963   if (ipps_ref)
3964     avahi_service_browser_free(ipps_ref);
3965 #    endif /* HAVE_TLS */
3966 
3967   if (data.client)
3968     avahi_client_free(data.client);
3969   if (data.simple_poll)
3970     avahi_simple_poll_free(data.simple_poll);
3971 #  endif /* HAVE_MDNSRESPONDER */
3972 #endif /* HAVE_DNSSD */
3973 
3974   DEBUG_puts("1cups_enum_dests: Returning 1.");
3975 
3976   return (1);
3977 }
3978 
3979 
3980 /*
3981  * 'cups_find_dest()' - Find a destination using a binary search.
3982  */
3983 
3984 static int				/* O - Index of match */
cups_find_dest(const char * name,const char * instance,int num_dests,cups_dest_t * dests,int prev,int * rdiff)3985 cups_find_dest(const char  *name,	/* I - Destination name */
3986                const char  *instance,	/* I - Instance or NULL */
3987                int         num_dests,	/* I - Number of destinations */
3988 	       cups_dest_t *dests,	/* I - Destinations */
3989 	       int         prev,	/* I - Previous index */
3990 	       int         *rdiff)	/* O - Difference of match */
3991 {
3992   int		left,			/* Low mark for binary search */
3993 		right,			/* High mark for binary search */
3994 		current,		/* Current index */
3995 		diff;			/* Result of comparison */
3996   cups_dest_t	key;			/* Search key */
3997 
3998 
3999   key.name     = (char *)name;
4000   key.instance = (char *)instance;
4001 
4002   if (prev >= 0)
4003   {
4004    /*
4005     * Start search on either side of previous...
4006     */
4007 
4008     if ((diff = cups_compare_dests(&key, dests + prev)) == 0 ||
4009         (diff < 0 && prev == 0) ||
4010 	(diff > 0 && prev == (num_dests - 1)))
4011     {
4012       *rdiff = diff;
4013       return (prev);
4014     }
4015     else if (diff < 0)
4016     {
4017      /*
4018       * Start with previous on right side...
4019       */
4020 
4021       left  = 0;
4022       right = prev;
4023     }
4024     else
4025     {
4026      /*
4027       * Start with previous on left side...
4028       */
4029 
4030       left  = prev;
4031       right = num_dests - 1;
4032     }
4033   }
4034   else
4035   {
4036    /*
4037     * Start search in the middle...
4038     */
4039 
4040     left  = 0;
4041     right = num_dests - 1;
4042   }
4043 
4044   do
4045   {
4046     current = (left + right) / 2;
4047     diff    = cups_compare_dests(&key, dests + current);
4048 
4049     if (diff == 0)
4050       break;
4051     else if (diff < 0)
4052       right = current;
4053     else
4054       left = current;
4055   }
4056   while ((right - left) > 1);
4057 
4058   if (diff != 0)
4059   {
4060    /*
4061     * Check the last 1 or 2 elements...
4062     */
4063 
4064     if ((diff = cups_compare_dests(&key, dests + left)) <= 0)
4065       current = left;
4066     else
4067     {
4068       diff    = cups_compare_dests(&key, dests + right);
4069       current = right;
4070     }
4071   }
4072 
4073  /*
4074   * Return the closest destination and the difference...
4075   */
4076 
4077   *rdiff = diff;
4078 
4079   return (current);
4080 }
4081 
4082 
4083 /*
4084  * 'cups_get_cb()' - Collect enumerated destinations.
4085  */
4086 
4087 static int                              /* O - 1 to continue, 0 to stop */
cups_get_cb(_cups_getdata_t * data,unsigned flags,cups_dest_t * dest)4088 cups_get_cb(_cups_getdata_t *data,      /* I - Data from cupsGetDests */
4089             unsigned        flags,      /* I - Enumeration flags */
4090             cups_dest_t     *dest)      /* I - Destination */
4091 {
4092   if (flags & CUPS_DEST_FLAGS_REMOVED)
4093   {
4094    /*
4095     * Remove destination from array...
4096     */
4097 
4098     data->num_dests = cupsRemoveDest(dest->name, dest->instance, data->num_dests, &data->dests);
4099   }
4100   else
4101   {
4102    /*
4103     * Add destination to array...
4104     */
4105 
4106     data->num_dests = cupsCopyDest(dest, data->num_dests, &data->dests);
4107   }
4108 
4109   return (1);
4110 }
4111 
4112 
4113 /*
4114  * 'cups_get_default()' - Get the default destination from an lpoptions file.
4115  */
4116 
4117 static char *				/* O - Default destination or NULL */
cups_get_default(const char * filename,char * namebuf,size_t namesize,const char ** instance)4118 cups_get_default(const char *filename,	/* I - File to read */
4119                  char       *namebuf,	/* I - Name buffer */
4120 		 size_t     namesize,	/* I - Size of name buffer */
4121 		 const char **instance)	/* I - Instance */
4122 {
4123   cups_file_t	*fp;			/* lpoptions file */
4124   char		line[8192],		/* Line from file */
4125 		*value,			/* Value for line */
4126 		*nameptr;		/* Pointer into name */
4127   int		linenum;		/* Current line */
4128 
4129 
4130   *namebuf = '\0';
4131 
4132   if ((fp = cupsFileOpen(filename, "r")) != NULL)
4133   {
4134     linenum  = 0;
4135 
4136     while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
4137     {
4138       if (!_cups_strcasecmp(line, "default") && value)
4139       {
4140         strlcpy(namebuf, value, namesize);
4141 
4142 	if ((nameptr = strchr(namebuf, ' ')) != NULL)
4143 	  *nameptr = '\0';
4144 	if ((nameptr = strchr(namebuf, '\t')) != NULL)
4145 	  *nameptr = '\0';
4146 
4147 	if ((nameptr = strchr(namebuf, '/')) != NULL)
4148 	  *nameptr++ = '\0';
4149 
4150         *instance = nameptr;
4151 	break;
4152       }
4153     }
4154 
4155     cupsFileClose(fp);
4156   }
4157 
4158   return (*namebuf ? namebuf : NULL);
4159 }
4160 
4161 
4162 /*
4163  * 'cups_get_dests()' - Get destinations from a file.
4164  */
4165 
4166 static int				/* O - Number of destinations */
cups_get_dests(const char * filename,const char * match_name,const char * match_inst,int load_all,int user_default_set,int num_dests,cups_dest_t ** dests)4167 cups_get_dests(
4168     const char  *filename,		/* I - File to read from */
4169     const char  *match_name,		/* I - Destination name we want */
4170     const char  *match_inst,		/* I - Instance name we want */
4171     int         load_all,		/* I - Load all saved destinations? */
4172     int         user_default_set,	/* I - User default printer set? */
4173     int         num_dests,		/* I - Number of destinations */
4174     cups_dest_t **dests)		/* IO - Destinations */
4175 {
4176   int		i;			/* Looping var */
4177   cups_dest_t	*dest;			/* Current destination */
4178   cups_file_t	*fp;			/* File pointer */
4179   char		line[8192],		/* Line from file */
4180 		*lineptr,		/* Pointer into line */
4181 		*name,			/* Name of destination/option */
4182 		*instance;		/* Instance of destination */
4183   int		linenum;		/* Current line number */
4184 
4185 
4186   DEBUG_printf(("7cups_get_dests(filename=\"%s\", match_name=\"%s\", match_inst=\"%s\", load_all=%d, user_default_set=%d, num_dests=%d, dests=%p)", filename, match_name, match_inst, load_all, user_default_set, num_dests, (void *)dests));
4187 
4188  /*
4189   * Try to open the file...
4190   */
4191 
4192   if ((fp = cupsFileOpen(filename, "r")) == NULL)
4193     return (num_dests);
4194 
4195  /*
4196   * Read each printer; each line looks like:
4197   *
4198   *    Dest name[/instance] options
4199   *    Default name[/instance] options
4200   */
4201 
4202   linenum = 0;
4203 
4204   while (cupsFileGetConf(fp, line, sizeof(line), &lineptr, &linenum))
4205   {
4206    /*
4207     * See what type of line it is...
4208     */
4209 
4210     DEBUG_printf(("9cups_get_dests: linenum=%d line=\"%s\" lineptr=\"%s\"",
4211                   linenum, line, lineptr));
4212 
4213     if ((_cups_strcasecmp(line, "dest") && _cups_strcasecmp(line, "default")) || !lineptr)
4214     {
4215       DEBUG_puts("9cups_get_dests: Not a dest or default line...");
4216       continue;
4217     }
4218 
4219     name = lineptr;
4220 
4221    /*
4222     * Search for an instance...
4223     */
4224 
4225     while (!isspace(*lineptr & 255) && *lineptr && *lineptr != '/')
4226       lineptr ++;
4227 
4228     if (*lineptr == '/')
4229     {
4230      /*
4231       * Found an instance...
4232       */
4233 
4234       *lineptr++ = '\0';
4235       instance = lineptr;
4236 
4237      /*
4238       * Search for an instance...
4239       */
4240 
4241       while (!isspace(*lineptr & 255) && *lineptr)
4242 	lineptr ++;
4243     }
4244     else
4245       instance = NULL;
4246 
4247     if (*lineptr)
4248       *lineptr++ = '\0';
4249 
4250     DEBUG_printf(("9cups_get_dests: name=\"%s\", instance=\"%s\"", name,
4251                   instance));
4252 
4253    /*
4254     * Match and/or ignore missing destinations...
4255     */
4256 
4257     if (match_name)
4258     {
4259       if (_cups_strcasecmp(name, match_name) ||
4260           (!instance && match_inst) ||
4261 	  (instance && !match_inst) ||
4262 	  (instance && _cups_strcasecmp(instance, match_inst)))
4263 	continue;
4264 
4265       dest = *dests;
4266     }
4267     else if (!load_all && cupsGetDest(name, NULL, num_dests, *dests) == NULL)
4268     {
4269       DEBUG_puts("9cups_get_dests: Not found!");
4270       continue;
4271     }
4272     else
4273     {
4274      /*
4275       * Add the destination...
4276       */
4277 
4278       num_dests = cupsAddDest(name, instance, num_dests, dests);
4279 
4280       if ((dest = cupsGetDest(name, instance, num_dests, *dests)) == NULL)
4281       {
4282        /*
4283 	* Out of memory!
4284 	*/
4285 
4286         DEBUG_puts("9cups_get_dests: Could not find destination after adding, must be out of memory.");
4287         break;
4288       }
4289     }
4290 
4291    /*
4292     * Add options until we hit the end of the line...
4293     */
4294 
4295     dest->num_options = cupsParseOptions(lineptr, dest->num_options, &(dest->options));
4296 
4297    /*
4298     * If we found what we were looking for, stop now...
4299     */
4300 
4301     if (match_name)
4302       break;
4303 
4304    /*
4305     * Set this as default if needed...
4306     */
4307 
4308     if (!user_default_set && !_cups_strcasecmp(line, "default"))
4309     {
4310       DEBUG_puts("9cups_get_dests: Setting as default...");
4311 
4312       for (i = 0; i < num_dests; i ++)
4313         (*dests)[i].is_default = 0;
4314 
4315       dest->is_default = 1;
4316     }
4317   }
4318 
4319  /*
4320   * Close the file and return...
4321   */
4322 
4323   cupsFileClose(fp);
4324 
4325   return (num_dests);
4326 }
4327 
4328 
4329 /*
4330  * 'cups_make_string()' - Make a comma-separated string of values from an IPP
4331  *                        attribute.
4332  */
4333 
4334 static char *				/* O - New string */
cups_make_string(ipp_attribute_t * attr,char * buffer,size_t bufsize)4335 cups_make_string(
4336     ipp_attribute_t *attr,		/* I - Attribute to convert */
4337     char            *buffer,		/* I - Buffer */
4338     size_t          bufsize)		/* I - Size of buffer */
4339 {
4340   int		i;			/* Looping var */
4341   char		*ptr,			/* Pointer into buffer */
4342 		*end,			/* Pointer to end of buffer */
4343 		*valptr;		/* Pointer into string attribute */
4344 
4345 
4346  /*
4347   * Return quickly if we have a single string value...
4348   */
4349 
4350   if (attr->num_values == 1 &&
4351       attr->value_tag != IPP_TAG_INTEGER &&
4352       attr->value_tag != IPP_TAG_ENUM &&
4353       attr->value_tag != IPP_TAG_BOOLEAN &&
4354       attr->value_tag != IPP_TAG_RANGE)
4355     return (attr->values[0].string.text);
4356 
4357  /*
4358   * Copy the values to the string, separating with commas and escaping strings
4359   * as needed...
4360   */
4361 
4362   end = buffer + bufsize - 1;
4363 
4364   for (i = 0, ptr = buffer; i < attr->num_values && ptr < end; i ++)
4365   {
4366     if (i)
4367       *ptr++ = ',';
4368 
4369     switch (attr->value_tag)
4370     {
4371       case IPP_TAG_INTEGER :
4372       case IPP_TAG_ENUM :
4373 	  snprintf(ptr, (size_t)(end - ptr + 1), "%d", attr->values[i].integer);
4374 	  break;
4375 
4376       case IPP_TAG_BOOLEAN :
4377 	  if (attr->values[i].boolean)
4378 	    strlcpy(ptr, "true", (size_t)(end - ptr + 1));
4379 	  else
4380 	    strlcpy(ptr, "false", (size_t)(end - ptr + 1));
4381 	  break;
4382 
4383       case IPP_TAG_RANGE :
4384 	  if (attr->values[i].range.lower == attr->values[i].range.upper)
4385 	    snprintf(ptr, (size_t)(end - ptr + 1), "%d", attr->values[i].range.lower);
4386 	  else
4387 	    snprintf(ptr, (size_t)(end - ptr + 1), "%d-%d", attr->values[i].range.lower, attr->values[i].range.upper);
4388 	  break;
4389 
4390       default :
4391 	  for (valptr = attr->values[i].string.text;
4392 	       *valptr && ptr < end;)
4393 	  {
4394 	    if (strchr(" \t\n\\\'\"", *valptr))
4395 	    {
4396 	      if (ptr >= (end - 1))
4397 	        break;
4398 
4399 	      *ptr++ = '\\';
4400 	    }
4401 
4402 	    *ptr++ = *valptr++;
4403 	  }
4404 
4405 	  *ptr = '\0';
4406 	  break;
4407     }
4408 
4409     ptr += strlen(ptr);
4410   }
4411 
4412   *ptr = '\0';
4413 
4414   return (buffer);
4415 }
4416 
4417 
4418 /*
4419  * 'cups_name_cb()' - Find an enumerated destination.
4420  */
4421 
4422 static int                              /* O - 1 to continue, 0 to stop */
cups_name_cb(_cups_namedata_t * data,unsigned flags,cups_dest_t * dest)4423 cups_name_cb(_cups_namedata_t *data,    /* I - Data from cupsGetNamedDest */
4424              unsigned         flags,    /* I - Enumeration flags */
4425              cups_dest_t      *dest)    /* I - Destination */
4426 {
4427   DEBUG_printf(("2cups_name_cb(data=%p(%s), flags=%x, dest=%p(%s)", (void *)data, data->name, flags, (void *)dest, dest->name));
4428 
4429   if (!(flags & CUPS_DEST_FLAGS_REMOVED) && !dest->instance && !strcasecmp(data->name, dest->name))
4430   {
4431    /*
4432     * Copy destination and stop enumeration...
4433     */
4434 
4435     cupsCopyDest(dest, 0, &data->dest);
4436     return (0);
4437   }
4438 
4439   return (1);
4440 }
4441 
4442 
4443 /*
4444  * 'cups_queue_name()' - Create a local queue name based on the service name.
4445  */
4446 
4447 static void
cups_queue_name(char * name,const char * serviceName,size_t namesize)4448 cups_queue_name(
4449     char       *name,			/* I - Name buffer */
4450     const char *serviceName,		/* I - Service name */
4451     size_t     namesize)		/* I - Size of name buffer */
4452 {
4453   const char	*ptr;			/* Pointer into serviceName */
4454   char		*nameptr;		/* Pointer into name */
4455 
4456 
4457   for (nameptr = name, ptr = serviceName; *ptr && nameptr < (name + namesize - 1); ptr ++)
4458   {
4459    /*
4460     * Sanitize the printer name...
4461     */
4462 
4463     if (_cups_isalnum(*ptr))
4464       *nameptr++ = *ptr;
4465     else if (nameptr == name || nameptr[-1] != '_')
4466       *nameptr++ = '_';
4467   }
4468 
4469  /*
4470   * Remove an underscore if it is the last character and isn't the only
4471   * character in the name...
4472   */
4473 
4474   if (nameptr > (name + 1) && nameptr[-1] == '_')
4475     nameptr --;
4476 
4477   *nameptr = '\0';
4478 }
4479