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