/* * 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 #ifdef _WIN32 # include # include #else # include #endif /* _WIN32 */ #include #ifdef HAVE_DNSSD # include #elif defined(HAVE_AVAHI) # include # include # include # include # include # include # 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); }