diff options
Diffstat (limited to 'tools/ippfind.c')
-rw-r--r-- | tools/ippfind.c | 2847 |
1 files changed, 2847 insertions, 0 deletions
diff --git a/tools/ippfind.c b/tools/ippfind.c new file mode 100644 index 000000000..246ab4dba --- /dev/null +++ b/tools/ippfind.c @@ -0,0 +1,2847 @@ +/* + * 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); +} |