From: msweet Date: Thu, 30 May 2013 21:45:32 +0000 (+0000) Subject: Save work on new ippfind tool. X-Git-Tag: release-1.7rc1~48 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bac0799207c3f0d02c0c7a5c9d68419c43679b4b;p=thirdparty%2Fcups.git Save work on new ippfind tool. git-svn-id: svn+ssh://src.apple.com/svn/cups/cups.org/trunk@11002 a1ca3aef-8c08-0410-bb20-df032aa958be --- diff --git a/test/ippfind.c b/test/ippfind.c new file mode 100644 index 0000000000..73f395c26f --- /dev/null +++ b/test/ippfind.c @@ -0,0 +1,1319 @@ +/* + * "$Id$" + * + * 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-2013 by Apple Inc. + * + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". + * + * This file is subject to the Apple OS-Developed Software exception. + * + * Usage: + * + * ./ippfind [options] regtype[,subtype][.domain.] ... [expression] + * ./ippfind [options] name[.regtype[.domain.]] ... [expression] + * ./ippfind --help + * ./ippfind --version + * + * Supported regtypes are: + * + * _http._tcp - HTTP (RFC 2616) + * _https._tcp - HTTPS (RFC 2818) + * _ipp._tcp - IPP (RFC 2911) + * _ipps._tcp - IPPS (pending) + * _printer._tcp - LPD (RFC 1179) + * + * Exit Codes: + * + * 0 if result for all processed expressions is true + * 1 if result of any processed expression is false + * 2 if browsing or any query or resolution failed + * 3 if an undefined option or invalid expression was specified + * + * Options: + * + * --help - Show program help + * --version - Show program version + * -4 - Use IPv4 when listing + * -6 - Use IPv6 when listing + * -T seconds - Specify browse timeout (default 10 + * seconds) + * -V version - Specify IPP version (1.1, 2.0, 2.1, 2.2) + * + * "expression" is any of the following: + * + * -d regex + * --domain regex - True if the domain matches the given + * regular expression. + * + * -e utility [argument ...] ; + * --exec utility [argument ...] ; - Executes the specified program; "{}" + * does a substitution (see below) + * + * -l + * --ls - Lists attributes returned by + * Get-Printer-Attributes for IPP printers, + * ???? of HEAD request for HTTP URLs) + * True if resource is accessible, false + * otherwise. + * + * --local - True if the service is local to this + * computer. + * + * -n regex + * --name regex - True if the name matches the given + * regular expression. + * + * --path regex - True if the URI resource path matches + * the given regular expression. + * + * -p + * --print - Prints the URI of found printers (always + * true, default if -e, -l, -p, -q, or -s + * is not specified. + * + * -q + * --quiet - Quiet mode (just return exit code) + * + * -r + * --remote - True if the service is not local to this + * computer. + * + * -s + * --print-name - Prints the service name of found + * printers. + * + * -t key + * --txt key - True if the TXT record contains the + * named key + * + * --txt-* regex - True if the TXT record contains the + * named key and matches the given regular + * expression. + * + * -u regex + * --uri regex - True if the URI matches the given + * regular expression. + * + * Expressions may also contain modifiers: + * + * ( expression ) - Group the result of expressions. + * + * ! expression + * --not expression - Unary NOT + * + * --false - Always false + * --true - Always true + * + * expression expression + * expression --and expression + * expression -a expression - Logical AND. + * + * expression -o expression + * expression --or expression - Logical OR. + * + * The substitutions for {} are: + * + * {} - URI + * {service_domain} - Domain name + * {service_hostname} - FQDN + * {service_name} - Service name + * {service_port} - Port number + * {service_regtype} - DNS-SD registration type + * {service_scheme} - URI scheme for DNS-SD registration type + * {service_uri} - URI + * {txt_*} - Value of TXT record key + * + * These variables are also set in the environment for executed programs: + * + * IPPFIND_SERVICE_DOMAIN - Domain name + * IPPFIND_SERVICE_HOSTNAME - FQDN + * IPPFIND_SERVICE_NAME - Service name + * IPPFIND_SERVICE_PORT - Port number + * IPPFIND_SERVICE_REGTYPE - DNS-SD registration type + * IPPFIND_SERVICE_SCHEME - URI scheme for DNS-SD registration type + * IPPFIND_SERVICE_URI - URI + * IPPFIND_TXT_* - Values of TXT record key (uppercase) + * + * Contents: + * + */ + +/* + * Include necessary headers. + */ + +#include +#include +#ifdef HAVE_DNSSD +# include +#elif defined(HAVE_AVAHI) +# include +# include +# include +# include +# include +# include +# define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX +#endif /* HAVE_DNSSD */ + + +/* + * Structures... + */ + +typedef enum ippfind_exit_e /* Exit codes */ +{ + IPPFIND_EXIT_OK = 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_t; + +typedef enum ippfind_op_e /* Operations for expressions */ +{ + 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_DOMAIN_REGEX, /* Domain matches regular expression */ + IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */ + 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 */ + + IPPFIND_OP_OUTPUT = 100, /* 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 *key; /* TXT record key */ + regex_t re; /* Regular expression for matching */ +} 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 */ + *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? */ + time_t resolve_time; /* Time we started the resolve */ +} 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 int ipp_version = 20; /* IPP version for LIST */ +static double timeout = 10; /* Timeout in seconds */ + + +/* + * Local functions... + */ + +#ifdef HAVE_DNSSD +static void browse_callback(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, void *context) + __attribute__((nonnull(1,5,6,7,8))); +static void browse_local_callback(DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context) + __attribute__((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 ippfind_srv_t *get_service(cups_array_t *services, + const char *serviceName, + const char *regtype, + const char *replyDomain) + __attribute__((nonnull(1,2,3,4))); +#ifdef HAVE_DNSSD +static void 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); + __attribute__((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, + AvahiBrowserEvent event, + const char *serviceName, + const char *regtype, + const char *replyDomain, + const char *host_name, + 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) __attribute__((noreturn)); +static void show_version(void) __attribute__((noreturn)); +static void unquote(char *dst, const char *src, size_t dstsize) + __attribute__((nonnull(1,2))); + + +/* + * 'main()' - Browse for printers. + */ + +int /* O - Exit status */ +main(int argc, /* I - Number of command-line args */ + char *argv[]) /* I - Command-line arguments */ +{ + int fd; /* File descriptor for Bonjour */ + cups_array_t *services; /* Service array */ + ippfind_srv_t *service; /* Current service */ + + + /* + * Initialize the locale... + */ + + _cupsSetLocale(argv); + + /* + * Create an array to track services... + */ + + services = cupsArrayNew((cups_array_func_t)compare_services, NULL); + + /* + * Start up masters for browsing/resolving... + */ + +#ifdef HAVE_DNSSD + if (DNSServiceCreateConnection(&dnssd_ref) != kDNSServiceErr_NoError) + { + perror("ERROR: Unable to create service connection"); + return (IPPFIND_EXIT_BONJOUR); + } + + fd = DNSServiceRefSockFD(dnssd_ref); + +#elif defined(HAVE_AVAHI) +#endif /* HAVE_DNSSD */ + +#if 0 + int i; /* Looping var */ + DNSServiceRef main_ref, /* Main service reference */ + ipp_ref; /* IPP service reference */ + int fd; /* Main file descriptor */ + fd_set input; /* Input set for select() */ + struct timeval timeout; /* Timeout for select() */ + cups_array_t *devices; /* Device array */ + ippfind_srv_t *device; /* Current device */ + http_t *http; /* Connection to printer */ + ipp_t *request, /* Get-Printer-Attributes request */ + *response; /* Get-Printer-Attributes response */ + ipp_attribute_t *attr; /* IPP attribute in response */ + const char *version, /* Version supported */ + *testfile; /* Test file to use */ + int ipponly = 0, /* Do IPP tests only? */ + snmponly = 0; /* Do SNMP walk only? */ + + + for (i = 1; i < argc; i ++) + if (!strcmp(argv[i], "snmp")) + snmponly = 1; + else if (!strcmp(argv[i], "ipp")) + ipponly = 1; + else + { + puts("Usage: ./ipp-printers [{ipp | snmp}]"); + return (1); + } + + /* + * Create an array to track devices... + */ + + devices = cupsArrayNew((cups_array_func_t)compare_services, NULL); + + /* + * Browse for different kinds of printers... + */ + + if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError) + { + perror("ERROR: Unable to create service connection"); + return (1); + } + + fd = DNSServiceRefSockFD(main_ref); + + ipp_ref = main_ref; + DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, + "_ipp._tcp", NULL, browse_callback, devices); + + /* + * Loop until we are killed... + */ + + progress(); + + for (;;) + { + FD_ZERO(&input); + FD_SET(fd, &input); + + timeout.tv_sec = 2; + timeout.tv_usec = 500000; + + if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0) + { + time_t curtime = time(NULL); + + for (device = (ippfind_srv_t *)cupsArrayFirst(devices); + device; + device = (ippfind_srv_t *)cupsArrayNext(devices)) + if (!device->got_resolve) + { + if (!device->ref) + break; + + if ((curtime - device->resolve_time) > 10) + { + device->got_resolve = -1; + fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n", + device->name); + progress(); + } + else + break; + } + + if (!device) + break; + } + + if (FD_ISSET(fd, &input)) + { + /* + * Process results of our browsing... + */ + + progress(); + DNSServiceProcessResult(main_ref); + } + else + { + /* + * Query any devices we've found... + */ + + DNSServiceErrorType status; /* DNS query status */ + int count; /* Number of queries */ + + + for (device = (ippfind_srv_t *)cupsArrayFirst(devices), count = 0; + device; + device = (ippfind_srv_t *)cupsArrayNext(devices)) + { + if (!device->ref && !device->sent) + { + /* + * Found the device, now get the TXT record(s) for it... + */ + + if (count < 50) + { + device->resolve_time = time(NULL); + device->ref = main_ref; + + status = DNSServiceResolve(&(device->ref), + kDNSServiceFlagsShareConnection, + 0, device->name, device->regtype, + device->domain, resolve_callback, + device); + if (status != kDNSServiceErr_NoError) + { + fprintf(stderr, "\rUnable to resolve \"%s\": %d\n", + device->name, status); + progress(); + } + else + count ++; + } + } + else if (!device->sent && device->got_resolve) + { + /* + * Got the TXT records, now report the device... + */ + + DNSServiceRefDeallocate(device->ref); + device->ref = 0; + device->sent = 1; + } + } + } + } + +#ifndef DEBUG + fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n", + cupsArrayCount(devices)); +#endif /* !DEBUG */ + + puts("#!/bin/sh -x"); + puts("test -d results && rm -rf results"); + puts("mkdir results"); + puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL"); + puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|" + "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER"); + + for (device = (ippfind_srv_t *)cupsArrayFirst(devices); + device; + device = (ippfind_srv_t *)cupsArrayNext(devices)) + { + if (device->got_resolve <= 0 || device->cups_shared) + continue; + +#ifdef DEBUG + fprintf(stderr, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n", + device->name, device->got_resolve, device->cups_shared, device->uri); +#else + fprintf(stderr, "Checking \"%s\"...\n", device->name); +#endif /* DEBUG */ + + if ((http = httpConnect(device->host, device->port)) == NULL) + { + fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name, + cupsLastErrorString()); + continue; + } + + request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, + device->uri); + + response = cupsDoRequest(http, request, device->rp); + + if (cupsLastError() > IPP_OK_SUBST) + fprintf(stderr, "Failed to query \"%s\": %s\n", device->name, + cupsLastErrorString()); + else + { + if ((attr = ippFindAttribute(response, "ipp-versions-supported", + IPP_TAG_KEYWORD)) != NULL) + { + version = attr->values[0].string.text; + + for (i = 1; i < attr->num_values; i ++) + if (strcmp(attr->values[i].string.text, version) > 0) + version = attr->values[i].string.text; + } + else + version = "1.0"; + + testfile = NULL; + + if ((attr = ippFindAttribute(response, "document-format-supported", + IPP_TAG_MIMETYPE)) != NULL) + { + /* + * Figure out the test file for printing, preferring PDF and PostScript + * over JPEG and plain text... + */ + + for (i = 0; i < attr->num_values; i ++) + { + if (!strcasecmp(attr->values[i].string.text, "application/pdf")) + { + testfile = "testfile.pdf"; + break; + } + else if (!strcasecmp(attr->values[i].string.text, + "application/postscript")) + testfile = "testfile.ps"; + else if (!strcasecmp(attr->values[i].string.text, "image/jpeg") && + !testfile) + testfile = "testfile.jpg"; + else if (!strcasecmp(attr->values[i].string.text, "text/plain") && + !testfile) + testfile = "testfile.txt"; + else if (!strcasecmp(attr->values[i].string.text, + "application/vnd.hp-PCL") && !testfile) + testfile = "testfile.pcl"; + } + + if (!testfile) + { + fprintf(stderr, + "Printer \"%s\" reports the following IPP file formats:\n", + device->name); + for (i = 0; i < attr->num_values; i ++) + fprintf(stderr, " \"%s\"\n", attr->values[i].string.text); + } + } + + if (!testfile && device->pdl) + { + char *pdl, /* Copy of pdl string */ + *start, *end; /* Pointers into pdl string */ + + + pdl = strdup(device->pdl); + for (start = device->pdl; start && *start; start = end) + { + if ((end = strchr(start, ',')) != NULL) + *end++ = '\0'; + + if (!strcasecmp(start, "application/pdf")) + { + testfile = "testfile.pdf"; + break; + } + else if (!strcasecmp(start, "application/postscript")) + testfile = "testfile.ps"; + else if (!strcasecmp(start, "image/jpeg") && !testfile) + testfile = "testfile.jpg"; + else if (!strcasecmp(start, "text/plain") && !testfile) + testfile = "testfile.txt"; + else if (!strcasecmp(start, "application/vnd.hp-PCL") && !testfile) + testfile = "testfile.pcl"; + } + free(pdl); + + if (testfile) + { + fprintf(stderr, + "Using \"%s\" for printer \"%s\" based on TXT record pdl " + "info.\n", testfile, device->name); + } + else + { + fprintf(stderr, + "Printer \"%s\" reports the following TXT file formats:\n", + device->name); + fprintf(stderr, " \"%s\"\n", device->pdl); + } + } + + if (!device->ty && + (attr = ippFindAttribute(response, "printer-make-and-model", + IPP_TAG_TEXT)) != NULL) + device->ty = strdup(attr->values[0].string.text); + + if (strcmp(version, "1.0") && testfile && device->ty) + { + char filename[1024], /* Filename */ + *fileptr; /* Pointer into filename */ + const char *typtr; /* Pointer into ty */ + + if (!strncasecmp(device->ty, "DeskJet", 7) || + !strncasecmp(device->ty, "DesignJet", 9) || + !strncasecmp(device->ty, "OfficeJet", 9) || + !strncasecmp(device->ty, "Photosmart", 10)) + strlcpy(filename, "HP_", sizeof(filename)); + else + filename[0] = '\0'; + + fileptr = filename + strlen(filename); + + if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29)) + typtr = device->ty + 22; + else + typtr = device->ty; + + while (*typtr && fileptr < (filename + sizeof(filename) - 1)) + { + if (isalnum(*typtr & 255) || *typtr == '-') + *fileptr++ = *typtr++; + else + { + *fileptr++ = '_'; + typtr++; + } + } + + *fileptr = '\0'; + + printf("# %s\n", device->name); + printf("echo \"Testing %s...\"\n", device->name); + + if (!ipponly) + { + printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " + "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n", + device->host, filename); + printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " + "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | " + "tee -a results/%s.snmpwalk\n", + device->host, filename); + } + + if (!snmponly) + { + printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " + "ipp-%s.test\" > results/%s.log\n", testfile, version, + device->uri, version, filename); + printf("CUPS_DEBUG_LOG=results/%s.debug_log " + "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " + "ipp-%s.test | tee -a results/%s.log\n", filename, + testfile, version, device->uri, + version, filename); + } + + puts(""); + } + else if (!device->ty) + fprintf(stderr, + "Ignoring \"%s\" since it doesn't provide a make and model.\n", + device->name); + else if (!testfile) + fprintf(stderr, + "Ignoring \"%s\" since it does not support a common format.\n", + device->name); + else + fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n", + device->name); + } + + ippDelete(response); + httpClose(http); + } + + return (0); +#endif /* 0 */ +} + + +#ifdef HAVE_DNSSD +/* + * 'browse_callback()' - Browse devices. + */ + +static void +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... + */ + + 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 +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... + */ + + 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(simple_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)); +} + + +/* + * '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); +} + + +#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) + fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno)); + else if (val > 0) + got_data = 1; + + return (val); +} +#endif /* HAVE_AVAHI */ + + +/* + * 'resolve_callback()' - Process resolve data. + */ + +#ifdef HAVE_DNSSD +static void +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... + */ + + if (errorCode != kDNSServiceErr_NoError) + return; + + service->is_resolved = 1; + service->host = strdup(hostTarget); + service->port = ntohs(port); + + /* + * 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 */ + AvahiBrowserEvent 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 */ + uint16_t port, /* I - Port number */ + AvahiStringList *txt, /* I - TXT records */ + AvahiLookupResultFlags flags, /* I - Lookup flags */ + void *context) /* I - Service */ +{ + char uri[1024]; /* URI */ + key[256], /* TXT key */ + *value; /* TXT value */ + ippfind_srv_t *service = (ippfind_srv_t *)context; + /* Service */ + AvahiStringList *current; /* Current TXT key/value pair */ + + + if (event != AVAHI_RESOLVER_FOUND) + { + bonjour_error = 1; + + avahi_service_resolver_free(resolver); + avahi_simple_poll_quit(uribuf->poll); + return; + } + + service->is_resolved = 1; + service->host = strdup(hostTarget); + service->port = ntohs(port); + + /* + * 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 == '/') + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL, + service->host, service->port, path); + else + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL, + service->host, service->port, "/%s", path); + + service->uri = strdup(uri); +} + + +/* + * 'usage()' - Show program usage. + */ + +static void +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, ""); + _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, _(" --help Show this help.")); + _cupsLangPuts(stderr, _(" --version Show program version.")); + _cupsLangPuts(stderr, ""); + _cupsLangPuts(stderr, _("Expressions:")); + _cupsLangPuts(stderr, _(" -d regex Match domain to regular expression.")); + _cupsLangPuts(stderr, _(" -e utility [argument ...] ;\n" + " Execute program if true.")); + _cupsLangPuts(stderr, _(" -l List attributes.")); + _cupsLangPuts(stderr, _(" --local True if service is local.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" --path regex Match resource path 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, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" -d name=value Set named variable to " + "value.")); + _cupsLangPuts(stderr, _(" -f filename Set default request " + "filename.")); + _cupsLangPuts(stderr, _(" -i seconds Repeat the last file with " + "the given time interval.")); + _cupsLangPuts(stderr, _(" -n count Repeat the last file the " + "given number of times.")); + _cupsLangPuts(stderr, _(" -q Run silently.")); + _cupsLangPuts(stderr, _(" -t Produce a test report.")); + _cupsLangPuts(stderr, _(" -v Be verbose.")); + + exit(IPPFIND_EXIT_OK); +} + + +/* + * 'show_version()' - Show program version. + */ + +static void +show_version(void) +{ + _cupsLangPuts(stderr, CUPS_SVERSION); + + exit(IPPFIND_EXIT_OK); +} + + +/* + * 'unquote()' - Unquote a name string. + */ + +static void +unquote(char *dst, /* I - Destination buffer */ + const char *src, /* I - Source string */ + size_t dstsize) /* I - Size of destination buffer */ +{ + char *dstend = dst + dstsize - 1; /* End of destination buffer */ + + + while (*src && dst < dstend) + { + if (*src == '\\') + { + src ++; + if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && + isdigit(src[2] & 255)) + { + *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; + src += 3; + } + else + *dst++ = *src++; + } + else + *dst++ = *src ++; + } + + *dst = '\0'; +} + + +/* + * End of "$Id$". + */