1/* 2 * "$Id: ippdiscover.c 11093 2013-07-03 20:48:42Z msweet $" 3 * 4 * ippdiscover command for CUPS. 5 * 6 * Copyright 2007-2013 by Apple Inc. 7 * Copyright 1997-2007 by Easy Software Products. 8 * 9 * These coded instructions, statements, and computer programs are the 10 * property of Apple Inc. and are protected by Federal copyright 11 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 12 * which should have been included with this file. If this file is 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 * 15 * This file is subject to the Apple OS-Developed Software exception. 16 * 17 * Contents: 18 * 19 */ 20 21 22/* 23 * Include necessary headers. 24 */ 25 26#include <cups/cups-private.h> 27#ifdef HAVE_DNSSD 28# include <dns_sd.h> 29# ifdef WIN32 30# pragma comment(lib, "dnssd.lib") 31# endif /* WIN32 */ 32#endif /* HAVE_DNSSD */ 33#ifdef HAVE_AVAHI 34# include <avahi-client/client.h> 35# include <avahi-client/lookup.h> 36# include <avahi-common/simple-watch.h> 37# include <avahi-common/domain.h> 38# include <avahi-common/error.h> 39# include <avahi-common/malloc.h> 40#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX 41#endif /* HAVE_AVAHI */ 42 43 44/* 45 * Local globals... 46 */ 47 48#ifdef HAVE_AVAHI 49static int got_data = 0; /* Got data from poll? */ 50static AvahiSimplePoll *simple_poll = NULL; 51 /* Poll information */ 52#endif /* HAVE_AVAHI */ 53static const char *program = NULL;/* Program to run */ 54 55 56/* 57 * Local functions... 58 */ 59 60#ifdef HAVE_DNSSD 61static void DNSSD_API browse_callback(DNSServiceRef sdRef, 62 DNSServiceFlags flags, 63 uint32_t interfaceIndex, 64 DNSServiceErrorType errorCode, 65 const char *serviceName, 66 const char *regtype, 67 const char *replyDomain, void *context) 68 __attribute__((nonnull(1,5,6,7,8))); 69static void DNSSD_API resolve_cb(DNSServiceRef sdRef, 70 DNSServiceFlags flags, 71 uint32_t interfaceIndex, 72 DNSServiceErrorType errorCode, 73 const char *fullName, 74 const char *hostTarget, 75 uint16_t port, uint16_t txtLen, 76 const unsigned char *txtRecord, 77 void *context); 78#endif /* HAVE_DNSSD */ 79 80#ifdef HAVE_AVAHI 81static void browse_callback(AvahiServiceBrowser *browser, 82 AvahiIfIndex interface, 83 AvahiProtocol protocol, 84 AvahiBrowserEvent event, 85 const char *serviceName, 86 const char *regtype, 87 const char *replyDomain, 88 AvahiLookupResultFlags flags, 89 void *context); 90static void client_cb(AvahiClient *client, AvahiClientState state, 91 void *simple_poll); 92static int poll_cb(struct pollfd *pollfds, unsigned int num_pollfds, 93 int timeout, void *context); 94static void resolve_cb(AvahiServiceResolver *resolver, 95 AvahiIfIndex interface, 96 AvahiProtocol protocol, 97 AvahiResolverEvent event, 98 const char *name, const char *type, 99 const char *domain, const char *host_name, 100 const AvahiAddress *address, uint16_t port, 101 AvahiStringList *txt, 102 AvahiLookupResultFlags flags, void *context); 103#endif /* HAVE_AVAHI */ 104 105static void resolve_and_run(const char *name, const char *type, 106 const char *domain); 107static void unquote(char *dst, const char *src, size_t dstsize); 108static void usage(void) __attribute__((noreturn)); 109 110 111/* 112 * 'main()' - Browse for printers and run the specified command. 113 */ 114 115int /* O - Exit status */ 116main(int argc, /* I - Number of command-line args */ 117 char *argv[]) /* I - Command-line arguments */ 118{ 119 int i; /* Looping var */ 120 const char *opt, /* Current option character */ 121 *name = NULL, /* Service name */ 122 *type = "_ipp._tcp", /* Service type */ 123 *domain = "local."; /* Service domain */ 124#ifdef HAVE_DNSSD 125 DNSServiceRef ref; /* Browsing service reference */ 126#endif /* HAVE_DNSSD */ 127#ifdef HAVE_AVAHI 128 AvahiClient *client; /* Client information */ 129 int error; /* Error code, if any */ 130#endif /* HAVE_AVAHI */ 131 132 133 for (i = 1; i < argc; i ++) 134 if (!strcmp(argv[i], "snmp")) 135 snmponly = 1; 136 else if (!strcmp(argv[i], "ipp")) 137 ipponly = 1; 138 else 139 { 140 puts("Usage: ./ipp-printers [{ipp | snmp}]"); 141 return (1); 142 } 143 144 /* 145 * Create an array to track devices... 146 */ 147 148 devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL); 149 150 /* 151 * Browse for different kinds of printers... 152 */ 153 154 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError) 155 { 156 perror("ERROR: Unable to create service connection"); 157 return (1); 158 } 159 160 fd = DNSServiceRefSockFD(main_ref); 161 162 ipp_ref = main_ref; 163 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, 164 "_ipp._tcp", NULL, browse_callback, devices); 165 166 /* 167 * Loop until we are killed... 168 */ 169 170 progress(); 171 172 for (;;) 173 { 174 FD_ZERO(&input); 175 FD_SET(fd, &input); 176 177 timeout.tv_sec = 2; 178 timeout.tv_usec = 500000; 179 180 if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0) 181 { 182 time_t curtime = time(NULL); 183 184 for (device = (cups_device_t *)cupsArrayFirst(devices); 185 device; 186 device = (cups_device_t *)cupsArrayNext(devices)) 187 if (!device->got_resolve) 188 { 189 if (!device->ref) 190 break; 191 192 if ((curtime - device->resolve_time) > 10) 193 { 194 device->got_resolve = -1; 195 fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n", 196 device->name); 197 progress(); 198 } 199 else 200 break; 201 } 202 203 if (!device) 204 break; 205 } 206 207 if (FD_ISSET(fd, &input)) 208 { 209 /* 210 * Process results of our browsing... 211 */ 212 213 progress(); 214 DNSServiceProcessResult(main_ref); 215 } 216 else 217 { 218 /* 219 * Query any devices we've found... 220 */ 221 222 DNSServiceErrorType status; /* DNS query status */ 223 int count; /* Number of queries */ 224 225 226 for (device = (cups_device_t *)cupsArrayFirst(devices), count = 0; 227 device; 228 device = (cups_device_t *)cupsArrayNext(devices)) 229 { 230 if (!device->ref && !device->sent) 231 { 232 /* 233 * Found the device, now get the TXT record(s) for it... 234 */ 235 236 if (count < 50) 237 { 238 device->resolve_time = time(NULL); 239 device->ref = main_ref; 240 241 status = DNSServiceResolve(&(device->ref), 242 kDNSServiceFlagsShareConnection, 243 0, device->name, device->regtype, 244 device->domain, resolve_callback, 245 device); 246 if (status != kDNSServiceErr_NoError) 247 { 248 fprintf(stderr, "\rUnable to resolve \"%s\": %d\n", 249 device->name, status); 250 progress(); 251 } 252 else 253 count ++; 254 } 255 } 256 else if (!device->sent && device->got_resolve) 257 { 258 /* 259 * Got the TXT records, now report the device... 260 */ 261 262 DNSServiceRefDeallocate(device->ref); 263 device->ref = 0; 264 device->sent = 1; 265 } 266 } 267 } 268 } 269 270#ifndef DEBUG 271 fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n", 272 cupsArrayCount(devices)); 273#endif /* !DEBUG */ 274 275 puts("#!/bin/sh -x"); 276 puts("test -d results && rm -rf results"); 277 puts("mkdir results"); 278 puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL"); 279 puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|" 280 "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER"); 281 282 for (device = (cups_device_t *)cupsArrayFirst(devices); 283 device; 284 device = (cups_device_t *)cupsArrayNext(devices)) 285 { 286 if (device->got_resolve <= 0 || device->cups_shared) 287 continue; 288 289#ifdef DEBUG 290 fprintf(stderr, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n", 291 device->name, device->got_resolve, device->cups_shared, device->uri); 292#else 293 fprintf(stderr, "Checking \"%s\"...\n", device->name); 294#endif /* DEBUG */ 295 296 if ((http = httpConnect(device->host, device->port)) == NULL) 297 { 298 fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name, 299 cupsLastErrorString()); 300 continue; 301 } 302 303 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); 304 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, 305 device->uri); 306 307 response = cupsDoRequest(http, request, device->rp); 308 309 if (cupsLastError() > IPP_OK_SUBST) 310 fprintf(stderr, "Failed to query \"%s\": %s\n", device->name, 311 cupsLastErrorString()); 312 else 313 { 314 if ((attr = ippFindAttribute(response, "ipp-versions-supported", 315 IPP_TAG_KEYWORD)) != NULL) 316 { 317 version = attr->values[0].string.text; 318 319 for (i = 1; i < attr->num_values; i ++) 320 if (strcmp(attr->values[i].string.text, version) > 0) 321 version = attr->values[i].string.text; 322 } 323 else 324 version = "1.0"; 325 326 testfile = NULL; 327 328 if ((attr = ippFindAttribute(response, "document-format-supported", 329 IPP_TAG_MIMETYPE)) != NULL) 330 { 331 /* 332 * Figure out the test file for printing, preferring PDF and PostScript 333 * over JPEG and plain text... 334 */ 335 336 for (i = 0; i < attr->num_values; i ++) 337 { 338 if (!strcasecmp(attr->values[i].string.text, "application/pdf")) 339 { 340 testfile = "testfile.pdf"; 341 break; 342 } 343 else if (!strcasecmp(attr->values[i].string.text, 344 "application/postscript")) 345 testfile = "testfile.ps"; 346 else if (!strcasecmp(attr->values[i].string.text, "image/jpeg") && 347 !testfile) 348 testfile = "testfile.jpg"; 349 else if (!strcasecmp(attr->values[i].string.text, "text/plain") && 350 !testfile) 351 testfile = "testfile.txt"; 352 else if (!strcasecmp(attr->values[i].string.text, 353 "application/vnd.hp-PCL") && !testfile) 354 testfile = "testfile.pcl"; 355 } 356 357 if (!testfile) 358 { 359 fprintf(stderr, 360 "Printer \"%s\" reports the following IPP file formats:\n", 361 device->name); 362 for (i = 0; i < attr->num_values; i ++) 363 fprintf(stderr, " \"%s\"\n", attr->values[i].string.text); 364 } 365 } 366 367 if (!testfile && device->pdl) 368 { 369 char *pdl, /* Copy of pdl string */ 370 *start, *end; /* Pointers into pdl string */ 371 372 373 pdl = strdup(device->pdl); 374 for (start = device->pdl; start && *start; start = end) 375 { 376 if ((end = strchr(start, ',')) != NULL) 377 *end++ = '\0'; 378 379 if (!strcasecmp(start, "application/pdf")) 380 { 381 testfile = "testfile.pdf"; 382 break; 383 } 384 else if (!strcasecmp(start, "application/postscript")) 385 testfile = "testfile.ps"; 386 else if (!strcasecmp(start, "image/jpeg") && !testfile) 387 testfile = "testfile.jpg"; 388 else if (!strcasecmp(start, "text/plain") && !testfile) 389 testfile = "testfile.txt"; 390 else if (!strcasecmp(start, "application/vnd.hp-PCL") && !testfile) 391 testfile = "testfile.pcl"; 392 } 393 free(pdl); 394 395 if (testfile) 396 { 397 fprintf(stderr, 398 "Using \"%s\" for printer \"%s\" based on TXT record pdl " 399 "info.\n", testfile, device->name); 400 } 401 else 402 { 403 fprintf(stderr, 404 "Printer \"%s\" reports the following TXT file formats:\n", 405 device->name); 406 fprintf(stderr, " \"%s\"\n", device->pdl); 407 } 408 } 409 410 if (!device->ty && 411 (attr = ippFindAttribute(response, "printer-make-and-model", 412 IPP_TAG_TEXT)) != NULL) 413 device->ty = strdup(attr->values[0].string.text); 414 415 if (strcmp(version, "1.0") && testfile && device->ty) 416 { 417 char filename[1024], /* Filename */ 418 *fileptr; /* Pointer into filename */ 419 const char *typtr; /* Pointer into ty */ 420 421 if (!strncasecmp(device->ty, "DeskJet", 7) || 422 !strncasecmp(device->ty, "DesignJet", 9) || 423 !strncasecmp(device->ty, "OfficeJet", 9) || 424 !strncasecmp(device->ty, "Photosmart", 10)) 425 strlcpy(filename, "HP_", sizeof(filename)); 426 else 427 filename[0] = '\0'; 428 429 fileptr = filename + strlen(filename); 430 431 if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29)) 432 typtr = device->ty + 22; 433 else 434 typtr = device->ty; 435 436 while (*typtr && fileptr < (filename + sizeof(filename) - 1)) 437 { 438 if (isalnum(*typtr & 255) || *typtr == '-') 439 *fileptr++ = *typtr++; 440 else 441 { 442 *fileptr++ = '_'; 443 typtr++; 444 } 445 } 446 447 *fileptr = '\0'; 448 449 printf("# %s\n", device->name); 450 printf("echo \"Testing %s...\"\n", device->name); 451 452 if (!ipponly) 453 { 454 printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " 455 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n", 456 device->host, filename); 457 printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " 458 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | " 459 "tee -a results/%s.snmpwalk\n", 460 device->host, filename); 461 } 462 463 if (!snmponly) 464 { 465 printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " 466 "ipp-%s.test\" > results/%s.log\n", testfile, version, 467 device->uri, version, filename); 468 printf("CUPS_DEBUG_LOG=results/%s.debug_log " 469 "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " 470 "ipp-%s.test | tee -a results/%s.log\n", filename, 471 testfile, version, device->uri, 472 version, filename); 473 } 474 475 puts(""); 476 } 477 else if (!device->ty) 478 fprintf(stderr, 479 "Ignoring \"%s\" since it doesn't provide a make and model.\n", 480 device->name); 481 else if (!testfile) 482 fprintf(stderr, 483 "Ignoring \"%s\" since it does not support a common format.\n", 484 device->name); 485 else 486 fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n", 487 device->name); 488 } 489 490 ippDelete(response); 491 httpClose(http); 492 } 493 494 return (0); 495} 496 497 498/* 499 * 'browse_callback()' - Browse devices. 500 */ 501 502static void 503browse_callback( 504 DNSServiceRef sdRef, /* I - Service reference */ 505 DNSServiceFlags flags, /* I - Option flags */ 506 uint32_t interfaceIndex, /* I - Interface number */ 507 DNSServiceErrorType errorCode, /* I - Error, if any */ 508 const char *serviceName, /* I - Name of service/device */ 509 const char *regtype, /* I - Type of service */ 510 const char *replyDomain, /* I - Service domain */ 511 void *context) /* I - Devices array */ 512{ 513#ifdef DEBUG 514 fprintf(stderr, "browse_callback(sdRef=%p, flags=%x, " 515 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", " 516 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n", 517 sdRef, flags, interfaceIndex, errorCode, 518 serviceName ? serviceName : "(null)", 519 regtype ? regtype : "(null)", 520 replyDomain ? replyDomain : "(null)", 521 context); 522#endif /* DEBUG */ 523 524 /* 525 * Only process "add" data... 526 */ 527 528 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) 529 return; 530 531 /* 532 * Get the device... 533 */ 534 535 get_device((cups_array_t *)context, serviceName, regtype, replyDomain); 536} 537 538 539/* 540 * 'compare_devices()' - Compare two devices. 541 */ 542 543static int /* O - Result of comparison */ 544compare_devices(cups_device_t *a, /* I - First device */ 545 cups_device_t *b) /* I - Second device */ 546{ 547 int retval = strcmp(a->name, b->name); 548 549 if (retval) 550 return (retval); 551 else 552 return (-strcmp(a->regtype, b->regtype)); 553} 554 555 556/* 557 * 'get_device()' - Create or update a device. 558 */ 559 560static cups_device_t * /* O - Device */ 561get_device(cups_array_t *devices, /* I - Device array */ 562 const char *serviceName, /* I - Name of service/device */ 563 const char *regtype, /* I - Type of service */ 564 const char *replyDomain) /* I - Service domain */ 565{ 566 cups_device_t key, /* Search key */ 567 *device; /* Device */ 568 char fullName[kDNSServiceMaxDomainName]; 569 /* Full name for query */ 570 571 572 /* 573 * See if this is a new device... 574 */ 575 576 key.name = (char *)serviceName; 577 key.regtype = (char *)regtype; 578 579 for (device = cupsArrayFind(devices, &key); 580 device; 581 device = cupsArrayNext(devices)) 582 if (strcasecmp(device->name, key.name)) 583 break; 584 else 585 { 586 if (!strcasecmp(device->domain, "local.") && 587 strcasecmp(device->domain, replyDomain)) 588 { 589 /* 590 * Update the .local listing to use the "global" domain name instead. 591 * The backend will try local lookups first, then the global domain name. 592 */ 593 594 free(device->domain); 595 device->domain = strdup(replyDomain); 596 597 DNSServiceConstructFullName(fullName, device->name, regtype, 598 replyDomain); 599 free(device->fullName); 600 device->fullName = strdup(fullName); 601 } 602 603 return (device); 604 } 605 606 /* 607 * Yes, add the device... 608 */ 609 610 device = calloc(sizeof(cups_device_t), 1); 611 device->name = strdup(serviceName); 612 device->domain = strdup(replyDomain); 613 device->regtype = strdup(regtype); 614 615 cupsArrayAdd(devices, device); 616 617 /* 618 * Set the "full name" of this service, which is used for queries... 619 */ 620 621 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); 622 device->fullName = strdup(fullName); 623 624#ifdef DEBUG 625 fprintf(stderr, "get_device: fullName=\"%s\"...\n", fullName); 626#endif /* DEBUG */ 627 628 return (device); 629} 630 631 632/* 633 * 'progress()' - Show query progress. 634 */ 635 636static void 637progress(void) 638{ 639#ifndef DEBUG 640 const char *chars = "|/-\\"; 641 static int count = 0; 642 643 644 fprintf(stderr, "\rLooking for printers %c", chars[count]); 645 fflush(stderr); 646 count = (count + 1) & 3; 647#endif /* !DEBUG */ 648} 649 650 651/* 652 * 'resolve_callback()' - Process resolve data. 653 */ 654 655static void 656resolve_callback( 657 DNSServiceRef sdRef, /* I - Service reference */ 658 DNSServiceFlags flags, /* I - Data flags */ 659 uint32_t interfaceIndex, /* I - Interface */ 660 DNSServiceErrorType errorCode, /* I - Error, if any */ 661 const char *fullName, /* I - Full service name */ 662 const char *hostTarget, /* I - Hostname */ 663 uint16_t port, /* I - Port number (network byte order) */ 664 uint16_t txtLen, /* I - Length of TXT record data */ 665 const unsigned char *txtRecord, /* I - TXT record data */ 666 void *context) /* I - Device */ 667{ 668 char temp[257], /* TXT key value */ 669 uri[1024]; /* Printer URI */ 670 const void *value; /* Value from TXT record */ 671 uint8_t valueLen; /* Length of value */ 672 cups_device_t *device = (cups_device_t *)context; 673 /* Device */ 674 675 676#ifdef DEBUG 677 fprintf(stderr, "\rresolve_callback(sdRef=%p, flags=%x, " 678 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", " 679 "hostTarget=\"%s\", port=%d, txtLen=%u, txtRecord=%p, " 680 "context=%p)\n", 681 sdRef, flags, interfaceIndex, errorCode, 682 fullName ? fullName : "(null)", hostTarget ? hostTarget : "(null)", 683 ntohs(port), txtLen, txtRecord, context); 684#endif /* DEBUG */ 685 686 /* 687 * Only process "add" data... 688 */ 689 690 if (errorCode != kDNSServiceErr_NoError) 691 return; 692 693 device->got_resolve = 1; 694 device->host = strdup(hostTarget); 695 device->port = ntohs(port); 696 697 /* 698 * Extract the "remote printer" key from the TXT record and save the URI... 699 */ 700 701 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "rp", 702 &valueLen)) != NULL) 703 { 704 if (((char *)value)[0] == '/') 705 { 706 /* 707 * "rp" value (incorrectly) has a leading slash already... 708 */ 709 710 memcpy(temp, value, valueLen); 711 temp[valueLen] = '\0'; 712 } 713 else 714 { 715 /* 716 * Convert to resource by concatenating with a leading "/"... 717 */ 718 719 temp[0] = '/'; 720 memcpy(temp + 1, value, valueLen); 721 temp[valueLen + 1] = '\0'; 722 } 723 } 724 else 725 { 726 /* 727 * Default "rp" value is blank, mapping to a path of "/"... 728 */ 729 730 temp[0] = '/'; 731 temp[1] = '\0'; 732 } 733 734 if (!strncmp(temp, "/printers/", 10)) 735 device->cups_shared = -1; 736 737 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", 738 NULL, hostTarget, ntohs(port), temp); 739 device->uri = strdup(uri); 740 device->rp = strdup(temp); 741 742 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "ty", 743 &valueLen)) != NULL) 744 { 745 memcpy(temp, value, valueLen); 746 temp[valueLen] = '\0'; 747 748 device->ty = strdup(temp); 749 } 750 751 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "pdl", 752 &valueLen)) != NULL) 753 { 754 memcpy(temp, value, valueLen); 755 temp[valueLen] = '\0'; 756 757 device->pdl = strdup(temp); 758 } 759 760 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "printer-type", 761 &valueLen)) != NULL) 762 device->cups_shared = 1; 763 764 if (device->cups_shared) 765 fprintf(stderr, "\rIgnoring CUPS printer %s\n", uri); 766 else 767 fprintf(stderr, "\rFound IPP printer %s\n", uri); 768 769 progress(); 770} 771 772 773/* 774 * 'unquote()' - Unquote a name string. 775 */ 776 777static void 778unquote(char *dst, /* I - Destination buffer */ 779 const char *src, /* I - Source string */ 780 size_t dstsize) /* I - Size of destination buffer */ 781{ 782 char *dstend = dst + dstsize - 1; /* End of destination buffer */ 783 784 785 while (*src && dst < dstend) 786 { 787 if (*src == '\\') 788 { 789 src ++; 790 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && 791 isdigit(src[2] & 255)) 792 { 793 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; 794 src += 3; 795 } 796 else 797 *dst++ = *src++; 798 } 799 else 800 *dst++ = *src ++; 801 } 802 803 *dst = '\0'; 804} 805 806 807/* 808 * 'usage()' - Show program usage and exit. 809 */ 810 811static void 812usage(void) 813{ 814 _cupsLangPuts(stdout, _("Usage: ippdiscover [options] -a\n" 815 " ippdiscover [options] \"service name\"\n" 816 "\n" 817 "Options:")); 818 _cupsLangPuts(stdout, _(" -a Browse for all services.")); 819 _cupsLangPuts(stdout, _(" -d domain Browse/resolve in specified domain.")); 820 _cupsLangPuts(stdout, _(" -p program Run specified program for each service.")); 821 _cupsLangPuts(stdout, _(" -t type Browse/resolve with specified type.")); 822 823 exit(0); 824} 825 826 827/* 828 * End of "$Id: ippdiscover.c 11093 2013-07-03 20:48:42Z msweet $". 829 */ 830