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