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