/*
- * "$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.
*
- * 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:
+ * Copyright 2008-2015 by Apple Inc.
*
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
*/
/*
* Include necessary headers.
*/
+#define _CUPS_NO_DEPRECATED
#include <cups/cups-private.h>
+#ifdef WIN32
+# include <process.h>
+# include <sys/timeb.h>
+#else
+# include <sys/wait.h>
+#endif /* WIN32 */
#include <regex.h>
#ifdef HAVE_DNSSD
# include <dns_sd.h>
# 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_OK = 0, /* OK and result is true */
+ 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_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_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 */
- IPPFIND_OP_OUTPUT = 100, /* Output operations */
+ /* "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_QUIET /* No output when true */
} ippfind_op_t;
typedef struct ippfind_expr_s /* Expression */
int invert; /* Invert the result */
char *key; /* TXT record key */
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 */
*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 */
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;
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 */
-static double timeout = 10; /* Timeout in seconds */
/*
*/
#ifdef HAVE_DNSSD
-static void browse_callback(DNSServiceRef sdRef,
+static void DNSSD_API browse_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *regtype,
const char *replyDomain, void *context)
__attribute__((nonnull(1,5,6,7,8)));
-static void browse_local_callback(DNSServiceRef sdRef,
+static void DNSSD_API browse_local_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
#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)
__attribute__((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 resolve_callback(DNSServiceRef sdRef,
+static void DNSSD_API resolve_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *hostTarget, uint16_t port,
uint16_t txtLen,
const unsigned char *txtRecord,
- void *context);
+ void *context)
__attribute__((nonnull(1,5,6,9, 10)));
#elif defined(HAVE_AVAHI)
static int poll_callback(struct pollfd *pollfds,
static void resolve_callback(AvahiServiceResolver *res,
AvahiIfIndex interface,
AvahiProtocol protocol,
- AvahiBrowserEvent event,
+ 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,
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(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 */
+ 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",
+ "HOST_REGEX",
+ "PORT_RANGE",
+ "PATH_REGEX",
+ "TXT_EXISTS",
+ "TXT_REGEX",
+ "URI_REGEX",
+ "EXEC",
+ "LIST",
+ "PRINT_NAME",
+ "PRINT_URI",
+ "QUIET"
+ };
/*
_cupsSetLocale(argv);
/*
- * Create an array to track services...
+ * 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);
/*
- * Start up masters for browsing/resolving...
+ * Parse command-line...
*/
-#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? */
-
+ 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 (!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);
- }
+ if (argv[i][0] == '-')
+ {
+ if (argv[i][1] == '-')
+ {
+ /*
+ * Parse --option options...
+ */
- fd = DNSServiceRefSockFD(main_ref);
+ if (!strcmp(argv[i], "--and"))
+ {
+ if (logic == IPPFIND_OP_OR)
+ {
+ _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or."));
+ show_usage();
+ }
- ipp_ref = main_ref;
- DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
- "_ipp._tcp", NULL, browse_callback, devices);
+ if (!current)
+ {
+ _cupsLangPuts(stderr,
+ _("ippfind: Missing expression before \"--and\"."));
+ show_usage();
+ }
- /*
- * Loop until we are killed...
- */
+ temp = NULL;
+ }
+ else if (!strcmp(argv[i], "--domain"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--domain");
+ show_usage();
+ }
- progress();
+ 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();
+ }
- for (;;)
- {
- FD_ZERO(&input);
- FD_SET(fd, &input);
+ if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
+ argv + i)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
- timeout.tv_sec = 2;
- timeout.tv_usec = 500000;
+ while (i < argc)
+ if (!strcmp(argv[i], ";"))
+ break;
+ else
+ i ++;
- if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0)
- {
- time_t curtime = time(NULL);
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."),
+ "--exec");
+ show_usage();
+ }
- for (device = (ippfind_srv_t *)cupsArrayFirst(devices);
- device;
- device = (ippfind_srv_t *)cupsArrayNext(devices))
- if (!device->got_resolve)
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--false"))
{
- 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 ((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 (!device)
- break;
- }
-
- if (FD_ISSET(fd, &input))
- {
- /*
- * Process results of our browsing...
- */
+ 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);
- progress();
- DNSServiceProcessResult(main_ref);
- }
- else
- {
- /*
- * Query any devices we've found...
- */
+ 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], "--name"))
+ {
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ "--name");
+ show_usage();
+ }
- DNSServiceErrorType status; /* DNS query status */
- int count; /* Number of queries */
+ 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;
- 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 (parent && parent->op == IPPFIND_OP_OR)
+ {
+ /*
+ * Already setup to do "foo --or bar --or baz"...
+ */
- 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 ++;
+ temp = NULL;
}
- }
- else if (!device->sent && device->got_resolve)
- {
- /*
- * Got the TXT records, now report the device...
- */
+ else if (!current->prev && parent)
+ {
+ /*
+ * Change parent node into an OR node...
+ */
- DNSServiceRefDeallocate(device->ref);
- device->ref = 0;
- device->sent = 1;
- }
- }
- }
- }
+ parent->op = IPPFIND_OP_OR;
+ temp = NULL;
+ }
+ else if (!current->prev)
+ {
+ /*
+ * Need to group "current" in a new OR node...
+ */
-#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;
+ if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
-#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 */
+ temp->parent = parent;
+ temp->child = current;
+ current->parent = temp;
- if ((http = httpConnect(device->host, device->port)) == NULL)
- {
- fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name,
- cupsLastErrorString());
- continue;
- }
+ if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
- request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
- ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
- device->uri);
+ 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;
+ }
- response = cupsDoRequest(http, request, device->rp);
+ current->parent = temp;
+ temp->child = current;
+ current = temp;
- 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;
+ if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
- 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";
+ temp->parent = parent;
+ current->parent = temp;
- testfile = NULL;
+ if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
- 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...
- */
+ 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();
+ }
- for (i = 0; i < attr->num_values; i ++)
+ if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--port"))
{
- if (!strcasecmp(attr->values[i].string.text, "application/pdf"))
+ i ++;
+ if (i >= argc)
{
- testfile = "testfile.pdf";
- break;
+ _cupsLangPrintf(stderr,
+ _("ippfind: Expected port range after %s."),
+ "--port");
+ show_usage();
}
- 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 ((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);
- if (!testfile)
+ have_output = 1;
+ }
+ else if (!strcmp(argv[i], "--print-name"))
{
- 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 ((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);
- if (!testfile && device->pdl)
- {
- char *pdl, /* Copy of pdl string */
- *start, *end; /* Pointers into pdl string */
+ 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 */
- pdl = strdup(device->pdl);
- for (start = device->pdl; start && *start; start = end)
- {
- if ((end = strchr(start, ',')) != NULL)
- *end++ = '\0';
+ i ++;
+ if (i >= argc)
+ {
+ _cupsLangPrintf(stderr,
+ _("ippfind: Missing regular expression after %s."),
+ argv[i - 1]);
+ show_usage();
+ }
- 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 ((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 (testfile)
+ if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i],
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
+ }
+ else if (!strcmp(argv[i], "--version"))
{
- fprintf(stderr,
- "Using \"%s\" for printer \"%s\" based on TXT record pdl "
- "info.\n", testfile, device->name);
+ show_version();
}
else
{
- fprintf(stderr,
- "Printer \"%s\" reports the following TXT file formats:\n",
- device->name);
- fprintf(stderr, " \"%s\"\n", device->pdl);
+ _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."),
+ "ippfind", argv[i]);
+ show_usage();
}
- }
- if (!device->ty &&
- (attr = ippFindAttribute(response, "printer-make-and-model",
- IPP_TAG_TEXT)) != NULL)
- device->ty = strdup(attr->values[0].string.text);
+ if (temp)
+ {
+ /*
+ * Add new expression...
+ */
- 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';
+ if (logic == IPPFIND_OP_AND &&
+ current && current->prev &&
+ parent && parent->op != IPPFIND_OP_AND)
+ {
+ /*
+ * Need to re-group "current" in a new AND node...
+ */
- fileptr = filename + strlen(filename);
+ ippfind_expr_t *tempand; /* Temporary AND node */
- if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29))
- typtr = device->ty + 22;
- else
- typtr = device->ty;
+ if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
+ NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
- while (*typtr && fileptr < (filename + sizeof(filename) - 1))
- {
- if (isalnum(*typtr & 255) || *typtr == '-')
- *fileptr++ = *typtr++;
- else
- {
- *fileptr++ = '_';
- typtr++;
+ /*
+ * 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;
}
- }
- *fileptr = '\0';
+ /*
+ * Add the new node at current level...
+ */
- printf("# %s\n", device->name);
- printf("echo \"Testing %s...\"\n", device->name);
+ temp->parent = parent;
+ temp->prev = current;
- 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 (current)
+ current->next = temp;
+ else if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
- 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);
+ current = temp;
+ invert = 0;
+ logic = IPPFIND_OP_AND;
+ temp = NULL;
}
-
- 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);
- }
+ {
+ /*
+ * Parse -o options
+ */
- ippDelete(response);
- httpClose(http);
- }
+ for (opt = argv[i] + 1; *opt; opt ++)
+ {
+ switch (*opt)
+ {
+ case '4' :
+ address_family = AF_INET;
+ break;
+
+ case '6' :
+ address_family = AF_INET6;
+ 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();
+ }
- return (0);
-#endif /* 0 */
-}
+ 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...
+ */
-#ifdef HAVE_DNSSD
-/*
- * 'browse_callback()' - Browse devices.
- */
+ temp->parent = parent;
+ temp->prev = current;
-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 (current)
+ current->next = temp;
+ else if (parent)
+ parent->child = temp;
+ else
+ expressions = temp;
- if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
- return;
+ 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();
+ }
- /*
- * Get the device...
- */
+ if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
+ return (IPPFIND_EXIT_MEMORY);
- get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
-}
+ 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 (buf[0] == '_')
+ {
+ regtype = buf;
+ }
+ else if ((regtype = strstr(buf, "._")) != NULL)
+ {
+ name = buf;
+ *regtype++ = '\0';
+ }
+ else
+ {
+ 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);
+
+#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...
+ */
+
+#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));
+
+ if (name)
+ printf("name=\"%s\"\n", name);
+
+ printf("regtype=\"%s\"\n", regtype);
+
+ if (domain)
+ printf("domain=\"%s\"\n", domain);
+
+ 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
+static void DNSSD_API
browse_local_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Option flags */
* Only process "add" data...
*/
+ (void)sdRef;
+ (void)interfaceIndex;
+
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
fprintf(stderr, "DEBUG: browse_callback: %s\n",
avahi_strerror(avahi_client_errno(client)));
bonjour_error = 1;
- avahi_simple_poll_quit(simple_poll);
+ avahi_simple_poll_quit(avahi_poll);
break;
case AVAHI_BROWSER_NEW:
* it doesn't yet exist.
*/
- service = get_service((cups_array_t *)context, name, type, domain);
+ 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 */
+{
+ int logic, /* Logical operation */
+ 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_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->key, service->num_txt,
+ service->txt) != NULL;
+ break;
+ case IPPFIND_OP_TXT_REGEX :
+ val = cupsGetOption(expression->key, 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 (flags & AVAHI_LOOKUP_RESULT_LOCAL)
- service->is_local = 1;
- break;
+ if ((pid = fork()) == 0)
+ {
+ /*
+ * Child comes here...
+ */
- case AVAHI_BROWSER_REMOVE:
- case AVAHI_BROWSER_ALL_FOR_NOW:
- case AVAHI_BROWSER_CACHE_EXHAUSTED:
- break;
+ 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 */
-/*
- * 'client_callback()' - Avahi client callback function.
- */
+ /*
+ * Free memory...
+ */
-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;
+ for (i = 0; i < num_args; i ++)
+ free(myargv[i]);
+
+ free(myargv);
+ free(myenvp);
/*
- * If the connection drops, quit.
+ * Return whether the program succeeded or crashed...
*/
- if (state == AVAHI_CLIENT_FAILURE)
+ if (getenv("IPPFIND_DEBUG"))
{
- fputs("DEBUG: Avahi connection failed.\n", stderr);
- bonjour_error = 1;
- avahi_simple_poll_quit(avahi_poll);
+#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 */
}
-}
-#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));
+ return (status == 0);
}
}
+/*
+ * '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", 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)
+ temp->key = (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.
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;
+ if (val > 0)
+ avahi_got_data = 1;
return (val);
}
*/
#ifdef HAVE_DNSSD
-static void
+static void DNSSD_API
resolve_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Data flags */
* Only process "add" data...
*/
- if (errorCode != kDNSServiceErr_NoError)
+ (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...
*/
AvahiServiceResolver *resolver, /* I - Resolver */
AvahiIfIndex interface, /* I - Interface */
AvahiProtocol protocol, /* I - Address protocol */
- AvahiBrowserEvent event, /* I - Event */
+ 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 uri[1024]; /* URI */
- key[256], /* TXT key */
+ 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(uribuf->poll);
+ avahi_simple_poll_quit(avahi_poll);
return;
}
service->is_resolved = 1;
service->host = strdup(hostTarget);
- service->port = ntohs(port);
+ 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...
path = "/";
if (*path == '/')
- httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL,
- service->host, service->port, path);
+ {
+ service->resource = strdup(path);
+ }
else
- httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL,
- service->host, service->port, "/%s", path);
+ {
+ 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);
}
/*
- * 'usage()' - Show program usage.
+ * 'show_usage()' - Show program usage.
*/
static void
-usage(void)
+show_usage(void)
{
_cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
"[.domain.] ... [expression]\n"
_cupsLangPuts(stderr, _(" --version Show program version."));
_cupsLangPuts(stderr, "");
_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, _(" -e utility [argument ...] ;\n"
- " Execute program if true."));
+ _cupsLangPuts(stderr, _(" -h regex Match hostname to regular expression."));
_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);
+ _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, "");
+ _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, "");
+ _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, "");
+ _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);
}
{
_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';
+ exit(IPPFIND_EXIT_TRUE);
}
-
-
-/*
- * End of "$Id$".
- */