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