1/* 2 * "$Id: ippfind.c 12142 2014-08-30 02:35:43Z msweet $" 3 * 4 * Utility to find IPP printers via Bonjour/DNS-SD and optionally run 5 * commands such as IPP and Bonjour conformance tests. This tool is 6 * inspired by the UNIX "find" command, thus its name. 7 * 8 * Copyright 2008-2014 by Apple Inc. 9 * 10 * These coded instructions, statements, and computer programs are the 11 * property of Apple Inc. and are protected by Federal copyright 12 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 13 * which should have been included with this file. If this file is 14 * file is missing or damaged, see the license at "http://www.cups.org/". 15 * 16 * This file is subject to the Apple OS-Developed Software exception. 17 */ 18 19/* 20 * Include necessary headers. 21 */ 22 23#define _CUPS_NO_DEPRECATED 24#include <cups/cups-private.h> 25#ifdef WIN32 26# include <process.h> 27# include <sys/timeb.h> 28#else 29# include <sys/wait.h> 30#endif /* WIN32 */ 31#include <regex.h> 32#ifdef HAVE_DNSSD 33# include <dns_sd.h> 34#elif defined(HAVE_AVAHI) 35# include <avahi-client/client.h> 36# include <avahi-client/lookup.h> 37# include <avahi-common/simple-watch.h> 38# include <avahi-common/domain.h> 39# include <avahi-common/error.h> 40# include <avahi-common/malloc.h> 41# define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX 42#endif /* HAVE_DNSSD */ 43 44#ifndef WIN32 45extern char **environ; /* Process environment variables */ 46#endif /* !WIN32 */ 47 48 49/* 50 * Structures... 51 */ 52 53typedef enum ippfind_exit_e /* Exit codes */ 54{ 55 IPPFIND_EXIT_TRUE = 0, /* OK and result is true */ 56 IPPFIND_EXIT_FALSE, /* OK but result is false*/ 57 IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */ 58 IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */ 59 IPPFIND_EXIT_MEMORY /* Out of memory */ 60} ippfind_exit_t; 61 62typedef enum ippfind_op_e /* Operations for expressions */ 63{ 64 /* "Evaluation" operations */ 65 IPPFIND_OP_NONE, /* No operation */ 66 IPPFIND_OP_AND, /* Logical AND of all children */ 67 IPPFIND_OP_OR, /* Logical OR of all children */ 68 IPPFIND_OP_TRUE, /* Always true */ 69 IPPFIND_OP_FALSE, /* Always false */ 70 IPPFIND_OP_IS_LOCAL, /* Is a local service */ 71 IPPFIND_OP_IS_REMOTE, /* Is a remote service */ 72 IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */ 73 IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */ 74 IPPFIND_OP_HOST_REGEX, /* Hostname matches regular expression */ 75 IPPFIND_OP_PORT_RANGE, /* Port matches range */ 76 IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */ 77 IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */ 78 IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */ 79 IPPFIND_OP_URI_REGEX, /* URI matches regular expression */ 80 81 /* "Output" operations */ 82 IPPFIND_OP_EXEC, /* Execute when true */ 83 IPPFIND_OP_LIST, /* List when true */ 84 IPPFIND_OP_PRINT_NAME, /* Print URI when true */ 85 IPPFIND_OP_PRINT_URI, /* Print name when true */ 86 IPPFIND_OP_QUIET /* No output when true */ 87} ippfind_op_t; 88 89typedef struct ippfind_expr_s /* Expression */ 90{ 91 struct ippfind_expr_s 92 *prev, /* Previous expression */ 93 *next, /* Next expression */ 94 *parent, /* Parent expressions */ 95 *child; /* Child expressions */ 96 ippfind_op_t op; /* Operation code (see above) */ 97 int invert; /* Invert the result */ 98 char *key; /* TXT record key */ 99 regex_t re; /* Regular expression for matching */ 100 int range[2]; /* Port number range */ 101 int num_args; /* Number of arguments for exec */ 102 char **args; /* Arguments for exec */ 103} ippfind_expr_t; 104 105typedef struct ippfind_srv_s /* Service information */ 106{ 107#ifdef HAVE_DNSSD 108 DNSServiceRef ref; /* Service reference for query */ 109#elif defined(HAVE_AVAHI) 110 AvahiServiceResolver *ref; /* Resolver */ 111#endif /* HAVE_DNSSD */ 112 char *name, /* Service name */ 113 *domain, /* Domain name */ 114 *regtype, /* Registration type */ 115 *fullName, /* Full name */ 116 *host, /* Hostname */ 117 *resource, /* Resource path */ 118 *uri; /* URI */ 119 int num_txt; /* Number of TXT record keys */ 120 cups_option_t *txt; /* TXT record keys */ 121 int port, /* Port number */ 122 is_local, /* Is a local service? */ 123 is_processed, /* Did we process the service? */ 124 is_resolved; /* Got the resolve data? */ 125} ippfind_srv_t; 126 127 128/* 129 * Local globals... 130 */ 131 132#ifdef HAVE_DNSSD 133static DNSServiceRef dnssd_ref; /* Master service reference */ 134#elif defined(HAVE_AVAHI) 135static AvahiClient *avahi_client = NULL;/* Client information */ 136static int avahi_got_data = 0; /* Got data from poll? */ 137static AvahiSimplePoll *avahi_poll = NULL; 138 /* Poll information */ 139#endif /* HAVE_DNSSD */ 140 141static int address_family = AF_UNSPEC; 142 /* Address family for LIST */ 143static int bonjour_error = 0; /* Error browsing/resolving? */ 144static double bonjour_timeout = 1.0; /* Timeout in seconds */ 145static int ipp_version = 20; /* IPP version for LIST */ 146 147 148/* 149 * Local functions... 150 */ 151 152#ifdef HAVE_DNSSD 153static void DNSSD_API browse_callback(DNSServiceRef sdRef, 154 DNSServiceFlags flags, 155 uint32_t interfaceIndex, 156 DNSServiceErrorType errorCode, 157 const char *serviceName, 158 const char *regtype, 159 const char *replyDomain, void *context) 160 __attribute__((nonnull(1,5,6,7,8))); 161static void DNSSD_API browse_local_callback(DNSServiceRef sdRef, 162 DNSServiceFlags flags, 163 uint32_t interfaceIndex, 164 DNSServiceErrorType errorCode, 165 const char *serviceName, 166 const char *regtype, 167 const char *replyDomain, 168 void *context) 169 __attribute__((nonnull(1,5,6,7,8))); 170#elif defined(HAVE_AVAHI) 171static void browse_callback(AvahiServiceBrowser *browser, 172 AvahiIfIndex interface, 173 AvahiProtocol protocol, 174 AvahiBrowserEvent event, 175 const char *serviceName, 176 const char *regtype, 177 const char *replyDomain, 178 AvahiLookupResultFlags flags, 179 void *context); 180static void client_callback(AvahiClient *client, 181 AvahiClientState state, 182 void *context); 183#endif /* HAVE_AVAHI */ 184 185static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b); 186static const char *dnssd_error_string(int error); 187static int eval_expr(ippfind_srv_t *service, 188 ippfind_expr_t *expressions); 189static int exec_program(ippfind_srv_t *service, int num_args, 190 char **args); 191static ippfind_srv_t *get_service(cups_array_t *services, 192 const char *serviceName, 193 const char *regtype, 194 const char *replyDomain) 195 __attribute__((nonnull(1,2,3,4))); 196static double get_time(void); 197static int list_service(ippfind_srv_t *service); 198static ippfind_expr_t *new_expr(ippfind_op_t op, int invert, 199 const char *value, const char *regex, 200 char **args); 201#ifdef HAVE_DNSSD 202static void DNSSD_API resolve_callback(DNSServiceRef sdRef, 203 DNSServiceFlags flags, 204 uint32_t interfaceIndex, 205 DNSServiceErrorType errorCode, 206 const char *fullName, 207 const char *hostTarget, uint16_t port, 208 uint16_t txtLen, 209 const unsigned char *txtRecord, 210 void *context) 211 __attribute__((nonnull(1,5,6,9, 10))); 212#elif defined(HAVE_AVAHI) 213static int poll_callback(struct pollfd *pollfds, 214 unsigned int num_pollfds, int timeout, 215 void *context); 216static void resolve_callback(AvahiServiceResolver *res, 217 AvahiIfIndex interface, 218 AvahiProtocol protocol, 219 AvahiResolverEvent event, 220 const char *serviceName, 221 const char *regtype, 222 const char *replyDomain, 223 const char *host_name, 224 const AvahiAddress *address, 225 uint16_t port, 226 AvahiStringList *txt, 227 AvahiLookupResultFlags flags, 228 void *context); 229#endif /* HAVE_DNSSD */ 230static void set_service_uri(ippfind_srv_t *service); 231static void show_usage(void) __attribute__((noreturn)); 232static void show_version(void) __attribute__((noreturn)); 233 234 235/* 236 * 'main()' - Browse for printers. 237 */ 238 239int /* O - Exit status */ 240main(int argc, /* I - Number of command-line args */ 241 char *argv[]) /* I - Command-line arguments */ 242{ 243 int i, /* Looping var */ 244 have_output = 0,/* Have output expression */ 245 status = IPPFIND_EXIT_FALSE; 246 /* Exit status */ 247 const char *opt, /* Option character */ 248 *search; /* Current browse/resolve string */ 249 cups_array_t *searches; /* Things to browse/resolve */ 250 cups_array_t *services; /* Service array */ 251 ippfind_srv_t *service; /* Current service */ 252 ippfind_expr_t *expressions = NULL, 253 /* Expression tree */ 254 *temp = NULL, /* New expression */ 255 *parent = NULL, /* Parent expression */ 256 *current = NULL,/* Current expression */ 257 *parens[100]; /* Markers for parenthesis */ 258 int num_parens = 0; /* Number of parenthesis */ 259 ippfind_op_t logic = IPPFIND_OP_AND; 260 /* Logic for next expression */ 261 int invert = 0; /* Invert expression? */ 262 int err; /* DNS-SD error */ 263#ifdef HAVE_DNSSD 264 fd_set sinput; /* Input set for select() */ 265 struct timeval stimeout; /* Timeout for select() */ 266#endif /* HAVE_DNSSD */ 267 double endtime; /* End time */ 268 static const char * const ops[] = /* Node operation names */ 269 { 270 "NONE", 271 "AND", 272 "OR", 273 "TRUE", 274 "FALSE", 275 "IS_LOCAL", 276 "IS_REMOTE", 277 "DOMAIN_REGEX", 278 "NAME_REGEX", 279 "HOST_REGEX", 280 "PORT_RANGE", 281 "PATH_REGEX", 282 "TXT_EXISTS", 283 "TXT_REGEX", 284 "URI_REGEX", 285 "EXEC", 286 "LIST", 287 "PRINT_NAME", 288 "PRINT_URI", 289 "QUIET" 290 }; 291 292 293 /* 294 * Initialize the locale... 295 */ 296 297 _cupsSetLocale(argv); 298 299 /* 300 * Create arrays to track services and things we want to browse/resolve... 301 */ 302 303 searches = cupsArrayNew(NULL, NULL); 304 services = cupsArrayNew((cups_array_func_t)compare_services, NULL); 305 306 /* 307 * Parse command-line... 308 */ 309 310 if (getenv("IPPFIND_DEBUG")) 311 for (i = 1; i < argc; i ++) 312 fprintf(stderr, "argv[%d]=\"%s\"\n", i, argv[i]); 313 314 for (i = 1; i < argc; i ++) 315 { 316 if (argv[i][0] == '-') 317 { 318 if (argv[i][1] == '-') 319 { 320 /* 321 * Parse --option options... 322 */ 323 324 if (!strcmp(argv[i], "--and")) 325 { 326 if (logic == IPPFIND_OP_OR) 327 { 328 _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or.")); 329 show_usage(); 330 } 331 332 if (!current) 333 { 334 _cupsLangPuts(stderr, 335 _("ippfind: Missing expression before \"--and\".")); 336 show_usage(); 337 } 338 339 temp = NULL; 340 } 341 else if (!strcmp(argv[i], "--domain")) 342 { 343 i ++; 344 if (i >= argc) 345 { 346 _cupsLangPrintf(stderr, 347 _("ippfind: Missing regular expression after %s."), 348 "--domain"); 349 show_usage(); 350 } 351 352 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i], 353 NULL)) == NULL) 354 return (IPPFIND_EXIT_MEMORY); 355 } 356 else if (!strcmp(argv[i], "--exec")) 357 { 358 i ++; 359 if (i >= argc) 360 { 361 _cupsLangPrintf(stderr, _("ippfind: Expected program after %s."), 362 "--exec"); 363 show_usage(); 364 } 365 366 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, 367 argv + i)) == NULL) 368 return (IPPFIND_EXIT_MEMORY); 369 370 while (i < argc) 371 if (!strcmp(argv[i], ";")) 372 break; 373 else 374 i ++; 375 376 if (i >= argc) 377 { 378 _cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."), 379 "--exec"); 380 show_usage(); 381 } 382 383 have_output = 1; 384 } 385 else if (!strcmp(argv[i], "--false")) 386 { 387 if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL, 388 NULL)) == NULL) 389 return (IPPFIND_EXIT_MEMORY); 390 } 391 else if (!strcmp(argv[i], "--help")) 392 { 393 show_usage(); 394 } 395 else if (!strcmp(argv[i], "--host")) 396 { 397 i ++; 398 if (i >= argc) 399 { 400 _cupsLangPrintf(stderr, 401 _("ippfind: Missing regular expression after %s."), 402 "--host"); 403 show_usage(); 404 } 405 406 if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i], 407 NULL)) == NULL) 408 return (IPPFIND_EXIT_MEMORY); 409 } 410 else if (!strcmp(argv[i], "--ls")) 411 { 412 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, 413 NULL)) == NULL) 414 return (IPPFIND_EXIT_MEMORY); 415 416 have_output = 1; 417 } 418 else if (!strcmp(argv[i], "--local")) 419 { 420 if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL, 421 NULL)) == NULL) 422 return (IPPFIND_EXIT_MEMORY); 423 } 424 else if (!strcmp(argv[i], "--name")) 425 { 426 i ++; 427 if (i >= argc) 428 { 429 _cupsLangPrintf(stderr, 430 _("ippfind: Missing regular expression after %s."), 431 "--name"); 432 show_usage(); 433 } 434 435 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i], 436 NULL)) == NULL) 437 return (IPPFIND_EXIT_MEMORY); 438 } 439 else if (!strcmp(argv[i], "--not")) 440 { 441 invert = 1; 442 } 443 else if (!strcmp(argv[i], "--or")) 444 { 445 if (!current) 446 { 447 _cupsLangPuts(stderr, 448 _("ippfind: Missing expression before \"--or\".")); 449 show_usage(); 450 } 451 452 logic = IPPFIND_OP_OR; 453 454 if (parent && parent->op == IPPFIND_OP_OR) 455 { 456 /* 457 * Already setup to do "foo --or bar --or baz"... 458 */ 459 460 temp = NULL; 461 } 462 else if (!current->prev && parent) 463 { 464 /* 465 * Change parent node into an OR node... 466 */ 467 468 parent->op = IPPFIND_OP_OR; 469 temp = NULL; 470 } 471 else if (!current->prev) 472 { 473 /* 474 * Need to group "current" in a new OR node... 475 */ 476 477 if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL, 478 NULL)) == NULL) 479 return (IPPFIND_EXIT_MEMORY); 480 481 temp->parent = parent; 482 temp->child = current; 483 current->parent = temp; 484 485 if (parent) 486 parent->child = temp; 487 else 488 expressions = temp; 489 490 parent = temp; 491 temp = NULL; 492 } 493 else 494 { 495 /* 496 * Need to group previous expressions in an AND node, and then 497 * put that in an OR node... 498 */ 499 500 if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, 501 NULL)) == NULL) 502 return (IPPFIND_EXIT_MEMORY); 503 504 while (current->prev) 505 { 506 current->parent = temp; 507 current = current->prev; 508 } 509 510 current->parent = temp; 511 temp->child = current; 512 current = temp; 513 514 if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL, 515 NULL)) == NULL) 516 return (IPPFIND_EXIT_MEMORY); 517 518 temp->parent = parent; 519 current->parent = temp; 520 521 if (parent) 522 parent->child = temp; 523 else 524 expressions = temp; 525 526 parent = temp; 527 temp = NULL; 528 } 529 } 530 else if (!strcmp(argv[i], "--path")) 531 { 532 i ++; 533 if (i >= argc) 534 { 535 _cupsLangPrintf(stderr, 536 _("ippfind: Missing regular expression after %s."), 537 "--path"); 538 show_usage(); 539 } 540 541 if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i], 542 NULL)) == NULL) 543 return (IPPFIND_EXIT_MEMORY); 544 } 545 else if (!strcmp(argv[i], "--port")) 546 { 547 i ++; 548 if (i >= argc) 549 { 550 _cupsLangPrintf(stderr, 551 _("ippfind: Expected port range after %s."), 552 "--port"); 553 show_usage(); 554 } 555 556 if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL, 557 NULL)) == NULL) 558 return (IPPFIND_EXIT_MEMORY); 559 } 560 else if (!strcmp(argv[i], "--print")) 561 { 562 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, 563 NULL)) == NULL) 564 return (IPPFIND_EXIT_MEMORY); 565 566 have_output = 1; 567 } 568 else if (!strcmp(argv[i], "--print-name")) 569 { 570 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, 571 NULL)) == NULL) 572 return (IPPFIND_EXIT_MEMORY); 573 574 have_output = 1; 575 } 576 else if (!strcmp(argv[i], "--quiet")) 577 { 578 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, 579 NULL)) == NULL) 580 return (IPPFIND_EXIT_MEMORY); 581 582 have_output = 1; 583 } 584 else if (!strcmp(argv[i], "--remote")) 585 { 586 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, 587 NULL)) == NULL) 588 return (IPPFIND_EXIT_MEMORY); 589 } 590 else if (!strcmp(argv[i], "--true")) 591 { 592 if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i], 593 NULL)) == NULL) 594 return (IPPFIND_EXIT_MEMORY); 595 } 596 else if (!strcmp(argv[i], "--txt")) 597 { 598 i ++; 599 if (i >= argc) 600 { 601 _cupsLangPrintf(stderr, _("ippfind: Expected key name after %s."), 602 "--txt"); 603 show_usage(); 604 } 605 606 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL, 607 NULL)) == NULL) 608 return (IPPFIND_EXIT_MEMORY); 609 } 610 else if (!strncmp(argv[i], "--txt-", 6)) 611 { 612 const char *key = argv[i] + 6;/* TXT key */ 613 614 i ++; 615 if (i >= argc) 616 { 617 _cupsLangPrintf(stderr, 618 _("ippfind: Missing regular expression after %s."), 619 argv[i - 1]); 620 show_usage(); 621 } 622 623 if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i], 624 NULL)) == NULL) 625 return (IPPFIND_EXIT_MEMORY); 626 } 627 else if (!strcmp(argv[i], "--uri")) 628 { 629 i ++; 630 if (i >= argc) 631 { 632 _cupsLangPrintf(stderr, 633 _("ippfind: Missing regular expression after %s."), 634 "--uri"); 635 show_usage(); 636 } 637 638 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i], 639 NULL)) == NULL) 640 return (IPPFIND_EXIT_MEMORY); 641 } 642 else if (!strcmp(argv[i], "--version")) 643 { 644 show_version(); 645 } 646 else 647 { 648 _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), 649 "ippfind", argv[i]); 650 show_usage(); 651 } 652 653 if (temp) 654 { 655 /* 656 * Add new expression... 657 */ 658 659 if (logic == IPPFIND_OP_AND && 660 current && current->prev && 661 parent && parent->op != IPPFIND_OP_AND) 662 { 663 /* 664 * Need to re-group "current" in a new AND node... 665 */ 666 667 ippfind_expr_t *tempand; /* Temporary AND node */ 668 669 if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, 670 NULL)) == NULL) 671 return (IPPFIND_EXIT_MEMORY); 672 673 /* 674 * Replace "current" with new AND node at the end of this list... 675 */ 676 677 current->prev->next = tempand; 678 tempand->prev = current->prev; 679 tempand->parent = parent; 680 681 /* 682 * Add "current to the new AND node... 683 */ 684 685 tempand->child = current; 686 current->parent = tempand; 687 current->prev = NULL; 688 parent = tempand; 689 } 690 691 /* 692 * Add the new node at current level... 693 */ 694 695 temp->parent = parent; 696 temp->prev = current; 697 698 if (current) 699 current->next = temp; 700 else if (parent) 701 parent->child = temp; 702 else 703 expressions = temp; 704 705 current = temp; 706 invert = 0; 707 logic = IPPFIND_OP_AND; 708 temp = NULL; 709 } 710 } 711 else 712 { 713 /* 714 * Parse -o options 715 */ 716 717 for (opt = argv[i] + 1; *opt; opt ++) 718 { 719 switch (*opt) 720 { 721 case '4' : 722 address_family = AF_INET; 723 break; 724 725 case '6' : 726 address_family = AF_INET6; 727 break; 728 729 case 'P' : 730 i ++; 731 if (i >= argc) 732 { 733 _cupsLangPrintf(stderr, 734 _("ippfind: Expected port range after %s."), 735 "-P"); 736 show_usage(); 737 } 738 739 if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], 740 NULL, NULL)) == NULL) 741 return (IPPFIND_EXIT_MEMORY); 742 break; 743 744 case 'T' : 745 i ++; 746 if (i >= argc) 747 { 748 _cupsLangPrintf(stderr, 749 _("%s: Missing timeout for \"-T\"."), 750 "ippfind"); 751 show_usage(); 752 } 753 754 bonjour_timeout = atof(argv[i]); 755 break; 756 757 case 'V' : 758 i ++; 759 if (i >= argc) 760 { 761 _cupsLangPrintf(stderr, 762 _("%s: Missing version for \"-V\"."), 763 "ippfind"); 764 show_usage(); 765 } 766 767 if (!strcmp(argv[i], "1.1")) 768 ipp_version = 11; 769 else if (!strcmp(argv[i], "2.0")) 770 ipp_version = 20; 771 else if (!strcmp(argv[i], "2.1")) 772 ipp_version = 21; 773 else if (!strcmp(argv[i], "2.2")) 774 ipp_version = 22; 775 else 776 { 777 _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), 778 "ippfind", argv[i]); 779 show_usage(); 780 } 781 break; 782 783 case 'd' : 784 i ++; 785 if (i >= argc) 786 { 787 _cupsLangPrintf(stderr, 788 _("ippfind: Missing regular expression after " 789 "%s."), "-d"); 790 show_usage(); 791 } 792 793 if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, 794 argv[i], NULL)) == NULL) 795 return (IPPFIND_EXIT_MEMORY); 796 break; 797 798 case 'h' : 799 i ++; 800 if (i >= argc) 801 { 802 _cupsLangPrintf(stderr, 803 _("ippfind: Missing regular expression after " 804 "%s."), "-h"); 805 show_usage(); 806 } 807 808 if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, 809 argv[i], NULL)) == NULL) 810 return (IPPFIND_EXIT_MEMORY); 811 break; 812 813 case 'l' : 814 if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, 815 NULL)) == NULL) 816 return (IPPFIND_EXIT_MEMORY); 817 818 have_output = 1; 819 break; 820 821 case 'n' : 822 i ++; 823 if (i >= argc) 824 { 825 _cupsLangPrintf(stderr, 826 _("ippfind: Missing regular expression after " 827 "%s."), "-n"); 828 show_usage(); 829 } 830 831 if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, 832 argv[i], NULL)) == NULL) 833 return (IPPFIND_EXIT_MEMORY); 834 break; 835 836 case 'p' : 837 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, 838 NULL)) == NULL) 839 return (IPPFIND_EXIT_MEMORY); 840 841 have_output = 1; 842 break; 843 844 case 'q' : 845 if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, 846 NULL)) == NULL) 847 return (IPPFIND_EXIT_MEMORY); 848 849 have_output = 1; 850 break; 851 852 case 'r' : 853 if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, 854 NULL)) == NULL) 855 return (IPPFIND_EXIT_MEMORY); 856 break; 857 858 case 's' : 859 if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, 860 NULL)) == NULL) 861 return (IPPFIND_EXIT_MEMORY); 862 863 have_output = 1; 864 break; 865 866 case 't' : 867 i ++; 868 if (i >= argc) 869 { 870 _cupsLangPrintf(stderr, 871 _("ippfind: Missing key name after %s."), 872 "-t"); 873 show_usage(); 874 } 875 876 if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], 877 NULL, NULL)) == NULL) 878 return (IPPFIND_EXIT_MEMORY); 879 break; 880 881 case 'u' : 882 i ++; 883 if (i >= argc) 884 { 885 _cupsLangPrintf(stderr, 886 _("ippfind: Missing regular expression after " 887 "%s."), "-u"); 888 show_usage(); 889 } 890 891 if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, 892 argv[i], NULL)) == NULL) 893 return (IPPFIND_EXIT_MEMORY); 894 break; 895 896 case 'x' : 897 i ++; 898 if (i >= argc) 899 { 900 _cupsLangPrintf(stderr, 901 _("ippfind: Missing program after %s."), 902 "-x"); 903 show_usage(); 904 } 905 906 if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, 907 argv + i)) == NULL) 908 return (IPPFIND_EXIT_MEMORY); 909 910 while (i < argc) 911 if (!strcmp(argv[i], ";")) 912 break; 913 else 914 i ++; 915 916 if (i >= argc) 917 { 918 _cupsLangPrintf(stderr, 919 _("ippfind: Missing semi-colon after %s."), 920 "-x"); 921 show_usage(); 922 } 923 924 have_output = 1; 925 break; 926 927 default : 928 _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), 929 "ippfind", *opt); 930 show_usage(); 931 } 932 933 if (temp) 934 { 935 /* 936 * Add new expression... 937 */ 938 939 if (logic == IPPFIND_OP_AND && 940 current && current->prev && 941 parent && parent->op != IPPFIND_OP_AND) 942 { 943 /* 944 * Need to re-group "current" in a new AND node... 945 */ 946 947 ippfind_expr_t *tempand; /* Temporary AND node */ 948 949 if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, 950 NULL)) == NULL) 951 return (IPPFIND_EXIT_MEMORY); 952 953 /* 954 * Replace "current" with new AND node at the end of this list... 955 */ 956 957 current->prev->next = tempand; 958 tempand->prev = current->prev; 959 tempand->parent = parent; 960 961 /* 962 * Add "current to the new AND node... 963 */ 964 965 tempand->child = current; 966 current->parent = tempand; 967 current->prev = NULL; 968 parent = tempand; 969 } 970 971 /* 972 * Add the new node at current level... 973 */ 974 975 temp->parent = parent; 976 temp->prev = current; 977 978 if (current) 979 current->next = temp; 980 else if (parent) 981 parent->child = temp; 982 else 983 expressions = temp; 984 985 current = temp; 986 invert = 0; 987 logic = IPPFIND_OP_AND; 988 temp = NULL; 989 } 990 } 991 } 992 } 993 else if (!strcmp(argv[i], "(")) 994 { 995 if (num_parens >= 100) 996 { 997 _cupsLangPuts(stderr, _("ippfind: Too many parenthesis.")); 998 show_usage(); 999 } 1000 1001 if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL) 1002 return (IPPFIND_EXIT_MEMORY); 1003 1004 parens[num_parens++] = temp; 1005 1006 if (current) 1007 { 1008 temp->parent = current->parent; 1009 current->next = temp; 1010 temp->prev = current; 1011 } 1012 else 1013 expressions = temp; 1014 1015 parent = temp; 1016 current = NULL; 1017 invert = 0; 1018 logic = IPPFIND_OP_AND; 1019 } 1020 else if (!strcmp(argv[i], ")")) 1021 { 1022 if (num_parens <= 0) 1023 { 1024 _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis.")); 1025 show_usage(); 1026 } 1027 1028 current = parens[--num_parens]; 1029 parent = current->parent; 1030 invert = 0; 1031 logic = IPPFIND_OP_AND; 1032 } 1033 else if (!strcmp(argv[i], "!")) 1034 { 1035 invert = 1; 1036 } 1037 else 1038 { 1039 /* 1040 * _regtype._tcp[,subtype][.domain] 1041 * 1042 * OR 1043 * 1044 * service-name[._regtype._tcp[.domain]] 1045 */ 1046 1047 cupsArrayAdd(searches, argv[i]); 1048 } 1049 } 1050 1051 if (num_parens > 0) 1052 { 1053 _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis.")); 1054 show_usage(); 1055 } 1056 1057 if (!have_output) 1058 { 1059 /* 1060 * Add an implicit --print-uri to the end... 1061 */ 1062 1063 if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL) 1064 return (IPPFIND_EXIT_MEMORY); 1065 1066 if (current) 1067 { 1068 while (current->parent) 1069 current = current->parent; 1070 1071 current->next = temp; 1072 temp->prev = current; 1073 } 1074 else 1075 expressions = temp; 1076 } 1077 1078 if (cupsArrayCount(searches) == 0) 1079 { 1080 /* 1081 * Add an implicit browse for IPP printers ("_ipp._tcp")... 1082 */ 1083 1084 cupsArrayAdd(searches, "_ipp._tcp"); 1085 } 1086 1087 if (getenv("IPPFIND_DEBUG")) 1088 { 1089 int indent = 4; /* Indentation */ 1090 1091 puts("Expression tree:"); 1092 current = expressions; 1093 while (current) 1094 { 1095 /* 1096 * Print the current node... 1097 */ 1098 1099 printf("%*s%s%s\n", indent, "", current->invert ? "!" : "", 1100 ops[current->op]); 1101 1102 /* 1103 * Advance to the next node... 1104 */ 1105 1106 if (current->child) 1107 { 1108 current = current->child; 1109 indent += 4; 1110 } 1111 else if (current->next) 1112 current = current->next; 1113 else if (current->parent) 1114 { 1115 while (current->parent) 1116 { 1117 indent -= 4; 1118 current = current->parent; 1119 if (current->next) 1120 break; 1121 } 1122 1123 current = current->next; 1124 } 1125 else 1126 current = NULL; 1127 } 1128 1129 puts("\nSearch items:"); 1130 for (search = (const char *)cupsArrayFirst(searches); 1131 search; 1132 search = (const char *)cupsArrayNext(searches)) 1133 printf(" %s\n", search); 1134 } 1135 1136 /* 1137 * Start up browsing/resolving... 1138 */ 1139 1140#ifdef HAVE_DNSSD 1141 if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError) 1142 { 1143 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), 1144 dnssd_error_string(err)); 1145 return (IPPFIND_EXIT_BONJOUR); 1146 } 1147 1148#elif defined(HAVE_AVAHI) 1149 if ((avahi_poll = avahi_simple_poll_new()) == NULL) 1150 { 1151 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), 1152 strerror(errno)); 1153 return (IPPFIND_EXIT_BONJOUR); 1154 } 1155 1156 avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL); 1157 1158 avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll), 1159 0, client_callback, avahi_poll, &err); 1160 if (!avahi_client) 1161 { 1162 _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), 1163 dnssd_error_string(err)); 1164 return (IPPFIND_EXIT_BONJOUR); 1165 } 1166#endif /* HAVE_DNSSD */ 1167 1168 for (search = (const char *)cupsArrayFirst(searches); 1169 search; 1170 search = (const char *)cupsArrayNext(searches)) 1171 { 1172 char buf[1024], /* Full name string */ 1173 *name = NULL, /* Service instance name */ 1174 *regtype, /* Registration type */ 1175 *domain; /* Domain, if any */ 1176 1177 strlcpy(buf, search, sizeof(buf)); 1178 if (buf[0] == '_') 1179 { 1180 regtype = buf; 1181 } 1182 else if ((regtype = strstr(buf, "._")) != NULL) 1183 { 1184 name = buf; 1185 *regtype++ = '\0'; 1186 } 1187 else 1188 { 1189 name = buf; 1190 regtype = "_ipp._tcp"; 1191 } 1192 1193 for (domain = regtype; *domain; domain ++) 1194 if (*domain == '.' && domain[1] != '_') 1195 { 1196 *domain++ = '\0'; 1197 break; 1198 } 1199 1200 if (!*domain) 1201 domain = NULL; 1202 1203 if (name) 1204 { 1205 /* 1206 * Resolve the given service instance name, regtype, and domain... 1207 */ 1208 1209 if (!domain) 1210 domain = "local."; 1211 1212 service = get_service(services, name, regtype, domain); 1213 1214#ifdef HAVE_DNSSD 1215 service->ref = dnssd_ref; 1216 err = DNSServiceResolve(&(service->ref), 1217 kDNSServiceFlagsShareConnection, 0, name, 1218 regtype, domain, resolve_callback, 1219 service); 1220 1221#elif defined(HAVE_AVAHI) 1222 service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC, 1223 AVAHI_PROTO_UNSPEC, name, 1224 regtype, domain, 1225 AVAHI_PROTO_UNSPEC, 0, 1226 resolve_callback, service); 1227 if (service->ref) 1228 err = 0; 1229 else 1230 err = avahi_client_errno(avahi_client); 1231#endif /* HAVE_DNSSD */ 1232 } 1233 else 1234 { 1235 /* 1236 * Browse for services of the given type... 1237 */ 1238 1239#ifdef HAVE_DNSSD 1240 DNSServiceRef ref; /* Browse reference */ 1241 1242 ref = dnssd_ref; 1243 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype, 1244 domain, browse_callback, services); 1245 1246 if (!err) 1247 { 1248 ref = dnssd_ref; 1249 err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 1250 kDNSServiceInterfaceIndexLocalOnly, regtype, 1251 domain, browse_local_callback, services); 1252 } 1253 1254#elif defined(HAVE_AVAHI) 1255 if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC, 1256 AVAHI_PROTO_UNSPEC, regtype, domain, 0, 1257 browse_callback, services)) 1258 err = 0; 1259 else 1260 err = avahi_client_errno(avahi_client); 1261#endif /* HAVE_DNSSD */ 1262 } 1263 1264 if (err) 1265 { 1266 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), 1267 dnssd_error_string(err)); 1268 1269 if (name) 1270 printf("name=\"%s\"\n", name); 1271 1272 printf("regtype=\"%s\"\n", regtype); 1273 1274 if (domain) 1275 printf("domain=\"%s\"\n", domain); 1276 1277 return (IPPFIND_EXIT_BONJOUR); 1278 } 1279 } 1280 1281 /* 1282 * Process browse/resolve requests... 1283 */ 1284 1285 if (bonjour_timeout > 1.0) 1286 endtime = get_time() + bonjour_timeout; 1287 else 1288 endtime = get_time() + 300.0; 1289 1290 while (get_time() < endtime) 1291 { 1292 int process = 0; /* Process services? */ 1293 1294#ifdef HAVE_DNSSD 1295 int fd = DNSServiceRefSockFD(dnssd_ref); 1296 /* File descriptor for DNS-SD */ 1297 1298 FD_ZERO(&sinput); 1299 FD_SET(fd, &sinput); 1300 1301 stimeout.tv_sec = 0; 1302 stimeout.tv_usec = 500000; 1303 1304 if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0) 1305 continue; 1306 1307 if (FD_ISSET(fd, &sinput)) 1308 { 1309 /* 1310 * Process responses... 1311 */ 1312 1313 DNSServiceProcessResult(dnssd_ref); 1314 } 1315 else 1316 { 1317 /* 1318 * Time to process services... 1319 */ 1320 1321 process = 1; 1322 } 1323 1324#elif defined(HAVE_AVAHI) 1325 avahi_got_data = 0; 1326 1327 if (avahi_simple_poll_iterate(avahi_poll, 500) > 0) 1328 { 1329 /* 1330 * We've been told to exit the loop. Perhaps the connection to 1331 * Avahi failed. 1332 */ 1333 1334 return (IPPFIND_EXIT_BONJOUR); 1335 } 1336 1337 if (!avahi_got_data) 1338 { 1339 /* 1340 * Time to process services... 1341 */ 1342 1343 process = 1; 1344 } 1345#endif /* HAVE_DNSSD */ 1346 1347 if (process) 1348 { 1349 /* 1350 * Process any services that we have found... 1351 */ 1352 1353 int active = 0, /* Number of active resolves */ 1354 resolved = 0, /* Number of resolved services */ 1355 processed = 0; /* Number of processed services */ 1356 1357 for (service = (ippfind_srv_t *)cupsArrayFirst(services); 1358 service; 1359 service = (ippfind_srv_t *)cupsArrayNext(services)) 1360 { 1361 if (service->is_processed) 1362 processed ++; 1363 1364 if (service->is_resolved) 1365 resolved ++; 1366 1367 if (!service->ref && !service->is_resolved) 1368 { 1369 /* 1370 * Found a service, now resolve it (but limit to 50 active resolves...) 1371 */ 1372 1373 if (active < 50) 1374 { 1375#ifdef HAVE_DNSSD 1376 service->ref = dnssd_ref; 1377 err = DNSServiceResolve(&(service->ref), 1378 kDNSServiceFlagsShareConnection, 0, 1379 service->name, service->regtype, 1380 service->domain, resolve_callback, 1381 service); 1382 1383#elif defined(HAVE_AVAHI) 1384 service->ref = avahi_service_resolver_new(avahi_client, 1385 AVAHI_IF_UNSPEC, 1386 AVAHI_PROTO_UNSPEC, 1387 service->name, 1388 service->regtype, 1389 service->domain, 1390 AVAHI_PROTO_UNSPEC, 0, 1391 resolve_callback, 1392 service); 1393 if (service->ref) 1394 err = 0; 1395 else 1396 err = avahi_client_errno(avahi_client); 1397#endif /* HAVE_DNSSD */ 1398 1399 if (err) 1400 { 1401 _cupsLangPrintf(stderr, 1402 _("ippfind: Unable to browse or resolve: %s"), 1403 dnssd_error_string(err)); 1404 return (IPPFIND_EXIT_BONJOUR); 1405 } 1406 1407 active ++; 1408 } 1409 } 1410 else if (service->is_resolved && !service->is_processed) 1411 { 1412 /* 1413 * Resolved, not process this service against the expressions... 1414 */ 1415 1416 if (service->ref) 1417 { 1418#ifdef HAVE_DNSSD 1419 DNSServiceRefDeallocate(service->ref); 1420#else 1421 avahi_service_resolver_free(service->ref); 1422#endif /* HAVE_DNSSD */ 1423 1424 service->ref = NULL; 1425 } 1426 1427 if (eval_expr(service, expressions)) 1428 status = IPPFIND_EXIT_TRUE; 1429 1430 service->is_processed = 1; 1431 } 1432 else if (service->ref) 1433 active ++; 1434 } 1435 1436 /* 1437 * If we have processed all services we have discovered, then we are done. 1438 */ 1439 1440 if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0) 1441 break; 1442 } 1443 } 1444 1445 if (bonjour_error) 1446 return (IPPFIND_EXIT_BONJOUR); 1447 else 1448 return (status); 1449} 1450 1451 1452#ifdef HAVE_DNSSD 1453/* 1454 * 'browse_callback()' - Browse devices. 1455 */ 1456 1457static void DNSSD_API 1458browse_callback( 1459 DNSServiceRef sdRef, /* I - Service reference */ 1460 DNSServiceFlags flags, /* I - Option flags */ 1461 uint32_t interfaceIndex, /* I - Interface number */ 1462 DNSServiceErrorType errorCode, /* I - Error, if any */ 1463 const char *serviceName, /* I - Name of service/device */ 1464 const char *regtype, /* I - Type of service */ 1465 const char *replyDomain, /* I - Service domain */ 1466 void *context) /* I - Services array */ 1467{ 1468 /* 1469 * Only process "add" data... 1470 */ 1471 1472 (void)sdRef; 1473 (void)interfaceIndex; 1474 1475 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) 1476 return; 1477 1478 /* 1479 * Get the device... 1480 */ 1481 1482 get_service((cups_array_t *)context, serviceName, regtype, replyDomain); 1483} 1484 1485 1486/* 1487 * 'browse_local_callback()' - Browse local devices. 1488 */ 1489 1490static void DNSSD_API 1491browse_local_callback( 1492 DNSServiceRef sdRef, /* I - Service reference */ 1493 DNSServiceFlags flags, /* I - Option flags */ 1494 uint32_t interfaceIndex, /* I - Interface number */ 1495 DNSServiceErrorType errorCode, /* I - Error, if any */ 1496 const char *serviceName, /* I - Name of service/device */ 1497 const char *regtype, /* I - Type of service */ 1498 const char *replyDomain, /* I - Service domain */ 1499 void *context) /* I - Services array */ 1500{ 1501 ippfind_srv_t *service; /* Service */ 1502 1503 1504 /* 1505 * Only process "add" data... 1506 */ 1507 1508 (void)sdRef; 1509 (void)interfaceIndex; 1510 1511 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) 1512 return; 1513 1514 /* 1515 * Get the device... 1516 */ 1517 1518 service = get_service((cups_array_t *)context, serviceName, regtype, 1519 replyDomain); 1520 service->is_local = 1; 1521} 1522#endif /* HAVE_DNSSD */ 1523 1524 1525#ifdef HAVE_AVAHI 1526/* 1527 * 'browse_callback()' - Browse devices. 1528 */ 1529 1530static void 1531browse_callback( 1532 AvahiServiceBrowser *browser, /* I - Browser */ 1533 AvahiIfIndex interface, /* I - Interface index (unused) */ 1534 AvahiProtocol protocol, /* I - Network protocol (unused) */ 1535 AvahiBrowserEvent event, /* I - What happened */ 1536 const char *name, /* I - Service name */ 1537 const char *type, /* I - Registration type */ 1538 const char *domain, /* I - Domain */ 1539 AvahiLookupResultFlags flags, /* I - Flags */ 1540 void *context) /* I - Services array */ 1541{ 1542 AvahiClient *client = avahi_service_browser_get_client(browser); 1543 /* Client information */ 1544 ippfind_srv_t *service; /* Service information */ 1545 1546 1547 (void)interface; 1548 (void)protocol; 1549 (void)context; 1550 1551 switch (event) 1552 { 1553 case AVAHI_BROWSER_FAILURE: 1554 fprintf(stderr, "DEBUG: browse_callback: %s\n", 1555 avahi_strerror(avahi_client_errno(client))); 1556 bonjour_error = 1; 1557 avahi_simple_poll_quit(avahi_poll); 1558 break; 1559 1560 case AVAHI_BROWSER_NEW: 1561 /* 1562 * This object is new on the network. Create a device entry for it if 1563 * it doesn't yet exist. 1564 */ 1565 1566 service = get_service((cups_array_t *)context, name, type, domain); 1567 1568 if (flags & AVAHI_LOOKUP_RESULT_LOCAL) 1569 service->is_local = 1; 1570 break; 1571 1572 case AVAHI_BROWSER_REMOVE: 1573 case AVAHI_BROWSER_ALL_FOR_NOW: 1574 case AVAHI_BROWSER_CACHE_EXHAUSTED: 1575 break; 1576 } 1577} 1578 1579 1580/* 1581 * 'client_callback()' - Avahi client callback function. 1582 */ 1583 1584static void 1585client_callback( 1586 AvahiClient *client, /* I - Client information (unused) */ 1587 AvahiClientState state, /* I - Current state */ 1588 void *context) /* I - User data (unused) */ 1589{ 1590 (void)client; 1591 (void)context; 1592 1593 /* 1594 * If the connection drops, quit. 1595 */ 1596 1597 if (state == AVAHI_CLIENT_FAILURE) 1598 { 1599 fputs("DEBUG: Avahi connection failed.\n", stderr); 1600 bonjour_error = 1; 1601 avahi_simple_poll_quit(avahi_poll); 1602 } 1603} 1604#endif /* HAVE_AVAHI */ 1605 1606 1607/* 1608 * 'compare_services()' - Compare two devices. 1609 */ 1610 1611static int /* O - Result of comparison */ 1612compare_services(ippfind_srv_t *a, /* I - First device */ 1613 ippfind_srv_t *b) /* I - Second device */ 1614{ 1615 return (strcmp(a->name, b->name)); 1616} 1617 1618 1619/* 1620 * 'dnssd_error_string()' - Return an error string for an error code. 1621 */ 1622 1623static const char * /* O - Error message */ 1624dnssd_error_string(int error) /* I - Error number */ 1625{ 1626# ifdef HAVE_DNSSD 1627 switch (error) 1628 { 1629 case kDNSServiceErr_NoError : 1630 return ("OK."); 1631 1632 default : 1633 case kDNSServiceErr_Unknown : 1634 return ("Unknown error."); 1635 1636 case kDNSServiceErr_NoSuchName : 1637 return ("Service not found."); 1638 1639 case kDNSServiceErr_NoMemory : 1640 return ("Out of memory."); 1641 1642 case kDNSServiceErr_BadParam : 1643 return ("Bad parameter."); 1644 1645 case kDNSServiceErr_BadReference : 1646 return ("Bad service reference."); 1647 1648 case kDNSServiceErr_BadState : 1649 return ("Bad state."); 1650 1651 case kDNSServiceErr_BadFlags : 1652 return ("Bad flags."); 1653 1654 case kDNSServiceErr_Unsupported : 1655 return ("Unsupported."); 1656 1657 case kDNSServiceErr_NotInitialized : 1658 return ("Not initialized."); 1659 1660 case kDNSServiceErr_AlreadyRegistered : 1661 return ("Already registered."); 1662 1663 case kDNSServiceErr_NameConflict : 1664 return ("Name conflict."); 1665 1666 case kDNSServiceErr_Invalid : 1667 return ("Invalid name."); 1668 1669 case kDNSServiceErr_Firewall : 1670 return ("Firewall prevents registration."); 1671 1672 case kDNSServiceErr_Incompatible : 1673 return ("Client library incompatible."); 1674 1675 case kDNSServiceErr_BadInterfaceIndex : 1676 return ("Bad interface index."); 1677 1678 case kDNSServiceErr_Refused : 1679 return ("Server prevents registration."); 1680 1681 case kDNSServiceErr_NoSuchRecord : 1682 return ("Record not found."); 1683 1684 case kDNSServiceErr_NoAuth : 1685 return ("Authentication required."); 1686 1687 case kDNSServiceErr_NoSuchKey : 1688 return ("Encryption key not found."); 1689 1690 case kDNSServiceErr_NATTraversal : 1691 return ("Unable to traverse NAT boundary."); 1692 1693 case kDNSServiceErr_DoubleNAT : 1694 return ("Unable to traverse double-NAT boundary."); 1695 1696 case kDNSServiceErr_BadTime : 1697 return ("Bad system time."); 1698 1699 case kDNSServiceErr_BadSig : 1700 return ("Bad signature."); 1701 1702 case kDNSServiceErr_BadKey : 1703 return ("Bad encryption key."); 1704 1705 case kDNSServiceErr_Transient : 1706 return ("Transient error occurred - please try again."); 1707 1708 case kDNSServiceErr_ServiceNotRunning : 1709 return ("Server not running."); 1710 1711 case kDNSServiceErr_NATPortMappingUnsupported : 1712 return ("NAT doesn't support NAT-PMP or UPnP."); 1713 1714 case kDNSServiceErr_NATPortMappingDisabled : 1715 return ("NAT supports NAT-PNP or UPnP but it is disabled."); 1716 1717 case kDNSServiceErr_NoRouter : 1718 return ("No Internet/default router configured."); 1719 1720 case kDNSServiceErr_PollingMode : 1721 return ("Service polling mode error."); 1722 1723#ifndef WIN32 1724 case kDNSServiceErr_Timeout : 1725 return ("Service timeout."); 1726#endif /* !WIN32 */ 1727 } 1728 1729# elif defined(HAVE_AVAHI) 1730 return (avahi_strerror(error)); 1731# endif /* HAVE_DNSSD */ 1732} 1733 1734 1735/* 1736 * 'eval_expr()' - Evaluate the expressions against the specified service. 1737 * 1738 * Returns 1 for true and 0 for false. 1739 */ 1740 1741static int /* O - Result of evaluation */ 1742eval_expr(ippfind_srv_t *service, /* I - Service */ 1743 ippfind_expr_t *expressions) /* I - Expressions */ 1744{ 1745 int logic, /* Logical operation */ 1746 result; /* Result of current expression */ 1747 ippfind_expr_t *expression; /* Current expression */ 1748 const char *val; /* TXT value */ 1749 1750 /* 1751 * Loop through the expressions... 1752 */ 1753 1754 if (expressions && expressions->parent) 1755 logic = expressions->parent->op; 1756 else 1757 logic = IPPFIND_OP_AND; 1758 1759 for (expression = expressions; expression; expression = expression->next) 1760 { 1761 switch (expression->op) 1762 { 1763 default : 1764 case IPPFIND_OP_AND : 1765 case IPPFIND_OP_OR : 1766 if (expression->child) 1767 result = eval_expr(service, expression->child); 1768 else 1769 result = expression->op == IPPFIND_OP_AND; 1770 break; 1771 case IPPFIND_OP_TRUE : 1772 result = 1; 1773 break; 1774 case IPPFIND_OP_FALSE : 1775 result = 0; 1776 break; 1777 case IPPFIND_OP_IS_LOCAL : 1778 result = service->is_local; 1779 break; 1780 case IPPFIND_OP_IS_REMOTE : 1781 result = !service->is_local; 1782 break; 1783 case IPPFIND_OP_DOMAIN_REGEX : 1784 result = !regexec(&(expression->re), service->domain, 0, NULL, 0); 1785 break; 1786 case IPPFIND_OP_NAME_REGEX : 1787 result = !regexec(&(expression->re), service->name, 0, NULL, 0); 1788 break; 1789 case IPPFIND_OP_HOST_REGEX : 1790 result = !regexec(&(expression->re), service->host, 0, NULL, 0); 1791 break; 1792 case IPPFIND_OP_PORT_RANGE : 1793 result = service->port >= expression->range[0] && 1794 service->port <= expression->range[1]; 1795 break; 1796 case IPPFIND_OP_PATH_REGEX : 1797 result = !regexec(&(expression->re), service->resource, 0, NULL, 0); 1798 break; 1799 case IPPFIND_OP_TXT_EXISTS : 1800 result = cupsGetOption(expression->key, service->num_txt, 1801 service->txt) != NULL; 1802 break; 1803 case IPPFIND_OP_TXT_REGEX : 1804 val = cupsGetOption(expression->key, service->num_txt, 1805 service->txt); 1806 if (val) 1807 result = !regexec(&(expression->re), val, 0, NULL, 0); 1808 else 1809 result = 0; 1810 1811 if (getenv("IPPFIND_DEBUG")) 1812 printf("TXT_REGEX of \"%s\": %d\n", val, result); 1813 break; 1814 case IPPFIND_OP_URI_REGEX : 1815 result = !regexec(&(expression->re), service->uri, 0, NULL, 0); 1816 break; 1817 case IPPFIND_OP_EXEC : 1818 result = exec_program(service, expression->num_args, 1819 expression->args); 1820 break; 1821 case IPPFIND_OP_LIST : 1822 result = list_service(service); 1823 break; 1824 case IPPFIND_OP_PRINT_NAME : 1825 _cupsLangPuts(stdout, service->name); 1826 result = 1; 1827 break; 1828 case IPPFIND_OP_PRINT_URI : 1829 _cupsLangPuts(stdout, service->uri); 1830 result = 1; 1831 break; 1832 case IPPFIND_OP_QUIET : 1833 result = 1; 1834 break; 1835 } 1836 1837 if (expression->invert) 1838 result = !result; 1839 1840 if (logic == IPPFIND_OP_AND && !result) 1841 return (0); 1842 else if (logic == IPPFIND_OP_OR && result) 1843 return (1); 1844 } 1845 1846 return (logic == IPPFIND_OP_AND); 1847} 1848 1849 1850/* 1851 * 'exec_program()' - Execute a program for a service. 1852 */ 1853 1854static int /* O - 1 if program terminated 1855 successfully, 0 otherwise. */ 1856exec_program(ippfind_srv_t *service, /* I - Service */ 1857 int num_args, /* I - Number of command-line args */ 1858 char **args) /* I - Command-line arguments */ 1859{ 1860 char **myargv, /* Command-line arguments */ 1861 **myenvp, /* Environment variables */ 1862 *ptr, /* Pointer into variable */ 1863 domain[1024], /* IPPFIND_SERVICE_DOMAIN */ 1864 hostname[1024], /* IPPFIND_SERVICE_HOSTNAME */ 1865 name[256], /* IPPFIND_SERVICE_NAME */ 1866 port[32], /* IPPFIND_SERVICE_PORT */ 1867 regtype[256], /* IPPFIND_SERVICE_REGTYPE */ 1868 scheme[128], /* IPPFIND_SERVICE_SCHEME */ 1869 uri[1024], /* IPPFIND_SERVICE_URI */ 1870 txt[100][256]; /* IPPFIND_TXT_foo */ 1871 int i, /* Looping var */ 1872 myenvc, /* Number of environment variables */ 1873 status; /* Exit status of program */ 1874#ifndef WIN32 1875 char program[1024]; /* Program to execute */ 1876 int pid; /* Process ID */ 1877#endif /* !WIN32 */ 1878 1879 1880 /* 1881 * Environment variables... 1882 */ 1883 1884 snprintf(domain, sizeof(domain), "IPPFIND_SERVICE_DOMAIN=%s", 1885 service->domain); 1886 snprintf(hostname, sizeof(hostname), "IPPFIND_SERVICE_HOSTNAME=%s", 1887 service->host); 1888 snprintf(name, sizeof(name), "IPPFIND_SERVICE_NAME=%s", service->name); 1889 snprintf(port, sizeof(port), "IPPFIND_SERVICE_PORT=%d", service->port); 1890 snprintf(regtype, sizeof(regtype), "IPPFIND_SERVICE_REGTYPE=%s", 1891 service->regtype); 1892 snprintf(scheme, sizeof(scheme), "IPPFIND_SERVICE_SCHEME=%s", 1893 !strncmp(service->regtype, "_http._tcp", 10) ? "http" : 1894 !strncmp(service->regtype, "_https._tcp", 11) ? "https" : 1895 !strncmp(service->regtype, "_ipp._tcp", 9) ? "ipp" : 1896 !strncmp(service->regtype, "_ipps._tcp", 10) ? "ipps" : "lpd"); 1897 snprintf(uri, sizeof(uri), "IPPFIND_SERVICE_URI=%s", service->uri); 1898 for (i = 0; i < service->num_txt && i < 100; i ++) 1899 { 1900 snprintf(txt[i], sizeof(txt[i]), "IPPFIND_TXT_%s=%s", service->txt[i].name, 1901 service->txt[i].value); 1902 for (ptr = txt[i] + 12; *ptr && *ptr != '='; ptr ++) 1903 *ptr = (char)_cups_toupper(*ptr); 1904 } 1905 1906 for (i = 0, myenvc = 7 + service->num_txt; environ[i]; i ++) 1907 if (strncmp(environ[i], "IPPFIND_", 8)) 1908 myenvc ++; 1909 1910 if ((myenvp = calloc(sizeof(char *), (size_t)(myenvc + 1))) == NULL) 1911 { 1912 _cupsLangPuts(stderr, _("ippfind: Out of memory.")); 1913 exit(IPPFIND_EXIT_MEMORY); 1914 } 1915 1916 for (i = 0, myenvc = 0; environ[i]; i ++) 1917 if (strncmp(environ[i], "IPPFIND_", 8)) 1918 myenvp[myenvc++] = environ[i]; 1919 1920 myenvp[myenvc++] = domain; 1921 myenvp[myenvc++] = hostname; 1922 myenvp[myenvc++] = name; 1923 myenvp[myenvc++] = port; 1924 myenvp[myenvc++] = regtype; 1925 myenvp[myenvc++] = scheme; 1926 myenvp[myenvc++] = uri; 1927 1928 for (i = 0; i < service->num_txt && i < 100; i ++) 1929 myenvp[myenvc++] = txt[i]; 1930 1931 /* 1932 * Allocate and copy command-line arguments... 1933 */ 1934 1935 if ((myargv = calloc(sizeof(char *), (size_t)(num_args + 1))) == NULL) 1936 { 1937 _cupsLangPuts(stderr, _("ippfind: Out of memory.")); 1938 exit(IPPFIND_EXIT_MEMORY); 1939 } 1940 1941 for (i = 0; i < num_args; i ++) 1942 { 1943 if (strchr(args[i], '{')) 1944 { 1945 char temp[2048], /* Temporary string */ 1946 *tptr, /* Pointer into temporary string */ 1947 keyword[256], /* {keyword} */ 1948 *kptr; /* Pointer into keyword */ 1949 1950 for (ptr = args[i], tptr = temp; *ptr; ptr ++) 1951 { 1952 if (*ptr == '{') 1953 { 1954 /* 1955 * Do a {var} substitution... 1956 */ 1957 1958 for (kptr = keyword, ptr ++; *ptr && *ptr != '}'; ptr ++) 1959 if (kptr < (keyword + sizeof(keyword) - 1)) 1960 *kptr++ = *ptr; 1961 1962 if (*ptr != '}') 1963 { 1964 _cupsLangPuts(stderr, 1965 _("ippfind: Missing close brace in substitution.")); 1966 exit(IPPFIND_EXIT_SYNTAX); 1967 } 1968 1969 *kptr = '\0'; 1970 if (!keyword[0] || !strcmp(keyword, "service_uri")) 1971 strlcpy(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp)); 1972 else if (!strcmp(keyword, "service_domain")) 1973 strlcpy(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp)); 1974 else if (!strcmp(keyword, "service_hostname")) 1975 strlcpy(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp)); 1976 else if (!strcmp(keyword, "service_name")) 1977 strlcpy(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp)); 1978 else if (!strcmp(keyword, "service_path")) 1979 strlcpy(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp)); 1980 else if (!strcmp(keyword, "service_port")) 1981 strlcpy(tptr, port + 20, sizeof(temp) - (size_t)(tptr - temp)); 1982 else if (!strcmp(keyword, "service_scheme")) 1983 strlcpy(tptr, scheme + 22, sizeof(temp) - (size_t)(tptr - temp)); 1984 else if (!strncmp(keyword, "txt_", 4)) 1985 { 1986 const char *txt = cupsGetOption(keyword + 4, service->num_txt, service->txt); 1987 if (txt) 1988 strlcpy(tptr, txt, sizeof(temp) - (size_t)(tptr - temp)); 1989 else 1990 *tptr = '\0'; 1991 } 1992 else 1993 { 1994 _cupsLangPrintf(stderr, _("ippfind: Unknown variable \"{%s}\"."), 1995 keyword); 1996 exit(IPPFIND_EXIT_SYNTAX); 1997 } 1998 1999 tptr += strlen(tptr); 2000 } 2001 else if (tptr < (temp + sizeof(temp) - 1)) 2002 *tptr++ = *ptr; 2003 } 2004 2005 *tptr = '\0'; 2006 myargv[i] = strdup(temp); 2007 } 2008 else 2009 myargv[i] = strdup(args[i]); 2010 } 2011 2012#ifdef WIN32 2013 if (getenv("IPPFIND_DEBUG")) 2014 { 2015 printf("\nProgram:\n %s\n", args[0]); 2016 puts("\nArguments:"); 2017 for (i = 0; i < num_args; i ++) 2018 printf(" %s\n", myargv[i]); 2019 puts("\nEnvironment:"); 2020 for (i = 0; i < myenvc; i ++) 2021 printf(" %s\n", myenvp[i]); 2022 } 2023 2024 status = _spawnvpe(_P_WAIT, args[0], myargv, myenvp); 2025 2026#else 2027 /* 2028 * Execute the program... 2029 */ 2030 2031 if (strchr(args[0], '/') && !access(args[0], X_OK)) 2032 strlcpy(program, args[0], sizeof(program)); 2033 else if (!cupsFileFind(args[0], getenv("PATH"), 1, program, sizeof(program))) 2034 { 2035 _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"), 2036 args[0], strerror(ENOENT)); 2037 exit(IPPFIND_EXIT_SYNTAX); 2038 } 2039 2040 if (getenv("IPPFIND_DEBUG")) 2041 { 2042 printf("\nProgram:\n %s\n", program); 2043 puts("\nArguments:"); 2044 for (i = 0; i < num_args; i ++) 2045 printf(" %s\n", myargv[i]); 2046 puts("\nEnvironment:"); 2047 for (i = 0; i < myenvc; i ++) 2048 printf(" %s\n", myenvp[i]); 2049 } 2050 2051 if ((pid = fork()) == 0) 2052 { 2053 /* 2054 * Child comes here... 2055 */ 2056 2057 execve(program, myargv, myenvp); 2058 exit(1); 2059 } 2060 else if (pid < 0) 2061 { 2062 _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"), 2063 args[0], strerror(errno)); 2064 exit(IPPFIND_EXIT_SYNTAX); 2065 } 2066 else 2067 { 2068 /* 2069 * Wait for it to complete... 2070 */ 2071 2072 while (wait(&status) != pid) 2073 ; 2074 } 2075#endif /* WIN32 */ 2076 2077 /* 2078 * Free memory... 2079 */ 2080 2081 for (i = 0; i < num_args; i ++) 2082 free(myargv[i]); 2083 2084 free(myargv); 2085 free(myenvp); 2086 2087 /* 2088 * Return whether the program succeeded or crashed... 2089 */ 2090 2091 if (getenv("IPPFIND_DEBUG")) 2092 { 2093#ifdef WIN32 2094 printf("Exit Status: %d\n", status); 2095#else 2096 if (WIFEXITED(status)) 2097 printf("Exit Status: %d\n", WEXITSTATUS(status)); 2098 else 2099 printf("Terminating Signal: %d\n", WTERMSIG(status)); 2100#endif /* WIN32 */ 2101 } 2102 2103 return (status == 0); 2104} 2105 2106 2107/* 2108 * 'get_service()' - Create or update a device. 2109 */ 2110 2111static ippfind_srv_t * /* O - Service */ 2112get_service(cups_array_t *services, /* I - Service array */ 2113 const char *serviceName, /* I - Name of service/device */ 2114 const char *regtype, /* I - Type of service */ 2115 const char *replyDomain) /* I - Service domain */ 2116{ 2117 ippfind_srv_t key, /* Search key */ 2118 *service; /* Service */ 2119 char fullName[kDNSServiceMaxDomainName]; 2120 /* Full name for query */ 2121 2122 2123 /* 2124 * See if this is a new device... 2125 */ 2126 2127 key.name = (char *)serviceName; 2128 key.regtype = (char *)regtype; 2129 2130 for (service = cupsArrayFind(services, &key); 2131 service; 2132 service = cupsArrayNext(services)) 2133 if (_cups_strcasecmp(service->name, key.name)) 2134 break; 2135 else if (!strcmp(service->regtype, key.regtype)) 2136 return (service); 2137 2138 /* 2139 * Yes, add the service... 2140 */ 2141 2142 service = calloc(sizeof(ippfind_srv_t), 1); 2143 service->name = strdup(serviceName); 2144 service->domain = strdup(replyDomain); 2145 service->regtype = strdup(regtype); 2146 2147 cupsArrayAdd(services, service); 2148 2149 /* 2150 * Set the "full name" of this service, which is used for queries and 2151 * resolves... 2152 */ 2153 2154#ifdef HAVE_DNSSD 2155 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); 2156#else /* HAVE_AVAHI */ 2157 avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, 2158 regtype, replyDomain); 2159#endif /* HAVE_DNSSD */ 2160 2161 service->fullName = strdup(fullName); 2162 2163 return (service); 2164} 2165 2166 2167/* 2168 * 'get_time()' - Get the current time-of-day in seconds. 2169 */ 2170 2171static double 2172get_time(void) 2173{ 2174#ifdef WIN32 2175 struct _timeb curtime; /* Current Windows time */ 2176 2177 _ftime(&curtime); 2178 2179 return (curtime.time + 0.001 * curtime.millitm); 2180 2181#else 2182 struct timeval curtime; /* Current UNIX time */ 2183 2184 if (gettimeofday(&curtime, NULL)) 2185 return (0.0); 2186 else 2187 return (curtime.tv_sec + 0.000001 * curtime.tv_usec); 2188#endif /* WIN32 */ 2189} 2190 2191 2192/* 2193 * 'list_service()' - List the contents of a service. 2194 */ 2195 2196static int /* O - 1 if successful, 0 otherwise */ 2197list_service(ippfind_srv_t *service) /* I - Service */ 2198{ 2199 http_addrlist_t *addrlist; /* Address(es) of service */ 2200 char port[10]; /* Port number of service */ 2201 2202 2203 snprintf(port, sizeof(port), "%d", service->port); 2204 2205 if ((addrlist = httpAddrGetList(service->host, address_family, port)) == NULL) 2206 { 2207 _cupsLangPrintf(stdout, "%s unreachable", service->uri); 2208 return (0); 2209 } 2210 2211 if (!strncmp(service->regtype, "_ipp._tcp", 9) || 2212 !strncmp(service->regtype, "_ipps._tcp", 10)) 2213 { 2214 /* 2215 * IPP/IPPS printer 2216 */ 2217 2218 http_t *http; /* HTTP connection */ 2219 ipp_t *request, /* IPP request */ 2220 *response; /* IPP response */ 2221 ipp_attribute_t *attr; /* IPP attribute */ 2222 int i, /* Looping var */ 2223 count, /* Number of values */ 2224 version, /* IPP version */ 2225 paccepting; /* printer-is-accepting-jobs value */ 2226 ipp_pstate_t pstate; /* printer-state value */ 2227 char preasons[1024], /* Comma-delimited printer-state-reasons */ 2228 *ptr, /* Pointer into reasons */ 2229 *end; /* End of reasons buffer */ 2230 static const char * const rattrs[] =/* Requested attributes */ 2231 { 2232 "printer-is-accepting-jobs", 2233 "printer-state", 2234 "printer-state-reasons" 2235 }; 2236 2237 /* 2238 * Connect to the printer... 2239 */ 2240 2241 http = httpConnect2(service->host, service->port, addrlist, address_family, 2242 !strncmp(service->regtype, "_ipps._tcp", 10) ? 2243 HTTP_ENCRYPTION_ALWAYS : 2244 HTTP_ENCRYPTION_IF_REQUESTED, 2245 1, 30000, NULL); 2246 2247 httpAddrFreeList(addrlist); 2248 2249 if (!http) 2250 { 2251 _cupsLangPrintf(stdout, "%s unavailable", service->uri); 2252 return (0); 2253 } 2254 2255 /* 2256 * Get the current printer state... 2257 */ 2258 2259 response = NULL; 2260 version = ipp_version; 2261 2262 do 2263 { 2264 request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); 2265 ippSetVersion(request, version / 10, version % 10); 2266 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, 2267 service->uri); 2268 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, 2269 "requesting-user-name", NULL, cupsUser()); 2270 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, 2271 "requested-attributes", 2272 (int)(sizeof(rattrs) / sizeof(rattrs[0])), NULL, rattrs); 2273 2274 response = cupsDoRequest(http, request, service->resource); 2275 2276 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11) 2277 version = 11; 2278 } 2279 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11); 2280 2281 /* 2282 * Show results... 2283 */ 2284 2285 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) 2286 { 2287 _cupsLangPrintf(stdout, "%s: unavailable", service->uri); 2288 return (0); 2289 } 2290 2291 if ((attr = ippFindAttribute(response, "printer-state", 2292 IPP_TAG_ENUM)) != NULL) 2293 pstate = (ipp_pstate_t)ippGetInteger(attr, 0); 2294 else 2295 pstate = IPP_PSTATE_STOPPED; 2296 2297 if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs", 2298 IPP_TAG_BOOLEAN)) != NULL) 2299 paccepting = ippGetBoolean(attr, 0); 2300 else 2301 paccepting = 0; 2302 2303 if ((attr = ippFindAttribute(response, "printer-state-reasons", 2304 IPP_TAG_KEYWORD)) != NULL) 2305 { 2306 strlcpy(preasons, ippGetString(attr, 0, NULL), sizeof(preasons)); 2307 2308 for (i = 1, count = ippGetCount(attr), ptr = preasons + strlen(preasons), 2309 end = preasons + sizeof(preasons) - 1; 2310 i < count && ptr < end; 2311 i ++, ptr += strlen(ptr)) 2312 { 2313 *ptr++ = ','; 2314 strlcpy(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1)); 2315 } 2316 } 2317 else 2318 strlcpy(preasons, "none", sizeof(preasons)); 2319 2320 ippDelete(response); 2321 httpClose(http); 2322 2323 _cupsLangPrintf(stdout, "%s %s %s %s", service->uri, 2324 ippEnumString("printer-state", pstate), 2325 paccepting ? "accepting-jobs" : "not-accepting-jobs", 2326 preasons); 2327 } 2328 else if (!strncmp(service->regtype, "_http._tcp", 10) || 2329 !strncmp(service->regtype, "_https._tcp", 11)) 2330 { 2331 /* 2332 * HTTP/HTTPS web page 2333 */ 2334 2335 http_t *http; /* HTTP connection */ 2336 http_status_t status; /* HEAD status */ 2337 2338 2339 /* 2340 * Connect to the web server... 2341 */ 2342 2343 http = httpConnect2(service->host, service->port, addrlist, address_family, 2344 !strncmp(service->regtype, "_ipps._tcp", 10) ? 2345 HTTP_ENCRYPTION_ALWAYS : 2346 HTTP_ENCRYPTION_IF_REQUESTED, 2347 1, 30000, NULL); 2348 2349 httpAddrFreeList(addrlist); 2350 2351 if (!http) 2352 { 2353 _cupsLangPrintf(stdout, "%s unavailable", service->uri); 2354 return (0); 2355 } 2356 2357 if (httpGet(http, service->resource)) 2358 { 2359 _cupsLangPrintf(stdout, "%s unavailable", service->uri); 2360 return (0); 2361 } 2362 2363 do 2364 { 2365 status = httpUpdate(http); 2366 } 2367 while (status == HTTP_STATUS_CONTINUE); 2368 2369 httpFlush(http); 2370 httpClose(http); 2371 2372 if (status >= HTTP_STATUS_BAD_REQUEST) 2373 { 2374 _cupsLangPrintf(stdout, "%s unavailable", service->uri); 2375 return (0); 2376 } 2377 2378 _cupsLangPrintf(stdout, "%s available", service->uri); 2379 } 2380 else if (!strncmp(service->regtype, "_printer._tcp", 13)) 2381 { 2382 /* 2383 * LPD printer 2384 */ 2385 2386 int sock; /* Socket */ 2387 2388 2389 if (!httpAddrConnect(addrlist, &sock)) 2390 { 2391 _cupsLangPrintf(stdout, "%s unavailable", service->uri); 2392 httpAddrFreeList(addrlist); 2393 return (0); 2394 } 2395 2396 _cupsLangPrintf(stdout, "%s available", service->uri); 2397 httpAddrFreeList(addrlist); 2398 2399 httpAddrClose(NULL, sock); 2400 } 2401 else 2402 { 2403 _cupsLangPrintf(stdout, "%s unsupported", service->uri); 2404 httpAddrFreeList(addrlist); 2405 return (0); 2406 } 2407 2408 return (1); 2409} 2410 2411 2412/* 2413 * 'new_expr()' - Create a new expression. 2414 */ 2415 2416static ippfind_expr_t * /* O - New expression */ 2417new_expr(ippfind_op_t op, /* I - Operation */ 2418 int invert, /* I - Invert result? */ 2419 const char *value, /* I - TXT key or port range */ 2420 const char *regex, /* I - Regular expression */ 2421 char **args) /* I - Pointer to argument strings */ 2422{ 2423 ippfind_expr_t *temp; /* New expression */ 2424 2425 2426 if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL) 2427 return (NULL); 2428 2429 temp->op = op; 2430 temp->invert = invert; 2431 2432 if (op == IPPFIND_OP_TXT_EXISTS || op == IPPFIND_OP_TXT_REGEX) 2433 temp->key = (char *)value; 2434 else if (op == IPPFIND_OP_PORT_RANGE) 2435 { 2436 /* 2437 * Pull port number range of the form "number", "-number" (0-number), 2438 * "number-" (number-65535), and "number-number". 2439 */ 2440 2441 if (*value == '-') 2442 { 2443 temp->range[1] = atoi(value + 1); 2444 } 2445 else if (strchr(value, '-')) 2446 { 2447 if (sscanf(value, "%d-%d", temp->range, temp->range + 1) == 1) 2448 temp->range[1] = 65535; 2449 } 2450 else 2451 { 2452 temp->range[0] = temp->range[1] = atoi(value); 2453 } 2454 } 2455 2456 if (regex) 2457 { 2458 int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_ICASE | REG_EXTENDED); 2459 2460 if (err) 2461 { 2462 char message[256]; /* Error message */ 2463 2464 regerror(err, &(temp->re), message, sizeof(message)); 2465 _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"), 2466 message); 2467 exit(IPPFIND_EXIT_SYNTAX); 2468 } 2469 } 2470 2471 if (args) 2472 { 2473 int num_args; /* Number of arguments */ 2474 2475 for (num_args = 1; args[num_args]; num_args ++) 2476 if (!strcmp(args[num_args], ";")) 2477 break; 2478 2479 temp->num_args = num_args; 2480 temp->args = malloc((size_t)num_args * sizeof(char *)); 2481 memcpy(temp->args, args, (size_t)num_args * sizeof(char *)); 2482 } 2483 2484 return (temp); 2485} 2486 2487 2488#ifdef HAVE_AVAHI 2489/* 2490 * 'poll_callback()' - Wait for input on the specified file descriptors. 2491 * 2492 * Note: This function is needed because avahi_simple_poll_iterate is broken 2493 * and always uses a timeout of 0 (!) milliseconds. 2494 * (Avahi Ticket #364) 2495 */ 2496 2497static int /* O - Number of file descriptors matching */ 2498poll_callback( 2499 struct pollfd *pollfds, /* I - File descriptors */ 2500 unsigned int num_pollfds, /* I - Number of file descriptors */ 2501 int timeout, /* I - Timeout in milliseconds (unused) */ 2502 void *context) /* I - User data (unused) */ 2503{ 2504 int val; /* Return value */ 2505 2506 2507 (void)timeout; 2508 (void)context; 2509 2510 val = poll(pollfds, num_pollfds, 500); 2511 2512 if (val > 0) 2513 avahi_got_data = 1; 2514 2515 return (val); 2516} 2517#endif /* HAVE_AVAHI */ 2518 2519 2520/* 2521 * 'resolve_callback()' - Process resolve data. 2522 */ 2523 2524#ifdef HAVE_DNSSD 2525static void DNSSD_API 2526resolve_callback( 2527 DNSServiceRef sdRef, /* I - Service reference */ 2528 DNSServiceFlags flags, /* I - Data flags */ 2529 uint32_t interfaceIndex, /* I - Interface */ 2530 DNSServiceErrorType errorCode, /* I - Error, if any */ 2531 const char *fullName, /* I - Full service name */ 2532 const char *hostTarget, /* I - Hostname */ 2533 uint16_t port, /* I - Port number (network byte order) */ 2534 uint16_t txtLen, /* I - Length of TXT record data */ 2535 const unsigned char *txtRecord, /* I - TXT record data */ 2536 void *context) /* I - Service */ 2537{ 2538 char key[256], /* TXT key value */ 2539 *value; /* Value from TXT record */ 2540 const unsigned char *txtEnd; /* End of TXT record */ 2541 uint8_t valueLen; /* Length of value */ 2542 ippfind_srv_t *service = (ippfind_srv_t *)context; 2543 /* Service */ 2544 2545 2546 /* 2547 * Only process "add" data... 2548 */ 2549 2550 (void)sdRef; 2551 (void)flags; 2552 (void)interfaceIndex; 2553 (void)fullName; 2554 2555 if (errorCode != kDNSServiceErr_NoError) 2556 { 2557 _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), 2558 dnssd_error_string(errorCode)); 2559 bonjour_error = 1; 2560 return; 2561 } 2562 2563 service->is_resolved = 1; 2564 service->host = strdup(hostTarget); 2565 service->port = ntohs(port); 2566 2567 /* 2568 * Loop through the TXT key/value pairs and add them to an array... 2569 */ 2570 2571 for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen) 2572 { 2573 /* 2574 * Ignore bogus strings... 2575 */ 2576 2577 valueLen = *txtRecord++; 2578 2579 memcpy(key, txtRecord, valueLen); 2580 key[valueLen] = '\0'; 2581 2582 if ((value = strchr(key, '=')) == NULL) 2583 continue; 2584 2585 *value++ = '\0'; 2586 2587 /* 2588 * Add to array of TXT values... 2589 */ 2590 2591 service->num_txt = cupsAddOption(key, value, service->num_txt, 2592 &(service->txt)); 2593 } 2594 2595 set_service_uri(service); 2596} 2597 2598 2599#elif defined(HAVE_AVAHI) 2600static void 2601resolve_callback( 2602 AvahiServiceResolver *resolver, /* I - Resolver */ 2603 AvahiIfIndex interface, /* I - Interface */ 2604 AvahiProtocol protocol, /* I - Address protocol */ 2605 AvahiResolverEvent event, /* I - Event */ 2606 const char *serviceName,/* I - Service name */ 2607 const char *regtype, /* I - Registration type */ 2608 const char *replyDomain,/* I - Domain name */ 2609 const char *hostTarget, /* I - FQDN */ 2610 const AvahiAddress *address, /* I - Address */ 2611 uint16_t port, /* I - Port number */ 2612 AvahiStringList *txt, /* I - TXT records */ 2613 AvahiLookupResultFlags flags, /* I - Lookup flags */ 2614 void *context) /* I - Service */ 2615{ 2616 char key[256], /* TXT key */ 2617 *value; /* TXT value */ 2618 ippfind_srv_t *service = (ippfind_srv_t *)context; 2619 /* Service */ 2620 AvahiStringList *current; /* Current TXT key/value pair */ 2621 2622 2623 (void)address; 2624 2625 if (event != AVAHI_RESOLVER_FOUND) 2626 { 2627 bonjour_error = 1; 2628 2629 avahi_service_resolver_free(resolver); 2630 avahi_simple_poll_quit(avahi_poll); 2631 return; 2632 } 2633 2634 service->is_resolved = 1; 2635 service->host = strdup(hostTarget); 2636 service->port = port; 2637 2638 /* 2639 * Loop through the TXT key/value pairs and add them to an array... 2640 */ 2641 2642 for (current = txt; current; current = current->next) 2643 { 2644 /* 2645 * Ignore bogus strings... 2646 */ 2647 2648 if (current->size > (sizeof(key) - 1)) 2649 continue; 2650 2651 memcpy(key, current->text, current->size); 2652 key[current->size] = '\0'; 2653 2654 if ((value = strchr(key, '=')) == NULL) 2655 continue; 2656 2657 *value++ = '\0'; 2658 2659 /* 2660 * Add to array of TXT values... 2661 */ 2662 2663 service->num_txt = cupsAddOption(key, value, service->num_txt, 2664 &(service->txt)); 2665 } 2666 2667 set_service_uri(service); 2668} 2669#endif /* HAVE_DNSSD */ 2670 2671 2672/* 2673 * 'set_service_uri()' - Set the URI of the service. 2674 */ 2675 2676static void 2677set_service_uri(ippfind_srv_t *service) /* I - Service */ 2678{ 2679 char uri[1024]; /* URI */ 2680 const char *path, /* Resource path */ 2681 *scheme; /* URI scheme */ 2682 2683 2684 if (!strncmp(service->regtype, "_http.", 6)) 2685 { 2686 scheme = "http"; 2687 path = cupsGetOption("path", service->num_txt, service->txt); 2688 } 2689 else if (!strncmp(service->regtype, "_https.", 7)) 2690 { 2691 scheme = "https"; 2692 path = cupsGetOption("path", service->num_txt, service->txt); 2693 } 2694 else if (!strncmp(service->regtype, "_ipp.", 5)) 2695 { 2696 scheme = "ipp"; 2697 path = cupsGetOption("rp", service->num_txt, service->txt); 2698 } 2699 else if (!strncmp(service->regtype, "_ipps.", 6)) 2700 { 2701 scheme = "ipps"; 2702 path = cupsGetOption("rp", service->num_txt, service->txt); 2703 } 2704 else if (!strncmp(service->regtype, "_printer.", 9)) 2705 { 2706 scheme = "lpd"; 2707 path = cupsGetOption("rp", service->num_txt, service->txt); 2708 } 2709 else 2710 return; 2711 2712 if (!path || !*path) 2713 path = "/"; 2714 2715 if (*path == '/') 2716 { 2717 service->resource = strdup(path); 2718 } 2719 else 2720 { 2721 snprintf(uri, sizeof(uri), "/%s", path); 2722 service->resource = strdup(uri); 2723 } 2724 2725 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, 2726 service->host, service->port, service->resource); 2727 service->uri = strdup(uri); 2728} 2729 2730 2731/* 2732 * 'show_usage()' - Show program usage. 2733 */ 2734 2735static void 2736show_usage(void) 2737{ 2738 _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]" 2739 "[.domain.] ... [expression]\n" 2740 " ippfind [options] name[.regtype[.domain.]] " 2741 "... [expression]\n" 2742 " ippfind --help\n" 2743 " ippfind --version")); 2744 _cupsLangPuts(stderr, ""); 2745 _cupsLangPuts(stderr, _("Options:")); 2746 _cupsLangPuts(stderr, _(" -4 Connect using IPv4.")); 2747 _cupsLangPuts(stderr, _(" -6 Connect using IPv6.")); 2748 _cupsLangPuts(stderr, _(" -T seconds Set the browse timeout in " 2749 "seconds.")); 2750 _cupsLangPuts(stderr, _(" -V version Set default IPP " 2751 "version.")); 2752 _cupsLangPuts(stderr, _(" --help Show this help.")); 2753 _cupsLangPuts(stderr, _(" --version Show program version.")); 2754 _cupsLangPuts(stderr, ""); 2755 _cupsLangPuts(stderr, _("Expressions:")); 2756 _cupsLangPuts(stderr, _(" -P number[-number] Match port to number or range.")); 2757 _cupsLangPuts(stderr, _(" -d regex Match domain to regular expression.")); 2758 _cupsLangPuts(stderr, _(" -h regex Match hostname to regular expression.")); 2759 _cupsLangPuts(stderr, _(" -l List attributes.")); 2760 _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); 2761 _cupsLangPuts(stderr, _(" -p Print URI if true.")); 2762 _cupsLangPuts(stderr, _(" -q Quietly report match via exit code.")); 2763 _cupsLangPuts(stderr, _(" -r True if service is remote.")); 2764 _cupsLangPuts(stderr, _(" -s Print service name if true.")); 2765 _cupsLangPuts(stderr, _(" -t key True if the TXT record contains the key.")); 2766 _cupsLangPuts(stderr, _(" -u regex Match URI to regular expression.")); 2767 _cupsLangPuts(stderr, _(" -x utility [argument ...] ;\n" 2768 " Execute program if true.")); 2769 _cupsLangPuts(stderr, _(" --domain regex Match domain to regular expression.")); 2770 _cupsLangPuts(stderr, _(" --exec utility [argument ...] ;\n" 2771 " Execute program if true.")); 2772 _cupsLangPuts(stderr, _(" --host regex Match hostname to regular expression.")); 2773 _cupsLangPuts(stderr, _(" --ls List attributes.")); 2774 _cupsLangPuts(stderr, _(" --local True if service is local.")); 2775 _cupsLangPuts(stderr, _(" --name regex Match service name to regular expression.")); 2776 _cupsLangPuts(stderr, _(" --path regex Match resource path to regular expression.")); 2777 _cupsLangPuts(stderr, _(" --port number[-number] Match port to number or range.")); 2778 _cupsLangPuts(stderr, _(" --print Print URI if true.")); 2779 _cupsLangPuts(stderr, _(" --print-name Print service name if true.")); 2780 _cupsLangPuts(stderr, _(" --quiet Quietly report match via exit code.")); 2781 _cupsLangPuts(stderr, _(" --remote True if service is remote.")); 2782 _cupsLangPuts(stderr, _(" --txt key True if the TXT record contains the key.")); 2783 _cupsLangPuts(stderr, _(" --txt-* regex Match TXT record key to regular expression.")); 2784 _cupsLangPuts(stderr, _(" --uri regex Match URI to regular expression.")); 2785 _cupsLangPuts(stderr, ""); 2786 _cupsLangPuts(stderr, _("Modifiers:")); 2787 _cupsLangPuts(stderr, _(" ( expressions ) Group expressions.")); 2788 _cupsLangPuts(stderr, _(" ! expression Unary NOT of expression.")); 2789 _cupsLangPuts(stderr, _(" --not expression Unary NOT of expression.")); 2790 _cupsLangPuts(stderr, _(" --false Always false.")); 2791 _cupsLangPuts(stderr, _(" --true Always true.")); 2792 _cupsLangPuts(stderr, _(" expression expression Logical AND.")); 2793 _cupsLangPuts(stderr, _(" expression --and expression\n" 2794 " Logical AND.")); 2795 _cupsLangPuts(stderr, _(" expression --or expression\n" 2796 " Logical OR.")); 2797 _cupsLangPuts(stderr, ""); 2798 _cupsLangPuts(stderr, _("Substitutions:")); 2799 _cupsLangPuts(stderr, _(" {} URI")); 2800 _cupsLangPuts(stderr, _(" {service_domain} Domain name")); 2801 _cupsLangPuts(stderr, _(" {service_hostname} Fully-qualified domain name")); 2802 _cupsLangPuts(stderr, _(" {service_name} Service instance name")); 2803 _cupsLangPuts(stderr, _(" {service_port} Port number")); 2804 _cupsLangPuts(stderr, _(" {service_regtype} DNS-SD registration type")); 2805 _cupsLangPuts(stderr, _(" {service_scheme} URI scheme")); 2806 _cupsLangPuts(stderr, _(" {service_uri} URI")); 2807 _cupsLangPuts(stderr, _(" {txt_*} Value of TXT record key")); 2808 _cupsLangPuts(stderr, ""); 2809 _cupsLangPuts(stderr, _("Environment Variables:")); 2810 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_DOMAIN Domain name")); 2811 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_HOSTNAME\n" 2812 " Fully-qualified domain name")); 2813 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_NAME Service instance name")); 2814 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_PORT Port number")); 2815 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type")); 2816 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_SCHEME URI scheme")); 2817 _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_URI URI")); 2818 _cupsLangPuts(stderr, _(" IPPFIND_TXT_* Value of TXT record key")); 2819 2820 exit(IPPFIND_EXIT_TRUE); 2821} 2822 2823 2824/* 2825 * 'show_version()' - Show program version. 2826 */ 2827 2828static void 2829show_version(void) 2830{ 2831 _cupsLangPuts(stderr, CUPS_SVERSION); 2832 2833 exit(IPPFIND_EXIT_TRUE); 2834} 2835 2836 2837/* 2838 * End of "$Id: ippfind.c 12142 2014-08-30 02:35:43Z msweet $". 2839 */ 2840