]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Save work on new ippfind tool.
authormsweet <msweet@a1ca3aef-8c08-0410-bb20-df032aa958be>
Thu, 30 May 2013 21:45:32 +0000 (21:45 +0000)
committermsweet <msweet@a1ca3aef-8c08-0410-bb20-df032aa958be>
Thu, 30 May 2013 21:45:32 +0000 (21:45 +0000)
git-svn-id: svn+ssh://src.apple.com/svn/cups/cups.org/trunk@11002 a1ca3aef-8c08-0410-bb20-df032aa958be

test/ippfind.c [new file with mode: 0644]

diff --git a/test/ippfind.c b/test/ippfind.c
new file mode 100644 (file)
index 0000000..73f395c
--- /dev/null
@@ -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 <cups/cups-private.h>
+#include <regex.h>
+#ifdef HAVE_DNSSD
+#  include <dns_sd.h>
+#elif defined(HAVE_AVAHI)
+#  include <avahi-client/client.h>
+#  include <avahi-client/lookup.h>
+#  include <avahi-common/simple-watch.h>
+#  include <avahi-common/domain.h>
+#  include <avahi-common/error.h>
+#  include <avahi-common/malloc.h>
+#  define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
+#endif /* HAVE_DNSSD */
+
+
+/*
+ * 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$".
+ */