| /* |
| * Utility to find IPP printers via Bonjour/DNS-SD and optionally run |
| * commands such as IPP and Bonjour conformance tests. This tool is |
| * inspired by the UNIX "find" command, thus its name. |
| * |
| * Copyright © 2008-2018 by Apple Inc. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers. |
| */ |
| |
| #define _CUPS_NO_DEPRECATED |
| #include <cups/cups-private.h> |
| #ifdef _WIN32 |
| # include <process.h> |
| # include <sys/timeb.h> |
| #else |
| # include <sys/wait.h> |
| #endif /* _WIN32 */ |
| #include <regex.h> |
| #ifdef HAVE_DNSSD |
| # include <dns_sd.h> |
| #elif defined(HAVE_AVAHI) |
| # include <avahi-client/client.h> |
| # include <avahi-client/lookup.h> |
| # include <avahi-common/simple-watch.h> |
| # include <avahi-common/domain.h> |
| # include <avahi-common/error.h> |
| # include <avahi-common/malloc.h> |
| # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX |
| #endif /* HAVE_DNSSD */ |
| |
| #ifndef _WIN32 |
| extern char **environ; /* Process environment variables */ |
| #endif /* !_WIN32 */ |
| |
| |
| /* |
| * Structures... |
| */ |
| |
| typedef enum ippfind_exit_e /* Exit codes */ |
| { |
| IPPFIND_EXIT_TRUE = 0, /* OK and result is true */ |
| IPPFIND_EXIT_FALSE, /* OK but result is false*/ |
| IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */ |
| IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */ |
| IPPFIND_EXIT_MEMORY /* Out of memory */ |
| } ippfind_exit_t; |
| |
| typedef enum ippfind_op_e /* Operations for expressions */ |
| { |
| /* "Evaluation" operations */ |
| IPPFIND_OP_NONE, /* No operation */ |
| IPPFIND_OP_AND, /* Logical AND of all children */ |
| IPPFIND_OP_OR, /* Logical OR of all children */ |
| IPPFIND_OP_TRUE, /* Always true */ |
| IPPFIND_OP_FALSE, /* Always false */ |
| IPPFIND_OP_IS_LOCAL, /* Is a local service */ |
| IPPFIND_OP_IS_REMOTE, /* Is a remote service */ |
| IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */ |
| IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */ |
| IPPFIND_OP_NAME_LITERAL, /* Name matches literal string */ |
| IPPFIND_OP_HOST_REGEX, /* Hostname matches regular expression */ |
| IPPFIND_OP_PORT_RANGE, /* Port matches range */ |
| IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */ |
| IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */ |
| IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */ |
| IPPFIND_OP_URI_REGEX, /* URI matches regular expression */ |
| |
| /* "Output" operations */ |
| IPPFIND_OP_EXEC, /* Execute when true */ |
| IPPFIND_OP_LIST, /* List when true */ |
| IPPFIND_OP_PRINT_NAME, /* Print URI when true */ |
| IPPFIND_OP_PRINT_URI, /* Print name when true */ |
| IPPFIND_OP_QUIET /* No output when true */ |
| } ippfind_op_t; |
| |
| typedef struct ippfind_expr_s /* Expression */ |
| { |
| struct ippfind_expr_s |
| *prev, /* Previous expression */ |
| *next, /* Next expression */ |
| *parent, /* Parent expressions */ |
| *child; /* Child expressions */ |
| ippfind_op_t op; /* Operation code (see above) */ |
| int invert; /* Invert the result */ |
| char *name; /* TXT record key or literal name */ |
| regex_t re; /* Regular expression for matching */ |
| int range[2]; /* Port number range */ |
| int num_args; /* Number of arguments for exec */ |
| char **args; /* Arguments for exec */ |
| } ippfind_expr_t; |
| |
| typedef struct ippfind_srv_s /* Service information */ |
| { |
| #ifdef HAVE_DNSSD |
| DNSServiceRef ref; /* Service reference for query */ |
| #elif defined(HAVE_AVAHI) |
| AvahiServiceResolver *ref; /* Resolver */ |
| #endif /* HAVE_DNSSD */ |
| char *name, /* Service name */ |
| *domain, /* Domain name */ |
| *regtype, /* Registration type */ |
| *fullName, /* Full name */ |
| *host, /* Hostname */ |
| *resource, /* Resource path */ |
| *uri; /* URI */ |
| int num_txt; /* Number of TXT record keys */ |
| cups_option_t *txt; /* TXT record keys */ |
| int port, /* Port number */ |
| is_local, /* Is a local service? */ |
| is_processed, /* Did we process the service? */ |
| is_resolved; /* Got the resolve data? */ |
| } ippfind_srv_t; |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| static DNSServiceRef dnssd_ref; /* Master service reference */ |
| #elif defined(HAVE_AVAHI) |
| static AvahiClient *avahi_client = NULL;/* Client information */ |
| static int avahi_got_data = 0; /* Got data from poll? */ |
| static AvahiSimplePoll *avahi_poll = NULL; |
| /* Poll information */ |
| #endif /* HAVE_DNSSD */ |
| |
| static int address_family = AF_UNSPEC; |
| /* Address family for LIST */ |
| static int bonjour_error = 0; /* Error browsing/resolving? */ |
| static double bonjour_timeout = 1.0; /* Timeout in seconds */ |
| static int ipp_version = 20; /* IPP version for LIST */ |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| static void DNSSD_API browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8); |
| static void DNSSD_API browse_local_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8); |
| #elif defined(HAVE_AVAHI) |
| static void browse_callback(AvahiServiceBrowser *browser, |
| AvahiIfIndex interface, |
| AvahiProtocol protocol, |
| AvahiBrowserEvent event, |
| const char *serviceName, |
| const char *regtype, |
| const char *replyDomain, |
| AvahiLookupResultFlags flags, |
| void *context); |
| static void client_callback(AvahiClient *client, |
| AvahiClientState state, |
| void *context); |
| #endif /* HAVE_AVAHI */ |
| |
| static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b); |
| static const char *dnssd_error_string(int error); |
| static int eval_expr(ippfind_srv_t *service, |
| ippfind_expr_t *expressions); |
| static int exec_program(ippfind_srv_t *service, int num_args, |
| char **args); |
| static ippfind_srv_t *get_service(cups_array_t *services, const char *serviceName, const char *regtype, const char *replyDomain) _CUPS_NONNULL(1,2,3,4); |
| static double get_time(void); |
| static int list_service(ippfind_srv_t *service); |
| static ippfind_expr_t *new_expr(ippfind_op_t op, int invert, |
| const char *value, const char *regex, |
| char **args); |
| #ifdef HAVE_DNSSD |
| static void DNSSD_API resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullName, const char *hostTarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) _CUPS_NONNULL(1,5,6,9, 10); |
| #elif defined(HAVE_AVAHI) |
| static int poll_callback(struct pollfd *pollfds, |
| unsigned int num_pollfds, int timeout, |
| void *context); |
| static void resolve_callback(AvahiServiceResolver *res, |
| AvahiIfIndex interface, |
| AvahiProtocol protocol, |
| AvahiResolverEvent event, |
| const char *serviceName, |
| const char *regtype, |
| const char *replyDomain, |
| const char *host_name, |
| const AvahiAddress *address, |
| uint16_t port, |
| AvahiStringList *txt, |
| AvahiLookupResultFlags flags, |
| void *context); |
| #endif /* HAVE_DNSSD */ |
| static void set_service_uri(ippfind_srv_t *service); |
| static void show_usage(void) _CUPS_NORETURN; |
| static void show_version(void) _CUPS_NORETURN; |
| |
| |
| /* |
| * 'main()' - Browse for printers. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line args */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i, /* Looping var */ |
| have_output = 0,/* Have output expression */ |
| status = IPPFIND_EXIT_FALSE; |
| /* Exit status */ |
| const char *opt, /* Option character */ |
| *search; /* Current browse/resolve string */ |
| cups_array_t *searches; /* Things to browse/resolve */ |
| cups_array_t *services; /* Service array */ |
| ippfind_srv_t *service; /* Current service */ |
| ippfind_expr_t *expressions = NULL, |
| /* Expression tree */ |
| *temp = NULL, /* New expression */ |
| *parent = NULL, /* Parent expression */ |
| *current = NULL,/* Current expression */ |
| *parens[100]; /* Markers for parenthesis */ |
| int num_parens = 0; /* Number of parenthesis */ |
| ippfind_op_t logic = IPPFIND_OP_AND; |
| /* Logic for next expression */ |
| int invert = 0; /* Invert expression? */ |
| int err; /* DNS-SD error */ |
| #ifdef HAVE_DNSSD |
| fd_set sinput; /* Input set for select() */ |
| struct timeval stimeout; /* Timeout for select() */ |
| #endif /* HAVE_DNSSD */ |
| double endtime; /* End time */ |
| static const char * const ops[] = /* Node operation names */ |
| { |
| "NONE", |
| "AND", |
| "OR", |
| "TRUE", |
| "FALSE", |
| "IS_LOCAL", |
| "IS_REMOTE", |
| "DOMAIN_REGEX", |
| "NAME_REGEX", |
| "NAME_LITERAL", |
| "HOST_REGEX", |
| "PORT_RANGE", |
| "PATH_REGEX", |
| "TXT_EXISTS", |
| "TXT_REGEX", |
| "URI_REGEX", |
| "EXEC", |
| "LIST", |
| "PRINT_NAME", |
| "PRINT_URI", |
| "QUIET" |
| }; |
| |
| |
| /* |
| * Initialize the locale... |
| */ |
| |
| _cupsSetLocale(argv); |
| |
| /* |
| * Create arrays to track services and things we want to browse/resolve... |
| */ |
| |
| searches = cupsArrayNew(NULL, NULL); |
| services = cupsArrayNew((cups_array_func_t)compare_services, NULL); |
| |
| /* |
| * Parse command-line... |
| */ |
| |
| if (getenv("IPPFIND_DEBUG")) |
| for (i = 1; i < argc; i ++) |
| fprintf(stderr, "argv[%d]=\"%s\"\n", i, argv[i]); |
| |
| for (i = 1; i < argc; i ++) |
| { |
| if (argv[i][0] == '-') |
| { |
| if (argv[i][1] == '-') |
| { |
| /* |
| * Parse --option options... |
| */ |
| |
| if (!strcmp(argv[i], "--and")) |
| { |
| if (logic == IPPFIND_OP_OR) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or.")); |
| show_usage(); |
| } |
| |
| if (!current) |
| { |
| _cupsLangPuts(stderr, |
| _("ippfind: Missing expression before \"--and\".")); |
| show_usage(); |
| } |
| |
| temp = NULL; |
| } |
| else if (!strcmp(argv[i], "--domain")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| "--domain"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--exec")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Expected program after %s."), |
| "--exec"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, |
| argv + i)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| while (i < argc) |
| if (!strcmp(argv[i], ";")) |
| break; |
| else |
| i ++; |
| |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."), |
| "--exec"); |
| show_usage(); |
| } |
| |
| have_output = 1; |
| } |
| else if (!strcmp(argv[i], "--false")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--help")) |
| { |
| show_usage(); |
| } |
| else if (!strcmp(argv[i], "--host")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| "--host"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--ls")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| } |
| else if (!strcmp(argv[i], "--local")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--literal-name")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "--literal-name"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--name")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| "--name"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--not")) |
| { |
| invert = 1; |
| } |
| else if (!strcmp(argv[i], "--or")) |
| { |
| if (!current) |
| { |
| _cupsLangPuts(stderr, |
| _("ippfind: Missing expression before \"--or\".")); |
| show_usage(); |
| } |
| |
| logic = IPPFIND_OP_OR; |
| |
| if (parent && parent->op == IPPFIND_OP_OR) |
| { |
| /* |
| * Already setup to do "foo --or bar --or baz"... |
| */ |
| |
| temp = NULL; |
| } |
| else if (!current->prev && parent) |
| { |
| /* |
| * Change parent node into an OR node... |
| */ |
| |
| parent->op = IPPFIND_OP_OR; |
| temp = NULL; |
| } |
| else if (!current->prev) |
| { |
| /* |
| * Need to group "current" in a new OR node... |
| */ |
| |
| if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| temp->parent = parent; |
| temp->child = current; |
| current->parent = temp; |
| |
| if (parent) |
| parent->child = temp; |
| else |
| expressions = temp; |
| |
| parent = temp; |
| temp = NULL; |
| } |
| else |
| { |
| /* |
| * Need to group previous expressions in an AND node, and then |
| * put that in an OR node... |
| */ |
| |
| if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| while (current->prev) |
| { |
| current->parent = temp; |
| current = current->prev; |
| } |
| |
| current->parent = temp; |
| temp->child = current; |
| current = temp; |
| |
| if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| temp->parent = parent; |
| current->parent = temp; |
| |
| if (parent) |
| parent->child = temp; |
| else |
| expressions = temp; |
| |
| parent = temp; |
| temp = NULL; |
| } |
| } |
| else if (!strcmp(argv[i], "--path")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| "--path"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--port")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Expected port range after %s."), |
| "--port"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--print")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| } |
| else if (!strcmp(argv[i], "--print-name")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| } |
| else if (!strcmp(argv[i], "--quiet")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| } |
| else if (!strcmp(argv[i], "--remote")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--true")) |
| { |
| if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--txt")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Expected key name after %s."), |
| "--txt"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strncmp(argv[i], "--txt-", 6)) |
| { |
| const char *key = argv[i] + 6;/* TXT key */ |
| |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| argv[i - 1]); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--uri")) |
| { |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after %s."), |
| "--uri"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i], |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| } |
| else if (!strcmp(argv[i], "--version")) |
| { |
| show_version(); |
| } |
| else |
| { |
| _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), |
| "ippfind", argv[i]); |
| show_usage(); |
| } |
| |
| if (temp) |
| { |
| /* |
| * Add new expression... |
| */ |
| |
| if (logic == IPPFIND_OP_AND && |
| current && current->prev && |
| parent && parent->op != IPPFIND_OP_AND) |
| { |
| /* |
| * Need to re-group "current" in a new AND node... |
| */ |
| |
| ippfind_expr_t *tempand; /* Temporary AND node */ |
| |
| if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| /* |
| * Replace "current" with new AND node at the end of this list... |
| */ |
| |
| current->prev->next = tempand; |
| tempand->prev = current->prev; |
| tempand->parent = parent; |
| |
| /* |
| * Add "current to the new AND node... |
| */ |
| |
| tempand->child = current; |
| current->parent = tempand; |
| current->prev = NULL; |
| parent = tempand; |
| } |
| |
| /* |
| * Add the new node at current level... |
| */ |
| |
| temp->parent = parent; |
| temp->prev = current; |
| |
| if (current) |
| current->next = temp; |
| else if (parent) |
| parent->child = temp; |
| else |
| expressions = temp; |
| |
| current = temp; |
| invert = 0; |
| logic = IPPFIND_OP_AND; |
| temp = NULL; |
| } |
| } |
| else |
| { |
| /* |
| * Parse -o options |
| */ |
| |
| for (opt = argv[i] + 1; *opt; opt ++) |
| { |
| switch (*opt) |
| { |
| case '4' : |
| address_family = AF_INET; |
| break; |
| |
| case '6' : |
| address_family = AF_INET6; |
| break; |
| |
| case 'N' : /* Literal name */ |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "-N"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'P' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Expected port range after %s."), |
| "-P"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], |
| NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'T' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("%s: Missing timeout for \"-T\"."), |
| "ippfind"); |
| show_usage(); |
| } |
| |
| bonjour_timeout = atof(argv[i]); |
| break; |
| |
| case 'V' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("%s: Missing version for \"-V\"."), |
| "ippfind"); |
| show_usage(); |
| } |
| |
| if (!strcmp(argv[i], "1.1")) |
| ipp_version = 11; |
| else if (!strcmp(argv[i], "2.0")) |
| ipp_version = 20; |
| else if (!strcmp(argv[i], "2.1")) |
| ipp_version = 21; |
| else if (!strcmp(argv[i], "2.2")) |
| ipp_version = 22; |
| else |
| { |
| _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), |
| "ippfind", argv[i]); |
| show_usage(); |
| } |
| break; |
| |
| case 'd' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after " |
| "%s."), "-d"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, |
| argv[i], NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'h' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after " |
| "%s."), "-h"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, |
| argv[i], NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'l' : |
| if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| break; |
| |
| case 'n' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after " |
| "%s."), "-n"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, |
| argv[i], NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'p' : |
| if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| break; |
| |
| case 'q' : |
| if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| break; |
| |
| case 'r' : |
| if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 's' : |
| if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| have_output = 1; |
| break; |
| |
| case 't' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing key name after %s."), |
| "-t"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], |
| NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'u' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing regular expression after " |
| "%s."), "-u"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, |
| argv[i], NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| break; |
| |
| case 'x' : |
| i ++; |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing program after %s."), |
| "-x"); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, |
| argv + i)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| while (i < argc) |
| if (!strcmp(argv[i], ";")) |
| break; |
| else |
| i ++; |
| |
| if (i >= argc) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Missing semi-colon after %s."), |
| "-x"); |
| show_usage(); |
| } |
| |
| have_output = 1; |
| break; |
| |
| default : |
| _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), |
| "ippfind", *opt); |
| show_usage(); |
| } |
| |
| if (temp) |
| { |
| /* |
| * Add new expression... |
| */ |
| |
| if (logic == IPPFIND_OP_AND && |
| current && current->prev && |
| parent && parent->op != IPPFIND_OP_AND) |
| { |
| /* |
| * Need to re-group "current" in a new AND node... |
| */ |
| |
| ippfind_expr_t *tempand; /* Temporary AND node */ |
| |
| if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, |
| NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| /* |
| * Replace "current" with new AND node at the end of this list... |
| */ |
| |
| current->prev->next = tempand; |
| tempand->prev = current->prev; |
| tempand->parent = parent; |
| |
| /* |
| * Add "current to the new AND node... |
| */ |
| |
| tempand->child = current; |
| current->parent = tempand; |
| current->prev = NULL; |
| parent = tempand; |
| } |
| |
| /* |
| * Add the new node at current level... |
| */ |
| |
| temp->parent = parent; |
| temp->prev = current; |
| |
| if (current) |
| current->next = temp; |
| else if (parent) |
| parent->child = temp; |
| else |
| expressions = temp; |
| |
| current = temp; |
| invert = 0; |
| logic = IPPFIND_OP_AND; |
| temp = NULL; |
| } |
| } |
| } |
| } |
| else if (!strcmp(argv[i], "(")) |
| { |
| if (num_parens >= 100) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Too many parenthesis.")); |
| show_usage(); |
| } |
| |
| if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| parens[num_parens++] = temp; |
| |
| if (current) |
| { |
| temp->parent = current->parent; |
| current->next = temp; |
| temp->prev = current; |
| } |
| else |
| expressions = temp; |
| |
| parent = temp; |
| current = NULL; |
| invert = 0; |
| logic = IPPFIND_OP_AND; |
| } |
| else if (!strcmp(argv[i], ")")) |
| { |
| if (num_parens <= 0) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis.")); |
| show_usage(); |
| } |
| |
| current = parens[--num_parens]; |
| parent = current->parent; |
| invert = 0; |
| logic = IPPFIND_OP_AND; |
| } |
| else if (!strcmp(argv[i], "!")) |
| { |
| invert = 1; |
| } |
| else |
| { |
| /* |
| * _regtype._tcp[,subtype][.domain] |
| * |
| * OR |
| * |
| * service-name[._regtype._tcp[.domain]] |
| */ |
| |
| cupsArrayAdd(searches, argv[i]); |
| } |
| } |
| |
| if (num_parens > 0) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis.")); |
| show_usage(); |
| } |
| |
| if (!have_output) |
| { |
| /* |
| * Add an implicit --print-uri to the end... |
| */ |
| |
| if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL) |
| return (IPPFIND_EXIT_MEMORY); |
| |
| if (current) |
| { |
| while (current->parent) |
| current = current->parent; |
| |
| current->next = temp; |
| temp->prev = current; |
| } |
| else |
| expressions = temp; |
| } |
| |
| if (cupsArrayCount(searches) == 0) |
| { |
| /* |
| * Add an implicit browse for IPP printers ("_ipp._tcp")... |
| */ |
| |
| cupsArrayAdd(searches, "_ipp._tcp"); |
| } |
| |
| if (getenv("IPPFIND_DEBUG")) |
| { |
| int indent = 4; /* Indentation */ |
| |
| puts("Expression tree:"); |
| current = expressions; |
| while (current) |
| { |
| /* |
| * Print the current node... |
| */ |
| |
| printf("%*s%s%s\n", indent, "", current->invert ? "!" : "", |
| ops[current->op]); |
| |
| /* |
| * Advance to the next node... |
| */ |
| |
| if (current->child) |
| { |
| current = current->child; |
| indent += 4; |
| } |
| else if (current->next) |
| current = current->next; |
| else if (current->parent) |
| { |
| while (current->parent) |
| { |
| indent -= 4; |
| current = current->parent; |
| if (current->next) |
| break; |
| } |
| |
| current = current->next; |
| } |
| else |
| current = NULL; |
| } |
| |
| puts("\nSearch items:"); |
| for (search = (const char *)cupsArrayFirst(searches); |
| search; |
| search = (const char *)cupsArrayNext(searches)) |
| printf(" %s\n", search); |
| } |
| |
| /* |
| * Start up browsing/resolving... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), |
| dnssd_error_string(err)); |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| |
| #elif defined(HAVE_AVAHI) |
| if ((avahi_poll = avahi_simple_poll_new()) == NULL) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), |
| strerror(errno)); |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| |
| avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL); |
| |
| avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll), |
| 0, client_callback, avahi_poll, &err); |
| if (!avahi_client) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), |
| dnssd_error_string(err)); |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| #endif /* HAVE_DNSSD */ |
| |
| for (search = (const char *)cupsArrayFirst(searches); |
| search; |
| search = (const char *)cupsArrayNext(searches)) |
| { |
| char buf[1024], /* Full name string */ |
| *name = NULL, /* Service instance name */ |
| *regtype, /* Registration type */ |
| *domain; /* Domain, if any */ |
| |
| strlcpy(buf, search, sizeof(buf)); |
| |
| if (!strncmp(buf, "_http._", 7) || !strncmp(buf, "_https._", 8) || !strncmp(buf, "_ipp._", 6) || !strncmp(buf, "_ipps._", 7)) |
| { |
| regtype = buf; |
| } |
| else if ((regtype = strstr(buf, "._")) != NULL) |
| { |
| if (strcmp(regtype, "._tcp")) |
| { |
| /* |
| * "something._protocol._tcp" -> search for something with the given |
| * protocol... |
| */ |
| |
| name = buf; |
| *regtype++ = '\0'; |
| } |
| else |
| { |
| /* |
| * "_protocol._tcp" -> search for everything with the given protocol... |
| */ |
| |
| /* name = NULL; */ |
| regtype = buf; |
| } |
| } |
| else |
| { |
| /* |
| * "something" -> search for something with IPP protocol... |
| */ |
| |
| name = buf; |
| regtype = "_ipp._tcp"; |
| } |
| |
| for (domain = regtype; *domain; domain ++) |
| { |
| if (*domain == '.' && domain[1] != '_') |
| { |
| *domain++ = '\0'; |
| break; |
| } |
| } |
| |
| if (!*domain) |
| domain = NULL; |
| |
| if (name) |
| { |
| /* |
| * Resolve the given service instance name, regtype, and domain... |
| */ |
| |
| if (!domain) |
| domain = "local."; |
| |
| service = get_service(services, name, regtype, domain); |
| |
| if (getenv("IPPFIND_DEBUG")) |
| fprintf(stderr, "Resolving name=\"%s\", regtype=\"%s\", domain=\"%s\"\n", name, regtype, domain); |
| |
| #ifdef HAVE_DNSSD |
| service->ref = dnssd_ref; |
| err = DNSServiceResolve(&(service->ref), |
| kDNSServiceFlagsShareConnection, 0, name, |
| regtype, domain, resolve_callback, |
| service); |
| |
| #elif defined(HAVE_AVAHI) |
| service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC, |
| AVAHI_PROTO_UNSPEC, name, |
| regtype, domain, |
| AVAHI_PROTO_UNSPEC, 0, |
| resolve_callback, service); |
| if (service->ref) |
| err = 0; |
| else |
| err = avahi_client_errno(avahi_client); |
| #endif /* HAVE_DNSSD */ |
| } |
| else |
| { |
| /* |
| * Browse for services of the given type... |
| */ |
| |
| if (getenv("IPPFIND_DEBUG")) |
| fprintf(stderr, "Browsing for regtype=\"%s\", domain=\"%s\"\n", regtype, domain); |
| |
| #ifdef HAVE_DNSSD |
| DNSServiceRef ref; /* Browse reference */ |
| |
| ref = dnssd_ref; |
| err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype, |
| domain, browse_callback, services); |
| |
| if (!err) |
| { |
| ref = dnssd_ref; |
| err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, |
| kDNSServiceInterfaceIndexLocalOnly, regtype, |
| domain, browse_local_callback, services); |
| } |
| |
| #elif defined(HAVE_AVAHI) |
| if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC, |
| AVAHI_PROTO_UNSPEC, regtype, domain, 0, |
| browse_callback, services)) |
| err = 0; |
| else |
| err = avahi_client_errno(avahi_client); |
| #endif /* HAVE_DNSSD */ |
| } |
| |
| if (err) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), |
| dnssd_error_string(err)); |
| |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| } |
| |
| /* |
| * Process browse/resolve requests... |
| */ |
| |
| if (bonjour_timeout > 1.0) |
| endtime = get_time() + bonjour_timeout; |
| else |
| endtime = get_time() + 300.0; |
| |
| while (get_time() < endtime) |
| { |
| int process = 0; /* Process services? */ |
| |
| #ifdef HAVE_DNSSD |
| int fd = DNSServiceRefSockFD(dnssd_ref); |
| /* File descriptor for DNS-SD */ |
| |
| FD_ZERO(&sinput); |
| FD_SET(fd, &sinput); |
| |
| stimeout.tv_sec = 0; |
| stimeout.tv_usec = 500000; |
| |
| if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0) |
| continue; |
| |
| if (FD_ISSET(fd, &sinput)) |
| { |
| /* |
| * Process responses... |
| */ |
| |
| DNSServiceProcessResult(dnssd_ref); |
| } |
| else |
| { |
| /* |
| * Time to process services... |
| */ |
| |
| process = 1; |
| } |
| |
| #elif defined(HAVE_AVAHI) |
| avahi_got_data = 0; |
| |
| if (avahi_simple_poll_iterate(avahi_poll, 500) > 0) |
| { |
| /* |
| * We've been told to exit the loop. Perhaps the connection to |
| * Avahi failed. |
| */ |
| |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| |
| if (!avahi_got_data) |
| { |
| /* |
| * Time to process services... |
| */ |
| |
| process = 1; |
| } |
| #endif /* HAVE_DNSSD */ |
| |
| if (process) |
| { |
| /* |
| * Process any services that we have found... |
| */ |
| |
| int active = 0, /* Number of active resolves */ |
| resolved = 0, /* Number of resolved services */ |
| processed = 0; /* Number of processed services */ |
| |
| for (service = (ippfind_srv_t *)cupsArrayFirst(services); |
| service; |
| service = (ippfind_srv_t *)cupsArrayNext(services)) |
| { |
| if (service->is_processed) |
| processed ++; |
| |
| if (service->is_resolved) |
| resolved ++; |
| |
| if (!service->ref && !service->is_resolved) |
| { |
| /* |
| * Found a service, now resolve it (but limit to 50 active resolves...) |
| */ |
| |
| if (active < 50) |
| { |
| #ifdef HAVE_DNSSD |
| service->ref = dnssd_ref; |
| err = DNSServiceResolve(&(service->ref), |
| kDNSServiceFlagsShareConnection, 0, |
| service->name, service->regtype, |
| service->domain, resolve_callback, |
| service); |
| |
| #elif defined(HAVE_AVAHI) |
| service->ref = avahi_service_resolver_new(avahi_client, |
| AVAHI_IF_UNSPEC, |
| AVAHI_PROTO_UNSPEC, |
| service->name, |
| service->regtype, |
| service->domain, |
| AVAHI_PROTO_UNSPEC, 0, |
| resolve_callback, |
| service); |
| if (service->ref) |
| err = 0; |
| else |
| err = avahi_client_errno(avahi_client); |
| #endif /* HAVE_DNSSD */ |
| |
| if (err) |
| { |
| _cupsLangPrintf(stderr, |
| _("ippfind: Unable to browse or resolve: %s"), |
| dnssd_error_string(err)); |
| return (IPPFIND_EXIT_BONJOUR); |
| } |
| |
| active ++; |
| } |
| } |
| else if (service->is_resolved && !service->is_processed) |
| { |
| /* |
| * Resolved, not process this service against the expressions... |
| */ |
| |
| if (service->ref) |
| { |
| #ifdef HAVE_DNSSD |
| DNSServiceRefDeallocate(service->ref); |
| #else |
| avahi_service_resolver_free(service->ref); |
| #endif /* HAVE_DNSSD */ |
| |
| service->ref = NULL; |
| } |
| |
| if (eval_expr(service, expressions)) |
| status = IPPFIND_EXIT_TRUE; |
| |
| service->is_processed = 1; |
| } |
| else if (service->ref) |
| active ++; |
| } |
| |
| /* |
| * If we have processed all services we have discovered, then we are done. |
| */ |
| |
| if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0) |
| break; |
| } |
| } |
| |
| if (bonjour_error) |
| return (IPPFIND_EXIT_BONJOUR); |
| else |
| return (status); |
| } |
| |
| |
| #ifdef HAVE_DNSSD |
| /* |
| * 'browse_callback()' - Browse devices. |
| */ |
| |
| static void DNSSD_API |
| browse_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Option flags */ |
| uint32_t interfaceIndex, /* I - Interface number */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *serviceName, /* I - Name of service/device */ |
| const char *regtype, /* I - Type of service */ |
| const char *replyDomain, /* I - Service domain */ |
| void *context) /* I - Services array */ |
| { |
| /* |
| * Only process "add" data... |
| */ |
| |
| (void)sdRef; |
| (void)interfaceIndex; |
| |
| if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) |
| return; |
| |
| /* |
| * Get the device... |
| */ |
| |
| get_service((cups_array_t *)context, serviceName, regtype, replyDomain); |
| } |
| |
| |
| /* |
| * 'browse_local_callback()' - Browse local devices. |
| */ |
| |
| static void DNSSD_API |
| browse_local_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Option flags */ |
| uint32_t interfaceIndex, /* I - Interface number */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *serviceName, /* I - Name of service/device */ |
| const char *regtype, /* I - Type of service */ |
| const char *replyDomain, /* I - Service domain */ |
| void *context) /* I - Services array */ |
| { |
| ippfind_srv_t *service; /* Service */ |
| |
| |
| /* |
| * Only process "add" data... |
| */ |
| |
| (void)sdRef; |
| (void)interfaceIndex; |
| |
| if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) |
| return; |
| |
| /* |
| * Get the device... |
| */ |
| |
| service = get_service((cups_array_t *)context, serviceName, regtype, |
| replyDomain); |
| service->is_local = 1; |
| } |
| #endif /* HAVE_DNSSD */ |
| |
| |
| #ifdef HAVE_AVAHI |
| /* |
| * 'browse_callback()' - Browse devices. |
| */ |
| |
| static void |
| browse_callback( |
| AvahiServiceBrowser *browser, /* I - Browser */ |
| AvahiIfIndex interface, /* I - Interface index (unused) */ |
| AvahiProtocol protocol, /* I - Network protocol (unused) */ |
| AvahiBrowserEvent event, /* I - What happened */ |
| const char *name, /* I - Service name */ |
| const char *type, /* I - Registration type */ |
| const char *domain, /* I - Domain */ |
| AvahiLookupResultFlags flags, /* I - Flags */ |
| void *context) /* I - Services array */ |
| { |
| AvahiClient *client = avahi_service_browser_get_client(browser); |
| /* Client information */ |
| ippfind_srv_t *service; /* Service information */ |
| |
| |
| (void)interface; |
| (void)protocol; |
| (void)context; |
| |
| switch (event) |
| { |
| case AVAHI_BROWSER_FAILURE: |
| fprintf(stderr, "DEBUG: browse_callback: %s\n", |
| avahi_strerror(avahi_client_errno(client))); |
| bonjour_error = 1; |
| avahi_simple_poll_quit(avahi_poll); |
| break; |
| |
| case AVAHI_BROWSER_NEW: |
| /* |
| * This object is new on the network. Create a device entry for it if |
| * it doesn't yet exist. |
| */ |
| |
| service = get_service((cups_array_t *)context, name, type, domain); |
| |
| if (flags & AVAHI_LOOKUP_RESULT_LOCAL) |
| service->is_local = 1; |
| break; |
| |
| case AVAHI_BROWSER_REMOVE: |
| case AVAHI_BROWSER_ALL_FOR_NOW: |
| case AVAHI_BROWSER_CACHE_EXHAUSTED: |
| break; |
| } |
| } |
| |
| |
| /* |
| * 'client_callback()' - Avahi client callback function. |
| */ |
| |
| static void |
| client_callback( |
| AvahiClient *client, /* I - Client information (unused) */ |
| AvahiClientState state, /* I - Current state */ |
| void *context) /* I - User data (unused) */ |
| { |
| (void)client; |
| (void)context; |
| |
| /* |
| * If the connection drops, quit. |
| */ |
| |
| if (state == AVAHI_CLIENT_FAILURE) |
| { |
| fputs("DEBUG: Avahi connection failed.\n", stderr); |
| bonjour_error = 1; |
| avahi_simple_poll_quit(avahi_poll); |
| } |
| } |
| #endif /* HAVE_AVAHI */ |
| |
| |
| /* |
| * 'compare_services()' - Compare two devices. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_services(ippfind_srv_t *a, /* I - First device */ |
| ippfind_srv_t *b) /* I - Second device */ |
| { |
| return (strcmp(a->name, b->name)); |
| } |
| |
| |
| /* |
| * 'dnssd_error_string()' - Return an error string for an error code. |
| */ |
| |
| static const char * /* O - Error message */ |
| dnssd_error_string(int error) /* I - Error number */ |
| { |
| # ifdef HAVE_DNSSD |
| switch (error) |
| { |
| case kDNSServiceErr_NoError : |
| return ("OK."); |
| |
| default : |
| case kDNSServiceErr_Unknown : |
| return ("Unknown error."); |
| |
| case kDNSServiceErr_NoSuchName : |
| return ("Service not found."); |
| |
| case kDNSServiceErr_NoMemory : |
| return ("Out of memory."); |
| |
| case kDNSServiceErr_BadParam : |
| return ("Bad parameter."); |
| |
| case kDNSServiceErr_BadReference : |
| return ("Bad service reference."); |
| |
| case kDNSServiceErr_BadState : |
| return ("Bad state."); |
| |
| case kDNSServiceErr_BadFlags : |
| return ("Bad flags."); |
| |
| case kDNSServiceErr_Unsupported : |
| return ("Unsupported."); |
| |
| case kDNSServiceErr_NotInitialized : |
| return ("Not initialized."); |
| |
| case kDNSServiceErr_AlreadyRegistered : |
| return ("Already registered."); |
| |
| case kDNSServiceErr_NameConflict : |
| return ("Name conflict."); |
| |
| case kDNSServiceErr_Invalid : |
| return ("Invalid name."); |
| |
| case kDNSServiceErr_Firewall : |
| return ("Firewall prevents registration."); |
| |
| case kDNSServiceErr_Incompatible : |
| return ("Client library incompatible."); |
| |
| case kDNSServiceErr_BadInterfaceIndex : |
| return ("Bad interface index."); |
| |
| case kDNSServiceErr_Refused : |
| return ("Server prevents registration."); |
| |
| case kDNSServiceErr_NoSuchRecord : |
| return ("Record not found."); |
| |
| case kDNSServiceErr_NoAuth : |
| return ("Authentication required."); |
| |
| case kDNSServiceErr_NoSuchKey : |
| return ("Encryption key not found."); |
| |
| case kDNSServiceErr_NATTraversal : |
| return ("Unable to traverse NAT boundary."); |
| |
| case kDNSServiceErr_DoubleNAT : |
| return ("Unable to traverse double-NAT boundary."); |
| |
| case kDNSServiceErr_BadTime : |
| return ("Bad system time."); |
| |
| case kDNSServiceErr_BadSig : |
| return ("Bad signature."); |
| |
| case kDNSServiceErr_BadKey : |
| return ("Bad encryption key."); |
| |
| case kDNSServiceErr_Transient : |
| return ("Transient error occurred - please try again."); |
| |
| case kDNSServiceErr_ServiceNotRunning : |
| return ("Server not running."); |
| |
| case kDNSServiceErr_NATPortMappingUnsupported : |
| return ("NAT doesn't support NAT-PMP or UPnP."); |
| |
| case kDNSServiceErr_NATPortMappingDisabled : |
| return ("NAT supports NAT-PNP or UPnP but it is disabled."); |
| |
| case kDNSServiceErr_NoRouter : |
| return ("No Internet/default router configured."); |
| |
| case kDNSServiceErr_PollingMode : |
| return ("Service polling mode error."); |
| |
| #ifndef _WIN32 |
| case kDNSServiceErr_Timeout : |
| return ("Service timeout."); |
| #endif /* !_WIN32 */ |
| } |
| |
| # elif defined(HAVE_AVAHI) |
| return (avahi_strerror(error)); |
| # endif /* HAVE_DNSSD */ |
| } |
| |
| |
| /* |
| * 'eval_expr()' - Evaluate the expressions against the specified service. |
| * |
| * Returns 1 for true and 0 for false. |
| */ |
| |
| static int /* O - Result of evaluation */ |
| eval_expr(ippfind_srv_t *service, /* I - Service */ |
| ippfind_expr_t *expressions) /* I - Expressions */ |
| { |
| ippfind_op_t logic; /* Logical operation */ |
| int result; /* Result of current expression */ |
| ippfind_expr_t *expression; /* Current expression */ |
| const char *val; /* TXT value */ |
| |
| /* |
| * Loop through the expressions... |
| */ |
| |
| if (expressions && expressions->parent) |
| logic = expressions->parent->op; |
| else |
| logic = IPPFIND_OP_AND; |
| |
| for (expression = expressions; expression; expression = expression->next) |
| { |
| switch (expression->op) |
| { |
| default : |
| case IPPFIND_OP_AND : |
| case IPPFIND_OP_OR : |
| if (expression->child) |
| result = eval_expr(service, expression->child); |
| else |
| result = expression->op == IPPFIND_OP_AND; |
| break; |
| case IPPFIND_OP_TRUE : |
| result = 1; |
| break; |
| case IPPFIND_OP_FALSE : |
| result = 0; |
| break; |
| case IPPFIND_OP_IS_LOCAL : |
| result = service->is_local; |
| break; |
| case IPPFIND_OP_IS_REMOTE : |
| result = !service->is_local; |
| break; |
| case IPPFIND_OP_DOMAIN_REGEX : |
| result = !regexec(&(expression->re), service->domain, 0, NULL, 0); |
| break; |
| case IPPFIND_OP_NAME_REGEX : |
| result = !regexec(&(expression->re), service->name, 0, NULL, 0); |
| break; |
| case IPPFIND_OP_NAME_LITERAL : |
| result = !_cups_strcasecmp(expression->name, service->name); |
| break; |
| case IPPFIND_OP_HOST_REGEX : |
| result = !regexec(&(expression->re), service->host, 0, NULL, 0); |
| break; |
| case IPPFIND_OP_PORT_RANGE : |
| result = service->port >= expression->range[0] && |
| service->port <= expression->range[1]; |
| break; |
| case IPPFIND_OP_PATH_REGEX : |
| result = !regexec(&(expression->re), service->resource, 0, NULL, 0); |
| break; |
| case IPPFIND_OP_TXT_EXISTS : |
| result = cupsGetOption(expression->name, service->num_txt, |
| service->txt) != NULL; |
| break; |
| case IPPFIND_OP_TXT_REGEX : |
| val = cupsGetOption(expression->name, service->num_txt, |
| service->txt); |
| if (val) |
| result = !regexec(&(expression->re), val, 0, NULL, 0); |
| else |
| result = 0; |
| |
| if (getenv("IPPFIND_DEBUG")) |
| printf("TXT_REGEX of \"%s\": %d\n", val, result); |
| break; |
| case IPPFIND_OP_URI_REGEX : |
| result = !regexec(&(expression->re), service->uri, 0, NULL, 0); |
| break; |
| case IPPFIND_OP_EXEC : |
| result = exec_program(service, expression->num_args, |
| expression->args); |
| break; |
| case IPPFIND_OP_LIST : |
| result = list_service(service); |
| break; |
| case IPPFIND_OP_PRINT_NAME : |
| _cupsLangPuts(stdout, service->name); |
| result = 1; |
| break; |
| case IPPFIND_OP_PRINT_URI : |
| _cupsLangPuts(stdout, service->uri); |
| result = 1; |
| break; |
| case IPPFIND_OP_QUIET : |
| result = 1; |
| break; |
| } |
| |
| if (expression->invert) |
| result = !result; |
| |
| if (logic == IPPFIND_OP_AND && !result) |
| return (0); |
| else if (logic == IPPFIND_OP_OR && result) |
| return (1); |
| } |
| |
| return (logic == IPPFIND_OP_AND); |
| } |
| |
| |
| /* |
| * 'exec_program()' - Execute a program for a service. |
| */ |
| |
| static int /* O - 1 if program terminated |
| successfully, 0 otherwise. */ |
| exec_program(ippfind_srv_t *service, /* I - Service */ |
| int num_args, /* I - Number of command-line args */ |
| char **args) /* I - Command-line arguments */ |
| { |
| char **myargv, /* Command-line arguments */ |
| **myenvp, /* Environment variables */ |
| *ptr, /* Pointer into variable */ |
| domain[1024], /* IPPFIND_SERVICE_DOMAIN */ |
| hostname[1024], /* IPPFIND_SERVICE_HOSTNAME */ |
| name[256], /* IPPFIND_SERVICE_NAME */ |
| port[32], /* IPPFIND_SERVICE_PORT */ |
| regtype[256], /* IPPFIND_SERVICE_REGTYPE */ |
| scheme[128], /* IPPFIND_SERVICE_SCHEME */ |
| uri[1024], /* IPPFIND_SERVICE_URI */ |
| txt[100][256]; /* IPPFIND_TXT_foo */ |
| int i, /* Looping var */ |
| myenvc, /* Number of environment variables */ |
| status; /* Exit status of program */ |
| #ifndef _WIN32 |
| char program[1024]; /* Program to execute */ |
| int pid; /* Process ID */ |
| #endif /* !_WIN32 */ |
| |
| |
| /* |
| * Environment variables... |
| */ |
| |
| snprintf(domain, sizeof(domain), "IPPFIND_SERVICE_DOMAIN=%s", |
| service->domain); |
| snprintf(hostname, sizeof(hostname), "IPPFIND_SERVICE_HOSTNAME=%s", |
| service->host); |
| snprintf(name, sizeof(name), "IPPFIND_SERVICE_NAME=%s", service->name); |
| snprintf(port, sizeof(port), "IPPFIND_SERVICE_PORT=%d", service->port); |
| snprintf(regtype, sizeof(regtype), "IPPFIND_SERVICE_REGTYPE=%s", |
| service->regtype); |
| snprintf(scheme, sizeof(scheme), "IPPFIND_SERVICE_SCHEME=%s", |
| !strncmp(service->regtype, "_http._tcp", 10) ? "http" : |
| !strncmp(service->regtype, "_https._tcp", 11) ? "https" : |
| !strncmp(service->regtype, "_ipp._tcp", 9) ? "ipp" : |
| !strncmp(service->regtype, "_ipps._tcp", 10) ? "ipps" : "lpd"); |
| snprintf(uri, sizeof(uri), "IPPFIND_SERVICE_URI=%s", service->uri); |
| for (i = 0; i < service->num_txt && i < 100; i ++) |
| { |
| snprintf(txt[i], sizeof(txt[i]), "IPPFIND_TXT_%s=%s", service->txt[i].name, |
| service->txt[i].value); |
| for (ptr = txt[i] + 12; *ptr && *ptr != '='; ptr ++) |
| *ptr = (char)_cups_toupper(*ptr); |
| } |
| |
| for (i = 0, myenvc = 7 + service->num_txt; environ[i]; i ++) |
| if (strncmp(environ[i], "IPPFIND_", 8)) |
| myenvc ++; |
| |
| if ((myenvp = calloc(sizeof(char *), (size_t)(myenvc + 1))) == NULL) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Out of memory.")); |
| exit(IPPFIND_EXIT_MEMORY); |
| } |
| |
| for (i = 0, myenvc = 0; environ[i]; i ++) |
| if (strncmp(environ[i], "IPPFIND_", 8)) |
| myenvp[myenvc++] = environ[i]; |
| |
| myenvp[myenvc++] = domain; |
| myenvp[myenvc++] = hostname; |
| myenvp[myenvc++] = name; |
| myenvp[myenvc++] = port; |
| myenvp[myenvc++] = regtype; |
| myenvp[myenvc++] = scheme; |
| myenvp[myenvc++] = uri; |
| |
| for (i = 0; i < service->num_txt && i < 100; i ++) |
| myenvp[myenvc++] = txt[i]; |
| |
| /* |
| * Allocate and copy command-line arguments... |
| */ |
| |
| if ((myargv = calloc(sizeof(char *), (size_t)(num_args + 1))) == NULL) |
| { |
| _cupsLangPuts(stderr, _("ippfind: Out of memory.")); |
| exit(IPPFIND_EXIT_MEMORY); |
| } |
| |
| for (i = 0; i < num_args; i ++) |
| { |
| if (strchr(args[i], '{')) |
| { |
| char temp[2048], /* Temporary string */ |
| *tptr, /* Pointer into temporary string */ |
| keyword[256], /* {keyword} */ |
| *kptr; /* Pointer into keyword */ |
| |
| for (ptr = args[i], tptr = temp; *ptr; ptr ++) |
| { |
| if (*ptr == '{') |
| { |
| /* |
| * Do a {var} substitution... |
| */ |
| |
| for (kptr = keyword, ptr ++; *ptr && *ptr != '}'; ptr ++) |
| if (kptr < (keyword + sizeof(keyword) - 1)) |
| *kptr++ = *ptr; |
| |
| if (*ptr != '}') |
| { |
| _cupsLangPuts(stderr, |
| _("ippfind: Missing close brace in substitution.")); |
| exit(IPPFIND_EXIT_SYNTAX); |
| } |
| |
| *kptr = '\0'; |
| if (!keyword[0] || !strcmp(keyword, "service_uri")) |
| strlcpy(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_domain")) |
| strlcpy(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_hostname")) |
| strlcpy(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_name")) |
| strlcpy(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_path")) |
| strlcpy(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_port")) |
| strlcpy(tptr, port + 21, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strcmp(keyword, "service_scheme")) |
| strlcpy(tptr, scheme + 22, sizeof(temp) - (size_t)(tptr - temp)); |
| else if (!strncmp(keyword, "txt_", 4)) |
| { |
| const char *val = cupsGetOption(keyword + 4, service->num_txt, service->txt); |
| if (val) |
| strlcpy(tptr, val, sizeof(temp) - (size_t)(tptr - temp)); |
| else |
| *tptr = '\0'; |
| } |
| else |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unknown variable \"{%s}\"."), |
| keyword); |
| exit(IPPFIND_EXIT_SYNTAX); |
| } |
| |
| tptr += strlen(tptr); |
| } |
| else if (tptr < (temp + sizeof(temp) - 1)) |
| *tptr++ = *ptr; |
| } |
| |
| *tptr = '\0'; |
| myargv[i] = strdup(temp); |
| } |
| else |
| myargv[i] = strdup(args[i]); |
| } |
| |
| #ifdef _WIN32 |
| if (getenv("IPPFIND_DEBUG")) |
| { |
| printf("\nProgram:\n %s\n", args[0]); |
| puts("\nArguments:"); |
| for (i = 0; i < num_args; i ++) |
| printf(" %s\n", myargv[i]); |
| puts("\nEnvironment:"); |
| for (i = 0; i < myenvc; i ++) |
| printf(" %s\n", myenvp[i]); |
| } |
| |
| status = _spawnvpe(_P_WAIT, args[0], myargv, myenvp); |
| |
| #else |
| /* |
| * Execute the program... |
| */ |
| |
| if (strchr(args[0], '/') && !access(args[0], X_OK)) |
| strlcpy(program, args[0], sizeof(program)); |
| else if (!cupsFileFind(args[0], getenv("PATH"), 1, program, sizeof(program))) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"), |
| args[0], strerror(ENOENT)); |
| exit(IPPFIND_EXIT_SYNTAX); |
| } |
| |
| if (getenv("IPPFIND_DEBUG")) |
| { |
| printf("\nProgram:\n %s\n", program); |
| puts("\nArguments:"); |
| for (i = 0; i < num_args; i ++) |
| printf(" %s\n", myargv[i]); |
| puts("\nEnvironment:"); |
| for (i = 0; i < myenvc; i ++) |
| printf(" %s\n", myenvp[i]); |
| } |
| |
| if ((pid = fork()) == 0) |
| { |
| /* |
| * Child comes here... |
| */ |
| |
| execve(program, myargv, myenvp); |
| exit(1); |
| } |
| else if (pid < 0) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"), |
| args[0], strerror(errno)); |
| exit(IPPFIND_EXIT_SYNTAX); |
| } |
| else |
| { |
| /* |
| * Wait for it to complete... |
| */ |
| |
| while (wait(&status) != pid) |
| ; |
| } |
| #endif /* _WIN32 */ |
| |
| /* |
| * Free memory... |
| */ |
| |
| for (i = 0; i < num_args; i ++) |
| free(myargv[i]); |
| |
| free(myargv); |
| free(myenvp); |
| |
| /* |
| * Return whether the program succeeded or crashed... |
| */ |
| |
| if (getenv("IPPFIND_DEBUG")) |
| { |
| #ifdef _WIN32 |
| printf("Exit Status: %d\n", status); |
| #else |
| if (WIFEXITED(status)) |
| printf("Exit Status: %d\n", WEXITSTATUS(status)); |
| else |
| printf("Terminating Signal: %d\n", WTERMSIG(status)); |
| #endif /* _WIN32 */ |
| } |
| |
| return (status == 0); |
| } |
| |
| |
| /* |
| * 'get_service()' - Create or update a device. |
| */ |
| |
| static ippfind_srv_t * /* O - Service */ |
| get_service(cups_array_t *services, /* I - Service array */ |
| const char *serviceName, /* I - Name of service/device */ |
| const char *regtype, /* I - Type of service */ |
| const char *replyDomain) /* I - Service domain */ |
| { |
| ippfind_srv_t key, /* Search key */ |
| *service; /* Service */ |
| char fullName[kDNSServiceMaxDomainName]; |
| /* Full name for query */ |
| |
| |
| /* |
| * See if this is a new device... |
| */ |
| |
| key.name = (char *)serviceName; |
| key.regtype = (char *)regtype; |
| |
| for (service = cupsArrayFind(services, &key); |
| service; |
| service = cupsArrayNext(services)) |
| if (_cups_strcasecmp(service->name, key.name)) |
| break; |
| else if (!strcmp(service->regtype, key.regtype)) |
| return (service); |
| |
| /* |
| * Yes, add the service... |
| */ |
| |
| service = calloc(sizeof(ippfind_srv_t), 1); |
| service->name = strdup(serviceName); |
| service->domain = strdup(replyDomain); |
| service->regtype = strdup(regtype); |
| |
| cupsArrayAdd(services, service); |
| |
| /* |
| * Set the "full name" of this service, which is used for queries and |
| * resolves... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); |
| #else /* HAVE_AVAHI */ |
| avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, |
| regtype, replyDomain); |
| #endif /* HAVE_DNSSD */ |
| |
| service->fullName = strdup(fullName); |
| |
| return (service); |
| } |
| |
| |
| /* |
| * 'get_time()' - Get the current time-of-day in seconds. |
| */ |
| |
| static double |
| get_time(void) |
| { |
| #ifdef _WIN32 |
| struct _timeb curtime; /* Current Windows time */ |
| |
| _ftime(&curtime); |
| |
| return (curtime.time + 0.001 * curtime.millitm); |
| |
| #else |
| struct timeval curtime; /* Current UNIX time */ |
| |
| if (gettimeofday(&curtime, NULL)) |
| return (0.0); |
| else |
| return (curtime.tv_sec + 0.000001 * curtime.tv_usec); |
| #endif /* _WIN32 */ |
| } |
| |
| |
| /* |
| * 'list_service()' - List the contents of a service. |
| */ |
| |
| static int /* O - 1 if successful, 0 otherwise */ |
| list_service(ippfind_srv_t *service) /* I - Service */ |
| { |
| http_addrlist_t *addrlist; /* Address(es) of service */ |
| char port[10]; /* Port number of service */ |
| |
| |
| snprintf(port, sizeof(port), "%d", service->port); |
| |
| if ((addrlist = httpAddrGetList(service->host, address_family, port)) == NULL) |
| { |
| _cupsLangPrintf(stdout, "%s unreachable", service->uri); |
| return (0); |
| } |
| |
| if (!strncmp(service->regtype, "_ipp._tcp", 9) || |
| !strncmp(service->regtype, "_ipps._tcp", 10)) |
| { |
| /* |
| * IPP/IPPS printer |
| */ |
| |
| http_t *http; /* HTTP connection */ |
| ipp_t *request, /* IPP request */ |
| *response; /* IPP response */ |
| ipp_attribute_t *attr; /* IPP attribute */ |
| int i, /* Looping var */ |
| count, /* Number of values */ |
| version, /* IPP version */ |
| paccepting; /* printer-is-accepting-jobs value */ |
| ipp_pstate_t pstate; /* printer-state value */ |
| char preasons[1024], /* Comma-delimited printer-state-reasons */ |
| *ptr, /* Pointer into reasons */ |
| *end; /* End of reasons buffer */ |
| static const char * const rattrs[] =/* Requested attributes */ |
| { |
| "printer-is-accepting-jobs", |
| "printer-state", |
| "printer-state-reasons" |
| }; |
| |
| /* |
| * Connect to the printer... |
| */ |
| |
| http = httpConnect2(service->host, service->port, addrlist, address_family, |
| !strncmp(service->regtype, "_ipps._tcp", 10) ? |
| HTTP_ENCRYPTION_ALWAYS : |
| HTTP_ENCRYPTION_IF_REQUESTED, |
| 1, 30000, NULL); |
| |
| httpAddrFreeList(addrlist); |
| |
| if (!http) |
| { |
| _cupsLangPrintf(stdout, "%s unavailable", service->uri); |
| return (0); |
| } |
| |
| /* |
| * Get the current printer state... |
| */ |
| |
| response = NULL; |
| version = ipp_version; |
| |
| do |
| { |
| request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); |
| ippSetVersion(request, version / 10, version % 10); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, |
| service->uri); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, |
| "requesting-user-name", NULL, cupsUser()); |
| ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, |
| "requested-attributes", |
| (int)(sizeof(rattrs) / sizeof(rattrs[0])), NULL, rattrs); |
| |
| response = cupsDoRequest(http, request, service->resource); |
| |
| if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11) |
| version = 11; |
| } |
| while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11); |
| |
| /* |
| * Show results... |
| */ |
| |
| if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE) |
| { |
| _cupsLangPrintf(stdout, "%s: unavailable", service->uri); |
| return (0); |
| } |
| |
| if ((attr = ippFindAttribute(response, "printer-state", |
| IPP_TAG_ENUM)) != NULL) |
| pstate = (ipp_pstate_t)ippGetInteger(attr, 0); |
| else |
| pstate = IPP_PSTATE_STOPPED; |
| |
| if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs", |
| IPP_TAG_BOOLEAN)) != NULL) |
| paccepting = ippGetBoolean(attr, 0); |
| else |
| paccepting = 0; |
| |
| if ((attr = ippFindAttribute(response, "printer-state-reasons", |
| IPP_TAG_KEYWORD)) != NULL) |
| { |
| strlcpy(preasons, ippGetString(attr, 0, NULL), sizeof(preasons)); |
| |
| for (i = 1, count = ippGetCount(attr), ptr = preasons + strlen(preasons), |
| end = preasons + sizeof(preasons) - 1; |
| i < count && ptr < end; |
| i ++, ptr += strlen(ptr)) |
| { |
| *ptr++ = ','; |
| strlcpy(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1)); |
| } |
| } |
| else |
| strlcpy(preasons, "none", sizeof(preasons)); |
| |
| ippDelete(response); |
| httpClose(http); |
| |
| _cupsLangPrintf(stdout, "%s %s %s %s", service->uri, ippEnumString("printer-state", (int)pstate), paccepting ? "accepting-jobs" : "not-accepting-jobs", preasons); |
| } |
| else if (!strncmp(service->regtype, "_http._tcp", 10) || |
| !strncmp(service->regtype, "_https._tcp", 11)) |
| { |
| /* |
| * HTTP/HTTPS web page |
| */ |
| |
| http_t *http; /* HTTP connection */ |
| http_status_t status; /* HEAD status */ |
| |
| |
| /* |
| * Connect to the web server... |
| */ |
| |
| http = httpConnect2(service->host, service->port, addrlist, address_family, |
| !strncmp(service->regtype, "_ipps._tcp", 10) ? |
| HTTP_ENCRYPTION_ALWAYS : |
| HTTP_ENCRYPTION_IF_REQUESTED, |
| 1, 30000, NULL); |
| |
| httpAddrFreeList(addrlist); |
| |
| if (!http) |
| { |
| _cupsLangPrintf(stdout, "%s unavailable", service->uri); |
| return (0); |
| } |
| |
| if (httpGet(http, service->resource)) |
| { |
| _cupsLangPrintf(stdout, "%s unavailable", service->uri); |
| return (0); |
| } |
| |
| do |
| { |
| status = httpUpdate(http); |
| } |
| while (status == HTTP_STATUS_CONTINUE); |
| |
| httpFlush(http); |
| httpClose(http); |
| |
| if (status >= HTTP_STATUS_BAD_REQUEST) |
| { |
| _cupsLangPrintf(stdout, "%s unavailable", service->uri); |
| return (0); |
| } |
| |
| _cupsLangPrintf(stdout, "%s available", service->uri); |
| } |
| else if (!strncmp(service->regtype, "_printer._tcp", 13)) |
| { |
| /* |
| * LPD printer |
| */ |
| |
| int sock; /* Socket */ |
| |
| |
| if (!httpAddrConnect(addrlist, &sock)) |
| { |
| _cupsLangPrintf(stdout, "%s unavailable", service->uri); |
| httpAddrFreeList(addrlist); |
| return (0); |
| } |
| |
| _cupsLangPrintf(stdout, "%s available", service->uri); |
| httpAddrFreeList(addrlist); |
| |
| httpAddrClose(NULL, sock); |
| } |
| else |
| { |
| _cupsLangPrintf(stdout, "%s unsupported", service->uri); |
| httpAddrFreeList(addrlist); |
| return (0); |
| } |
| |
| return (1); |
| } |
| |
| |
| /* |
| * 'new_expr()' - Create a new expression. |
| */ |
| |
| static ippfind_expr_t * /* O - New expression */ |
| new_expr(ippfind_op_t op, /* I - Operation */ |
| int invert, /* I - Invert result? */ |
| const char *value, /* I - TXT key or port range */ |
| const char *regex, /* I - Regular expression */ |
| char **args) /* I - Pointer to argument strings */ |
| { |
| ippfind_expr_t *temp; /* New expression */ |
| |
| |
| if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL) |
| return (NULL); |
| |
| temp->op = op; |
| temp->invert = invert; |
| |
| if (op == IPPFIND_OP_TXT_EXISTS || op == IPPFIND_OP_TXT_REGEX || op == IPPFIND_OP_NAME_LITERAL) |
| temp->name = (char *)value; |
| else if (op == IPPFIND_OP_PORT_RANGE) |
| { |
| /* |
| * Pull port number range of the form "number", "-number" (0-number), |
| * "number-" (number-65535), and "number-number". |
| */ |
| |
| if (*value == '-') |
| { |
| temp->range[1] = atoi(value + 1); |
| } |
| else if (strchr(value, '-')) |
| { |
| if (sscanf(value, "%d-%d", temp->range, temp->range + 1) == 1) |
| temp->range[1] = 65535; |
| } |
| else |
| { |
| temp->range[0] = temp->range[1] = atoi(value); |
| } |
| } |
| |
| if (regex) |
| { |
| int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_ICASE | REG_EXTENDED); |
| |
| if (err) |
| { |
| char message[256]; /* Error message */ |
| |
| regerror(err, &(temp->re), message, sizeof(message)); |
| _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"), |
| message); |
| exit(IPPFIND_EXIT_SYNTAX); |
| } |
| } |
| |
| if (args) |
| { |
| int num_args; /* Number of arguments */ |
| |
| for (num_args = 1; args[num_args]; num_args ++) |
| if (!strcmp(args[num_args], ";")) |
| break; |
| |
| temp->num_args = num_args; |
| temp->args = malloc((size_t)num_args * sizeof(char *)); |
| memcpy(temp->args, args, (size_t)num_args * sizeof(char *)); |
| } |
| |
| return (temp); |
| } |
| |
| |
| #ifdef HAVE_AVAHI |
| /* |
| * 'poll_callback()' - Wait for input on the specified file descriptors. |
| * |
| * Note: This function is needed because avahi_simple_poll_iterate is broken |
| * and always uses a timeout of 0 (!) milliseconds. |
| * (Avahi Ticket #364) |
| */ |
| |
| static int /* O - Number of file descriptors matching */ |
| poll_callback( |
| struct pollfd *pollfds, /* I - File descriptors */ |
| unsigned int num_pollfds, /* I - Number of file descriptors */ |
| int timeout, /* I - Timeout in milliseconds (unused) */ |
| void *context) /* I - User data (unused) */ |
| { |
| int val; /* Return value */ |
| |
| |
| (void)timeout; |
| (void)context; |
| |
| val = poll(pollfds, num_pollfds, 500); |
| |
| if (val > 0) |
| avahi_got_data = 1; |
| |
| return (val); |
| } |
| #endif /* HAVE_AVAHI */ |
| |
| |
| /* |
| * 'resolve_callback()' - Process resolve data. |
| */ |
| |
| #ifdef HAVE_DNSSD |
| static void DNSSD_API |
| resolve_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Data flags */ |
| uint32_t interfaceIndex, /* I - Interface */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *fullName, /* I - Full service name */ |
| const char *hostTarget, /* I - Hostname */ |
| uint16_t port, /* I - Port number (network byte order) */ |
| uint16_t txtLen, /* I - Length of TXT record data */ |
| const unsigned char *txtRecord, /* I - TXT record data */ |
| void *context) /* I - Service */ |
| { |
| char key[256], /* TXT key value */ |
| *value; /* Value from TXT record */ |
| const unsigned char *txtEnd; /* End of TXT record */ |
| uint8_t valueLen; /* Length of value */ |
| ippfind_srv_t *service = (ippfind_srv_t *)context; |
| /* Service */ |
| |
| |
| /* |
| * Only process "add" data... |
| */ |
| |
| (void)sdRef; |
| (void)flags; |
| (void)interfaceIndex; |
| (void)fullName; |
| |
| if (errorCode != kDNSServiceErr_NoError) |
| { |
| _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), |
| dnssd_error_string(errorCode)); |
| bonjour_error = 1; |
| return; |
| } |
| |
| service->is_resolved = 1; |
| service->host = strdup(hostTarget); |
| service->port = ntohs(port); |
| |
| value = service->host + strlen(service->host) - 1; |
| if (value >= service->host && *value == '.') |
| *value = '\0'; |
| |
| /* |
| * Loop through the TXT key/value pairs and add them to an array... |
| */ |
| |
| for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen) |
| { |
| /* |
| * Ignore bogus strings... |
| */ |
| |
| valueLen = *txtRecord++; |
| |
| memcpy(key, txtRecord, valueLen); |
| key[valueLen] = '\0'; |
| |
| if ((value = strchr(key, '=')) == NULL) |
| continue; |
| |
| *value++ = '\0'; |
| |
| /* |
| * Add to array of TXT values... |
| */ |
| |
| service->num_txt = cupsAddOption(key, value, service->num_txt, |
| &(service->txt)); |
| } |
| |
| set_service_uri(service); |
| } |
| |
| |
| #elif defined(HAVE_AVAHI) |
| static void |
| resolve_callback( |
| AvahiServiceResolver *resolver, /* I - Resolver */ |
| AvahiIfIndex interface, /* I - Interface */ |
| AvahiProtocol protocol, /* I - Address protocol */ |
| AvahiResolverEvent event, /* I - Event */ |
| const char *serviceName,/* I - Service name */ |
| const char *regtype, /* I - Registration type */ |
| const char *replyDomain,/* I - Domain name */ |
| const char *hostTarget, /* I - FQDN */ |
| const AvahiAddress *address, /* I - Address */ |
| uint16_t port, /* I - Port number */ |
| AvahiStringList *txt, /* I - TXT records */ |
| AvahiLookupResultFlags flags, /* I - Lookup flags */ |
| void *context) /* I - Service */ |
| { |
| char key[256], /* TXT key */ |
| *value; /* TXT value */ |
| ippfind_srv_t *service = (ippfind_srv_t *)context; |
| /* Service */ |
| AvahiStringList *current; /* Current TXT key/value pair */ |
| |
| |
| (void)address; |
| |
| if (event != AVAHI_RESOLVER_FOUND) |
| { |
| bonjour_error = 1; |
| |
| avahi_service_resolver_free(resolver); |
| avahi_simple_poll_quit(avahi_poll); |
| return; |
| } |
| |
| service->is_resolved = 1; |
| service->host = strdup(hostTarget); |
| service->port = port; |
| |
| value = service->host + strlen(service->host) - 1; |
| if (value >= service->host && *value == '.') |
| *value = '\0'; |
| |
| /* |
| * Loop through the TXT key/value pairs and add them to an array... |
| */ |
| |
| for (current = txt; current; current = current->next) |
| { |
| /* |
| * Ignore bogus strings... |
| */ |
| |
| if (current->size > (sizeof(key) - 1)) |
| continue; |
| |
| memcpy(key, current->text, current->size); |
| key[current->size] = '\0'; |
| |
| if ((value = strchr(key, '=')) == NULL) |
| continue; |
| |
| *value++ = '\0'; |
| |
| /* |
| * Add to array of TXT values... |
| */ |
| |
| service->num_txt = cupsAddOption(key, value, service->num_txt, |
| &(service->txt)); |
| } |
| |
| set_service_uri(service); |
| } |
| #endif /* HAVE_DNSSD */ |
| |
| |
| /* |
| * 'set_service_uri()' - Set the URI of the service. |
| */ |
| |
| static void |
| set_service_uri(ippfind_srv_t *service) /* I - Service */ |
| { |
| char uri[1024]; /* URI */ |
| const char *path, /* Resource path */ |
| *scheme; /* URI scheme */ |
| |
| |
| if (!strncmp(service->regtype, "_http.", 6)) |
| { |
| scheme = "http"; |
| path = cupsGetOption("path", service->num_txt, service->txt); |
| } |
| else if (!strncmp(service->regtype, "_https.", 7)) |
| { |
| scheme = "https"; |
| path = cupsGetOption("path", service->num_txt, service->txt); |
| } |
| else if (!strncmp(service->regtype, "_ipp.", 5)) |
| { |
| scheme = "ipp"; |
| path = cupsGetOption("rp", service->num_txt, service->txt); |
| } |
| else if (!strncmp(service->regtype, "_ipps.", 6)) |
| { |
| scheme = "ipps"; |
| path = cupsGetOption("rp", service->num_txt, service->txt); |
| } |
| else if (!strncmp(service->regtype, "_printer.", 9)) |
| { |
| scheme = "lpd"; |
| path = cupsGetOption("rp", service->num_txt, service->txt); |
| } |
| else |
| return; |
| |
| if (!path || !*path) |
| path = "/"; |
| |
| if (*path == '/') |
| { |
| service->resource = strdup(path); |
| } |
| else |
| { |
| snprintf(uri, sizeof(uri), "/%s", path); |
| service->resource = strdup(uri); |
| } |
| |
| httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, |
| service->host, service->port, service->resource); |
| service->uri = strdup(uri); |
| } |
| |
| |
| /* |
| * 'show_usage()' - Show program usage. |
| */ |
| |
| static void |
| show_usage(void) |
| { |
| _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]" |
| "[.domain.] ... [expression]\n" |
| " ippfind [options] name[.regtype[.domain.]] " |
| "... [expression]\n" |
| " ippfind --help\n" |
| " ippfind --version")); |
| _cupsLangPuts(stderr, _("Options:")); |
| _cupsLangPuts(stderr, _("-4 Connect using IPv4")); |
| _cupsLangPuts(stderr, _("-6 Connect using IPv6")); |
| _cupsLangPuts(stderr, _("-T seconds Set the browse timeout in seconds")); |
| _cupsLangPuts(stderr, _("-V version Set default IPP version")); |
| _cupsLangPuts(stderr, _("--version Show program version")); |
| _cupsLangPuts(stderr, _("Expressions:")); |
| _cupsLangPuts(stderr, _("-P number[-number] Match port to number or range")); |
| _cupsLangPuts(stderr, _("-d regex Match domain to regular expression")); |
| _cupsLangPuts(stderr, _("-h regex Match hostname to regular expression")); |
| _cupsLangPuts(stderr, _("-l List attributes")); |
| _cupsLangPuts(stderr, _("-n regex Match service name to regular expression")); |
| _cupsLangPuts(stderr, _("-p Print URI if true")); |
| _cupsLangPuts(stderr, _("-q Quietly report match via exit code")); |
| _cupsLangPuts(stderr, _("-r True if service is remote")); |
| _cupsLangPuts(stderr, _("-s Print service name if true")); |
| _cupsLangPuts(stderr, _("-t key True if the TXT record contains the key")); |
| _cupsLangPuts(stderr, _("-u regex Match URI to regular expression")); |
| _cupsLangPuts(stderr, _("-x utility [argument ...] ;\n" |
| " Execute program if true")); |
| _cupsLangPuts(stderr, _("--domain regex Match domain to regular expression")); |
| _cupsLangPuts(stderr, _("--exec utility [argument ...] ;\n" |
| " Execute program if true")); |
| _cupsLangPuts(stderr, _("--host regex Match hostname to regular expression")); |
| _cupsLangPuts(stderr, _("--ls List attributes")); |
| _cupsLangPuts(stderr, _("--local True if service is local")); |
| _cupsLangPuts(stderr, _("--name regex Match service name to regular expression")); |
| _cupsLangPuts(stderr, _("--path regex Match resource path to regular expression")); |
| _cupsLangPuts(stderr, _("--port number[-number] Match port to number or range")); |
| _cupsLangPuts(stderr, _("--print Print URI if true")); |
| _cupsLangPuts(stderr, _("--print-name Print service name if true")); |
| _cupsLangPuts(stderr, _("--quiet Quietly report match via exit code")); |
| _cupsLangPuts(stderr, _("--remote True if service is remote")); |
| _cupsLangPuts(stderr, _("--txt key True if the TXT record contains the key")); |
| _cupsLangPuts(stderr, _("--txt-* regex Match TXT record key to regular expression")); |
| _cupsLangPuts(stderr, _("--uri regex Match URI to regular expression")); |
| _cupsLangPuts(stderr, _("Modifiers:")); |
| _cupsLangPuts(stderr, _("( expressions ) Group expressions")); |
| _cupsLangPuts(stderr, _("! expression Unary NOT of expression")); |
| _cupsLangPuts(stderr, _("--not expression Unary NOT of expression")); |
| _cupsLangPuts(stderr, _("--false Always false")); |
| _cupsLangPuts(stderr, _("--true Always true")); |
| _cupsLangPuts(stderr, _("expression expression Logical AND")); |
| _cupsLangPuts(stderr, _("expression --and expression\n" |
| " Logical AND")); |
| _cupsLangPuts(stderr, _("expression --or expression\n" |
| " Logical OR")); |
| _cupsLangPuts(stderr, _("Substitutions:")); |
| _cupsLangPuts(stderr, _("{} URI")); |
| _cupsLangPuts(stderr, _("{service_domain} Domain name")); |
| _cupsLangPuts(stderr, _("{service_hostname} Fully-qualified domain name")); |
| _cupsLangPuts(stderr, _("{service_name} Service instance name")); |
| _cupsLangPuts(stderr, _("{service_port} Port number")); |
| _cupsLangPuts(stderr, _("{service_regtype} DNS-SD registration type")); |
| _cupsLangPuts(stderr, _("{service_scheme} URI scheme")); |
| _cupsLangPuts(stderr, _("{service_uri} URI")); |
| _cupsLangPuts(stderr, _("{txt_*} Value of TXT record key")); |
| _cupsLangPuts(stderr, _("Environment Variables:")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_DOMAIN Domain name")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_HOSTNAME\n" |
| " Fully-qualified domain name")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_NAME Service instance name")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_PORT Port number")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_REGTYPE DNS-SD registration type")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_SCHEME URI scheme")); |
| _cupsLangPuts(stderr, _("IPPFIND_SERVICE_URI URI")); |
| _cupsLangPuts(stderr, _("IPPFIND_TXT_* Value of TXT record key")); |
| |
| exit(IPPFIND_EXIT_TRUE); |
| } |
| |
| |
| /* |
| * 'show_version()' - Show program version. |
| */ |
| |
| static void |
| show_version(void) |
| { |
| _cupsLangPuts(stderr, CUPS_SVERSION); |
| |
| exit(IPPFIND_EXIT_TRUE); |
| } |