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