blob: 246ab4dba87363dd3a09e58fc41c09916a842f5c [file] [log] [blame]
/*
* Utility to find IPP printers via Bonjour/DNS-SD and optionally run
* commands such as IPP and Bonjour conformance tests. This tool is
* inspired by the UNIX "find" command, thus its name.
*
* Copyright © 2008-2018 by Apple Inc.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers.
*/
#define _CUPS_NO_DEPRECATED
#include <cups/cups-private.h>
#ifdef _WIN32
# include <process.h>
# include <sys/timeb.h>
#else
# include <sys/wait.h>
#endif /* _WIN32 */
#include <regex.h>
#ifdef HAVE_DNSSD
# include <dns_sd.h>
#elif defined(HAVE_AVAHI)
# include <avahi-client/client.h>
# include <avahi-client/lookup.h>
# include <avahi-common/simple-watch.h>
# include <avahi-common/domain.h>
# include <avahi-common/error.h>
# include <avahi-common/malloc.h>
# define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
#endif /* HAVE_DNSSD */
#ifndef _WIN32
extern char **environ; /* Process environment variables */
#endif /* !_WIN32 */
/*
* Structures...
*/
typedef enum ippfind_exit_e /* Exit codes */
{
IPPFIND_EXIT_TRUE = 0, /* OK and result is true */
IPPFIND_EXIT_FALSE, /* OK but result is false*/
IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */
IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */
IPPFIND_EXIT_MEMORY /* Out of memory */
} ippfind_exit_t;
typedef enum ippfind_op_e /* Operations for expressions */
{
/* "Evaluation" operations */
IPPFIND_OP_NONE, /* No operation */
IPPFIND_OP_AND, /* Logical AND of all children */
IPPFIND_OP_OR, /* Logical OR of all children */
IPPFIND_OP_TRUE, /* Always true */
IPPFIND_OP_FALSE, /* Always false */
IPPFIND_OP_IS_LOCAL, /* Is a local service */
IPPFIND_OP_IS_REMOTE, /* Is a remote service */
IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */
IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */
IPPFIND_OP_NAME_LITERAL, /* Name matches literal string */
IPPFIND_OP_HOST_REGEX, /* Hostname matches regular expression */
IPPFIND_OP_PORT_RANGE, /* Port matches range */
IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */
IPPFIND_OP_TXT_EXISTS, /* TXT record key exists */
IPPFIND_OP_TXT_REGEX, /* TXT record key matches regular expression */
IPPFIND_OP_URI_REGEX, /* URI matches regular expression */
/* "Output" operations */
IPPFIND_OP_EXEC, /* Execute when true */
IPPFIND_OP_LIST, /* List when true */
IPPFIND_OP_PRINT_NAME, /* Print URI when true */
IPPFIND_OP_PRINT_URI, /* Print name when true */
IPPFIND_OP_QUIET /* No output when true */
} ippfind_op_t;
typedef struct ippfind_expr_s /* Expression */
{
struct ippfind_expr_s
*prev, /* Previous expression */
*next, /* Next expression */
*parent, /* Parent expressions */
*child; /* Child expressions */
ippfind_op_t op; /* Operation code (see above) */
int invert; /* Invert the result */
char *name; /* TXT record key or literal name */
regex_t re; /* Regular expression for matching */
int range[2]; /* Port number range */
int num_args; /* Number of arguments for exec */
char **args; /* Arguments for exec */
} ippfind_expr_t;
typedef struct ippfind_srv_s /* Service information */
{
#ifdef HAVE_DNSSD
DNSServiceRef ref; /* Service reference for query */
#elif defined(HAVE_AVAHI)
AvahiServiceResolver *ref; /* Resolver */
#endif /* HAVE_DNSSD */
char *name, /* Service name */
*domain, /* Domain name */
*regtype, /* Registration type */
*fullName, /* Full name */
*host, /* Hostname */
*resource, /* Resource path */
*uri; /* URI */
int num_txt; /* Number of TXT record keys */
cups_option_t *txt; /* TXT record keys */
int port, /* Port number */
is_local, /* Is a local service? */
is_processed, /* Did we process the service? */
is_resolved; /* Got the resolve data? */
} ippfind_srv_t;
/*
* Local globals...
*/
#ifdef HAVE_DNSSD
static DNSServiceRef dnssd_ref; /* Master service reference */
#elif defined(HAVE_AVAHI)
static AvahiClient *avahi_client = NULL;/* Client information */
static int avahi_got_data = 0; /* Got data from poll? */
static AvahiSimplePoll *avahi_poll = NULL;
/* Poll information */
#endif /* HAVE_DNSSD */
static int address_family = AF_UNSPEC;
/* Address family for LIST */
static int bonjour_error = 0; /* Error browsing/resolving? */
static double bonjour_timeout = 1.0; /* Timeout in seconds */
static int ipp_version = 20; /* IPP version for LIST */
/*
* Local functions...
*/
#ifdef HAVE_DNSSD
static void DNSSD_API browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8);
static void DNSSD_API browse_local_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_NONNULL(1,5,6,7,8);
#elif defined(HAVE_AVAHI)
static void browse_callback(AvahiServiceBrowser *browser,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char *serviceName,
const char *regtype,
const char *replyDomain,
AvahiLookupResultFlags flags,
void *context);
static void client_callback(AvahiClient *client,
AvahiClientState state,
void *context);
#endif /* HAVE_AVAHI */
static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b);
static const char *dnssd_error_string(int error);
static int eval_expr(ippfind_srv_t *service,
ippfind_expr_t *expressions);
static int exec_program(ippfind_srv_t *service, int num_args,
char **args);
static ippfind_srv_t *get_service(cups_array_t *services, const char *serviceName, const char *regtype, const char *replyDomain) _CUPS_NONNULL(1,2,3,4);
static double get_time(void);
static int list_service(ippfind_srv_t *service);
static ippfind_expr_t *new_expr(ippfind_op_t op, int invert,
const char *value, const char *regex,
char **args);
#ifdef HAVE_DNSSD
static void DNSSD_API resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullName, const char *hostTarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) _CUPS_NONNULL(1,5,6,9, 10);
#elif defined(HAVE_AVAHI)
static int poll_callback(struct pollfd *pollfds,
unsigned int num_pollfds, int timeout,
void *context);
static void resolve_callback(AvahiServiceResolver *res,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiResolverEvent event,
const char *serviceName,
const char *regtype,
const char *replyDomain,
const char *host_name,
const AvahiAddress *address,
uint16_t port,
AvahiStringList *txt,
AvahiLookupResultFlags flags,
void *context);
#endif /* HAVE_DNSSD */
static void set_service_uri(ippfind_srv_t *service);
static void show_usage(void) _CUPS_NORETURN;
static void show_version(void) _CUPS_NORETURN;
/*
* 'main()' - Browse for printers.
*/
int /* O - Exit status */
main(int argc, /* I - Number of command-line args */
char *argv[]) /* I - Command-line arguments */
{
int i, /* Looping var */
have_output = 0,/* Have output expression */
status = IPPFIND_EXIT_FALSE;
/* Exit status */
const char *opt, /* Option character */
*search; /* Current browse/resolve string */
cups_array_t *searches; /* Things to browse/resolve */
cups_array_t *services; /* Service array */
ippfind_srv_t *service; /* Current service */
ippfind_expr_t *expressions = NULL,
/* Expression tree */
*temp = NULL, /* New expression */
*parent = NULL, /* Parent expression */
*current = NULL,/* Current expression */
*parens[100]; /* Markers for parenthesis */
int num_parens = 0; /* Number of parenthesis */
ippfind_op_t logic = IPPFIND_OP_AND;
/* Logic for next expression */
int invert = 0; /* Invert expression? */
int err; /* DNS-SD error */
#ifdef HAVE_DNSSD
fd_set sinput; /* Input set for select() */
struct timeval stimeout; /* Timeout for select() */
#endif /* HAVE_DNSSD */
double endtime; /* End time */
static const char * const ops[] = /* Node operation names */
{
"NONE",
"AND",
"OR",
"TRUE",
"FALSE",
"IS_LOCAL",
"IS_REMOTE",
"DOMAIN_REGEX",
"NAME_REGEX",
"NAME_LITERAL",
"HOST_REGEX",
"PORT_RANGE",
"PATH_REGEX",
"TXT_EXISTS",
"TXT_REGEX",
"URI_REGEX",
"EXEC",
"LIST",
"PRINT_NAME",
"PRINT_URI",
"QUIET"
};
/*
* Initialize the locale...
*/
_cupsSetLocale(argv);
/*
* Create arrays to track services and things we want to browse/resolve...
*/
searches = cupsArrayNew(NULL, NULL);
services = cupsArrayNew((cups_array_func_t)compare_services, NULL);
/*
* Parse command-line...
*/
if (getenv("IPPFIND_DEBUG"))
for (i = 1; i < argc; i ++)
fprintf(stderr, "argv[%d]=\"%s\"\n", i, argv[i]);
for (i = 1; i < argc; i ++)
{
if (argv[i][0] == '-')
{
if (argv[i][1] == '-')
{
/*
* Parse --option options...
*/
if (!strcmp(argv[i], "--and"))
{
if (logic == IPPFIND_OP_OR)
{
_cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or."));
show_usage();
}
if (!current)
{
_cupsLangPuts(stderr,
_("ippfind: Missing expression before \"--and\"."));
show_usage();
}
temp = NULL;
}
else if (!strcmp(argv[i], "--domain"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
"--domain");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--exec"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ippfind: Expected program after %s."),
"--exec");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
argv + i)) == NULL)
return (IPPFIND_EXIT_MEMORY);
while (i < argc)
if (!strcmp(argv[i], ";"))
break;
else
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ippfind: Expected semi-colon after %s."),
"--exec");
show_usage();
}
have_output = 1;
}
else if (!strcmp(argv[i], "--false"))
{
if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--help"))
{
show_usage();
}
else if (!strcmp(argv[i], "--host"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
"--host");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--ls"))
{
if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
}
else if (!strcmp(argv[i], "--local"))
{
if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--literal-name"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "--literal-name");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--name"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
"--name");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--not"))
{
invert = 1;
}
else if (!strcmp(argv[i], "--or"))
{
if (!current)
{
_cupsLangPuts(stderr,
_("ippfind: Missing expression before \"--or\"."));
show_usage();
}
logic = IPPFIND_OP_OR;
if (parent && parent->op == IPPFIND_OP_OR)
{
/*
* Already setup to do "foo --or bar --or baz"...
*/
temp = NULL;
}
else if (!current->prev && parent)
{
/*
* Change parent node into an OR node...
*/
parent->op = IPPFIND_OP_OR;
temp = NULL;
}
else if (!current->prev)
{
/*
* Need to group "current" in a new OR node...
*/
if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
temp->parent = parent;
temp->child = current;
current->parent = temp;
if (parent)
parent->child = temp;
else
expressions = temp;
parent = temp;
temp = NULL;
}
else
{
/*
* Need to group previous expressions in an AND node, and then
* put that in an OR node...
*/
if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
while (current->prev)
{
current->parent = temp;
current = current->prev;
}
current->parent = temp;
temp->child = current;
current = temp;
if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
temp->parent = parent;
current->parent = temp;
if (parent)
parent->child = temp;
else
expressions = temp;
parent = temp;
temp = NULL;
}
}
else if (!strcmp(argv[i], "--path"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
"--path");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--port"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Expected port range after %s."),
"--port");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--print"))
{
if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
}
else if (!strcmp(argv[i], "--print-name"))
{
if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
}
else if (!strcmp(argv[i], "--quiet"))
{
if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
}
else if (!strcmp(argv[i], "--remote"))
{
if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--true"))
{
if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--txt"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ippfind: Expected key name after %s."),
"--txt");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strncmp(argv[i], "--txt-", 6))
{
const char *key = argv[i] + 6;/* TXT key */
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
argv[i - 1]);
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--uri"))
{
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after %s."),
"--uri");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i],
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--version"))
{
show_version();
}
else
{
_cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."),
"ippfind", argv[i]);
show_usage();
}
if (temp)
{
/*
* Add new expression...
*/
if (logic == IPPFIND_OP_AND &&
current && current->prev &&
parent && parent->op != IPPFIND_OP_AND)
{
/*
* Need to re-group "current" in a new AND node...
*/
ippfind_expr_t *tempand; /* Temporary AND node */
if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
/*
* Replace "current" with new AND node at the end of this list...
*/
current->prev->next = tempand;
tempand->prev = current->prev;
tempand->parent = parent;
/*
* Add "current to the new AND node...
*/
tempand->child = current;
current->parent = tempand;
current->prev = NULL;
parent = tempand;
}
/*
* Add the new node at current level...
*/
temp->parent = parent;
temp->prev = current;
if (current)
current->next = temp;
else if (parent)
parent->child = temp;
else
expressions = temp;
current = temp;
invert = 0;
logic = IPPFIND_OP_AND;
temp = NULL;
}
}
else
{
/*
* Parse -o options
*/
for (opt = argv[i] + 1; *opt; opt ++)
{
switch (*opt)
{
case '4' :
address_family = AF_INET;
break;
case '6' :
address_family = AF_INET6;
break;
case 'N' : /* Literal name */
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr, _("ippfind: Missing name after %s."), "-N");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_NAME_LITERAL, invert, argv[i], NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'P' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Expected port range after %s."),
"-P");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i],
NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'T' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("%s: Missing timeout for \"-T\"."),
"ippfind");
show_usage();
}
bonjour_timeout = atof(argv[i]);
break;
case 'V' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("%s: Missing version for \"-V\"."),
"ippfind");
show_usage();
}
if (!strcmp(argv[i], "1.1"))
ipp_version = 11;
else if (!strcmp(argv[i], "2.0"))
ipp_version = 20;
else if (!strcmp(argv[i], "2.1"))
ipp_version = 21;
else if (!strcmp(argv[i], "2.2"))
ipp_version = 22;
else
{
_cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."),
"ippfind", argv[i]);
show_usage();
}
break;
case 'd' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after "
"%s."), "-d");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL,
argv[i], NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'h' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after "
"%s."), "-h");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL,
argv[i], NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'l' :
if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 'n' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after "
"%s."), "-n");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL,
argv[i], NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'p' :
if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 'q' :
if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 'r' :
if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 's' :
if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 't' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing key name after %s."),
"-t");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i],
NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'u' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing regular expression after "
"%s."), "-u");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL,
argv[i], NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
break;
case 'x' :
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing program after %s."),
"-x");
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL,
argv + i)) == NULL)
return (IPPFIND_EXIT_MEMORY);
while (i < argc)
if (!strcmp(argv[i], ";"))
break;
else
i ++;
if (i >= argc)
{
_cupsLangPrintf(stderr,
_("ippfind: Missing semi-colon after %s."),
"-x");
show_usage();
}
have_output = 1;
break;
default :
_cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."),
"ippfind", *opt);
show_usage();
}
if (temp)
{
/*
* Add new expression...
*/
if (logic == IPPFIND_OP_AND &&
current && current->prev &&
parent && parent->op != IPPFIND_OP_AND)
{
/*
* Need to re-group "current" in a new AND node...
*/
ippfind_expr_t *tempand; /* Temporary AND node */
if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
/*
* Replace "current" with new AND node at the end of this list...
*/
current->prev->next = tempand;
tempand->prev = current->prev;
tempand->parent = parent;
/*
* Add "current to the new AND node...
*/
tempand->child = current;
current->parent = tempand;
current->prev = NULL;
parent = tempand;
}
/*
* Add the new node at current level...
*/
temp->parent = parent;
temp->prev = current;
if (current)
current->next = temp;
else if (parent)
parent->child = temp;
else
expressions = temp;
current = temp;
invert = 0;
logic = IPPFIND_OP_AND;
temp = NULL;
}
}
}
}
else if (!strcmp(argv[i], "("))
{
if (num_parens >= 100)
{
_cupsLangPuts(stderr, _("ippfind: Too many parenthesis."));
show_usage();
}
if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
parens[num_parens++] = temp;
if (current)
{
temp->parent = current->parent;
current->next = temp;
temp->prev = current;
}
else
expressions = temp;
parent = temp;
current = NULL;
invert = 0;
logic = IPPFIND_OP_AND;
}
else if (!strcmp(argv[i], ")"))
{
if (num_parens <= 0)
{
_cupsLangPuts(stderr, _("ippfind: Missing open parenthesis."));
show_usage();
}
current = parens[--num_parens];
parent = current->parent;
invert = 0;
logic = IPPFIND_OP_AND;
}
else if (!strcmp(argv[i], "!"))
{
invert = 1;
}
else
{
/*
* _regtype._tcp[,subtype][.domain]
*
* OR
*
* service-name[._regtype._tcp[.domain]]
*/
cupsArrayAdd(searches, argv[i]);
}
}
if (num_parens > 0)
{
_cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
show_usage();
}
if (!have_output)
{
/*
* Add an implicit --print-uri to the end...
*/
if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL)
return (IPPFIND_EXIT_MEMORY);
if (current)
{
while (current->parent)
current = current->parent;
current->next = temp;
temp->prev = current;
}
else
expressions = temp;
}
if (cupsArrayCount(searches) == 0)
{
/*
* Add an implicit browse for IPP printers ("_ipp._tcp")...
*/
cupsArrayAdd(searches, "_ipp._tcp");
}
if (getenv("IPPFIND_DEBUG"))
{
int indent = 4; /* Indentation */
puts("Expression tree:");
current = expressions;
while (current)
{
/*
* Print the current node...
*/
printf("%*s%s%s\n", indent, "", current->invert ? "!" : "",
ops[current->op]);
/*
* Advance to the next node...
*/
if (current->child)
{
current = current->child;
indent += 4;
}
else if (current->next)
current = current->next;
else if (current->parent)
{
while (current->parent)
{
indent -= 4;
current = current->parent;
if (current->next)
break;
}
current = current->next;
}
else
current = NULL;
}
puts("\nSearch items:");
for (search = (const char *)cupsArrayFirst(searches);
search;
search = (const char *)cupsArrayNext(searches))
printf(" %s\n", search);
}
/*
* Start up browsing/resolving...
*/
#ifdef HAVE_DNSSD
if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
dnssd_error_string(err));
return (IPPFIND_EXIT_BONJOUR);
}
#elif defined(HAVE_AVAHI)
if ((avahi_poll = avahi_simple_poll_new()) == NULL)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
strerror(errno));
return (IPPFIND_EXIT_BONJOUR);
}
avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);
avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll),
0, client_callback, avahi_poll, &err);
if (!avahi_client)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"),
dnssd_error_string(err));
return (IPPFIND_EXIT_BONJOUR);
}
#endif /* HAVE_DNSSD */
for (search = (const char *)cupsArrayFirst(searches);
search;
search = (const char *)cupsArrayNext(searches))
{
char buf[1024], /* Full name string */
*name = NULL, /* Service instance name */
*regtype, /* Registration type */
*domain; /* Domain, if any */
strlcpy(buf, search, sizeof(buf));
if (!strncmp(buf, "_http._", 7) || !strncmp(buf, "_https._", 8) || !strncmp(buf, "_ipp._", 6) || !strncmp(buf, "_ipps._", 7))
{
regtype = buf;
}
else if ((regtype = strstr(buf, "._")) != NULL)
{
if (strcmp(regtype, "._tcp"))
{
/*
* "something._protocol._tcp" -> search for something with the given
* protocol...
*/
name = buf;
*regtype++ = '\0';
}
else
{
/*
* "_protocol._tcp" -> search for everything with the given protocol...
*/
/* name = NULL; */
regtype = buf;
}
}
else
{
/*
* "something" -> search for something with IPP protocol...
*/
name = buf;
regtype = "_ipp._tcp";
}
for (domain = regtype; *domain; domain ++)
{
if (*domain == '.' && domain[1] != '_')
{
*domain++ = '\0';
break;
}
}
if (!*domain)
domain = NULL;
if (name)
{
/*
* Resolve the given service instance name, regtype, and domain...
*/
if (!domain)
domain = "local.";
service = get_service(services, name, regtype, domain);
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "Resolving name=\"%s\", regtype=\"%s\", domain=\"%s\"\n", name, regtype, domain);
#ifdef HAVE_DNSSD
service->ref = dnssd_ref;
err = DNSServiceResolve(&(service->ref),
kDNSServiceFlagsShareConnection, 0, name,
regtype, domain, resolve_callback,
service);
#elif defined(HAVE_AVAHI)
service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, name,
regtype, domain,
AVAHI_PROTO_UNSPEC, 0,
resolve_callback, service);
if (service->ref)
err = 0;
else
err = avahi_client_errno(avahi_client);
#endif /* HAVE_DNSSD */
}
else
{
/*
* Browse for services of the given type...
*/
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "Browsing for regtype=\"%s\", domain=\"%s\"\n", regtype, domain);
#ifdef HAVE_DNSSD
DNSServiceRef ref; /* Browse reference */
ref = dnssd_ref;
err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype,
domain, browse_callback, services);
if (!err)
{
ref = dnssd_ref;
err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly, regtype,
domain, browse_local_callback, services);
}
#elif defined(HAVE_AVAHI)
if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC, regtype, domain, 0,
browse_callback, services))
err = 0;
else
err = avahi_client_errno(avahi_client);
#endif /* HAVE_DNSSD */
}
if (err)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
dnssd_error_string(err));
return (IPPFIND_EXIT_BONJOUR);
}
}
/*
* Process browse/resolve requests...
*/
if (bonjour_timeout > 1.0)
endtime = get_time() + bonjour_timeout;
else
endtime = get_time() + 300.0;
while (get_time() < endtime)
{
int process = 0; /* Process services? */
#ifdef HAVE_DNSSD
int fd = DNSServiceRefSockFD(dnssd_ref);
/* File descriptor for DNS-SD */
FD_ZERO(&sinput);
FD_SET(fd, &sinput);
stimeout.tv_sec = 0;
stimeout.tv_usec = 500000;
if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0)
continue;
if (FD_ISSET(fd, &sinput))
{
/*
* Process responses...
*/
DNSServiceProcessResult(dnssd_ref);
}
else
{
/*
* Time to process services...
*/
process = 1;
}
#elif defined(HAVE_AVAHI)
avahi_got_data = 0;
if (avahi_simple_poll_iterate(avahi_poll, 500) > 0)
{
/*
* We've been told to exit the loop. Perhaps the connection to
* Avahi failed.
*/
return (IPPFIND_EXIT_BONJOUR);
}
if (!avahi_got_data)
{
/*
* Time to process services...
*/
process = 1;
}
#endif /* HAVE_DNSSD */
if (process)
{
/*
* Process any services that we have found...
*/
int active = 0, /* Number of active resolves */
resolved = 0, /* Number of resolved services */
processed = 0; /* Number of processed services */
for (service = (ippfind_srv_t *)cupsArrayFirst(services);
service;
service = (ippfind_srv_t *)cupsArrayNext(services))
{
if (service->is_processed)
processed ++;
if (service->is_resolved)
resolved ++;
if (!service->ref && !service->is_resolved)
{
/*
* Found a service, now resolve it (but limit to 50 active resolves...)
*/
if (active < 50)
{
#ifdef HAVE_DNSSD
service->ref = dnssd_ref;
err = DNSServiceResolve(&(service->ref),
kDNSServiceFlagsShareConnection, 0,
service->name, service->regtype,
service->domain, resolve_callback,
service);
#elif defined(HAVE_AVAHI)
service->ref = avahi_service_resolver_new(avahi_client,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
service->name,
service->regtype,
service->domain,
AVAHI_PROTO_UNSPEC, 0,
resolve_callback,
service);
if (service->ref)
err = 0;
else
err = avahi_client_errno(avahi_client);
#endif /* HAVE_DNSSD */
if (err)
{
_cupsLangPrintf(stderr,
_("ippfind: Unable to browse or resolve: %s"),
dnssd_error_string(err));
return (IPPFIND_EXIT_BONJOUR);
}
active ++;
}
}
else if (service->is_resolved && !service->is_processed)
{
/*
* Resolved, not process this service against the expressions...
*/
if (service->ref)
{
#ifdef HAVE_DNSSD
DNSServiceRefDeallocate(service->ref);
#else
avahi_service_resolver_free(service->ref);
#endif /* HAVE_DNSSD */
service->ref = NULL;
}
if (eval_expr(service, expressions))
status = IPPFIND_EXIT_TRUE;
service->is_processed = 1;
}
else if (service->ref)
active ++;
}
/*
* If we have processed all services we have discovered, then we are done.
*/
if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0)
break;
}
}
if (bonjour_error)
return (IPPFIND_EXIT_BONJOUR);
else
return (status);
}
#ifdef HAVE_DNSSD
/*
* 'browse_callback()' - Browse devices.
*/
static void DNSSD_API
browse_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Option flags */
uint32_t interfaceIndex, /* I - Interface number */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain, /* I - Service domain */
void *context) /* I - Services array */
{
/*
* Only process "add" data...
*/
(void)sdRef;
(void)interfaceIndex;
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
/*
* Get the device...
*/
get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
}
/*
* 'browse_local_callback()' - Browse local devices.
*/
static void DNSSD_API
browse_local_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Option flags */
uint32_t interfaceIndex, /* I - Interface number */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain, /* I - Service domain */
void *context) /* I - Services array */
{
ippfind_srv_t *service; /* Service */
/*
* Only process "add" data...
*/
(void)sdRef;
(void)interfaceIndex;
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
/*
* Get the device...
*/
service = get_service((cups_array_t *)context, serviceName, regtype,
replyDomain);
service->is_local = 1;
}
#endif /* HAVE_DNSSD */
#ifdef HAVE_AVAHI
/*
* 'browse_callback()' - Browse devices.
*/
static void
browse_callback(
AvahiServiceBrowser *browser, /* I - Browser */
AvahiIfIndex interface, /* I - Interface index (unused) */
AvahiProtocol protocol, /* I - Network protocol (unused) */
AvahiBrowserEvent event, /* I - What happened */
const char *name, /* I - Service name */
const char *type, /* I - Registration type */
const char *domain, /* I - Domain */
AvahiLookupResultFlags flags, /* I - Flags */
void *context) /* I - Services array */
{
AvahiClient *client = avahi_service_browser_get_client(browser);
/* Client information */
ippfind_srv_t *service; /* Service information */
(void)interface;
(void)protocol;
(void)context;
switch (event)
{
case AVAHI_BROWSER_FAILURE:
fprintf(stderr, "DEBUG: browse_callback: %s\n",
avahi_strerror(avahi_client_errno(client)));
bonjour_error = 1;
avahi_simple_poll_quit(avahi_poll);
break;
case AVAHI_BROWSER_NEW:
/*
* This object is new on the network. Create a device entry for it if
* it doesn't yet exist.
*/
service = get_service((cups_array_t *)context, name, type, domain);
if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
service->is_local = 1;
break;
case AVAHI_BROWSER_REMOVE:
case AVAHI_BROWSER_ALL_FOR_NOW:
case AVAHI_BROWSER_CACHE_EXHAUSTED:
break;
}
}
/*
* 'client_callback()' - Avahi client callback function.
*/
static void
client_callback(
AvahiClient *client, /* I - Client information (unused) */
AvahiClientState state, /* I - Current state */
void *context) /* I - User data (unused) */
{
(void)client;
(void)context;
/*
* If the connection drops, quit.
*/
if (state == AVAHI_CLIENT_FAILURE)
{
fputs("DEBUG: Avahi connection failed.\n", stderr);
bonjour_error = 1;
avahi_simple_poll_quit(avahi_poll);
}
}
#endif /* HAVE_AVAHI */
/*
* 'compare_services()' - Compare two devices.
*/
static int /* O - Result of comparison */
compare_services(ippfind_srv_t *a, /* I - First device */
ippfind_srv_t *b) /* I - Second device */
{
return (strcmp(a->name, b->name));
}
/*
* 'dnssd_error_string()' - Return an error string for an error code.
*/
static const char * /* O - Error message */
dnssd_error_string(int error) /* I - Error number */
{
# ifdef HAVE_DNSSD
switch (error)
{
case kDNSServiceErr_NoError :
return ("OK.");
default :
case kDNSServiceErr_Unknown :
return ("Unknown error.");
case kDNSServiceErr_NoSuchName :
return ("Service not found.");
case kDNSServiceErr_NoMemory :
return ("Out of memory.");
case kDNSServiceErr_BadParam :
return ("Bad parameter.");
case kDNSServiceErr_BadReference :
return ("Bad service reference.");
case kDNSServiceErr_BadState :
return ("Bad state.");
case kDNSServiceErr_BadFlags :
return ("Bad flags.");
case kDNSServiceErr_Unsupported :
return ("Unsupported.");
case kDNSServiceErr_NotInitialized :
return ("Not initialized.");
case kDNSServiceErr_AlreadyRegistered :
return ("Already registered.");
case kDNSServiceErr_NameConflict :
return ("Name conflict.");
case kDNSServiceErr_Invalid :
return ("Invalid name.");
case kDNSServiceErr_Firewall :
return ("Firewall prevents registration.");
case kDNSServiceErr_Incompatible :
return ("Client library incompatible.");
case kDNSServiceErr_BadInterfaceIndex :
return ("Bad interface index.");
case kDNSServiceErr_Refused :
return ("Server prevents registration.");
case kDNSServiceErr_NoSuchRecord :
return ("Record not found.");
case kDNSServiceErr_NoAuth :
return ("Authentication required.");
case kDNSServiceErr_NoSuchKey :
return ("Encryption key not found.");
case kDNSServiceErr_NATTraversal :
return ("Unable to traverse NAT boundary.");
case kDNSServiceErr_DoubleNAT :
return ("Unable to traverse double-NAT boundary.");
case kDNSServiceErr_BadTime :
return ("Bad system time.");
case kDNSServiceErr_BadSig :
return ("Bad signature.");
case kDNSServiceErr_BadKey :
return ("Bad encryption key.");
case kDNSServiceErr_Transient :
return ("Transient error occurred - please try again.");
case kDNSServiceErr_ServiceNotRunning :
return ("Server not running.");
case kDNSServiceErr_NATPortMappingUnsupported :
return ("NAT doesn't support NAT-PMP or UPnP.");
case kDNSServiceErr_NATPortMappingDisabled :
return ("NAT supports NAT-PNP or UPnP but it is disabled.");
case kDNSServiceErr_NoRouter :
return ("No Internet/default router configured.");
case kDNSServiceErr_PollingMode :
return ("Service polling mode error.");
#ifndef _WIN32
case kDNSServiceErr_Timeout :
return ("Service timeout.");
#endif /* !_WIN32 */
}
# elif defined(HAVE_AVAHI)
return (avahi_strerror(error));
# endif /* HAVE_DNSSD */
}
/*
* 'eval_expr()' - Evaluate the expressions against the specified service.
*
* Returns 1 for true and 0 for false.
*/
static int /* O - Result of evaluation */
eval_expr(ippfind_srv_t *service, /* I - Service */
ippfind_expr_t *expressions) /* I - Expressions */
{
ippfind_op_t logic; /* Logical operation */
int result; /* Result of current expression */
ippfind_expr_t *expression; /* Current expression */
const char *val; /* TXT value */
/*
* Loop through the expressions...
*/
if (expressions && expressions->parent)
logic = expressions->parent->op;
else
logic = IPPFIND_OP_AND;
for (expression = expressions; expression; expression = expression->next)
{
switch (expression->op)
{
default :
case IPPFIND_OP_AND :
case IPPFIND_OP_OR :
if (expression->child)
result = eval_expr(service, expression->child);
else
result = expression->op == IPPFIND_OP_AND;
break;
case IPPFIND_OP_TRUE :
result = 1;
break;
case IPPFIND_OP_FALSE :
result = 0;
break;
case IPPFIND_OP_IS_LOCAL :
result = service->is_local;
break;
case IPPFIND_OP_IS_REMOTE :
result = !service->is_local;
break;
case IPPFIND_OP_DOMAIN_REGEX :
result = !regexec(&(expression->re), service->domain, 0, NULL, 0);
break;
case IPPFIND_OP_NAME_REGEX :
result = !regexec(&(expression->re), service->name, 0, NULL, 0);
break;
case IPPFIND_OP_NAME_LITERAL :
result = !_cups_strcasecmp(expression->name, service->name);
break;
case IPPFIND_OP_HOST_REGEX :
result = !regexec(&(expression->re), service->host, 0, NULL, 0);
break;
case IPPFIND_OP_PORT_RANGE :
result = service->port >= expression->range[0] &&
service->port <= expression->range[1];
break;
case IPPFIND_OP_PATH_REGEX :
result = !regexec(&(expression->re), service->resource, 0, NULL, 0);
break;
case IPPFIND_OP_TXT_EXISTS :
result = cupsGetOption(expression->name, service->num_txt,
service->txt) != NULL;
break;
case IPPFIND_OP_TXT_REGEX :
val = cupsGetOption(expression->name, service->num_txt,
service->txt);
if (val)
result = !regexec(&(expression->re), val, 0, NULL, 0);
else
result = 0;
if (getenv("IPPFIND_DEBUG"))
printf("TXT_REGEX of \"%s\": %d\n", val, result);
break;
case IPPFIND_OP_URI_REGEX :
result = !regexec(&(expression->re), service->uri, 0, NULL, 0);
break;
case IPPFIND_OP_EXEC :
result = exec_program(service, expression->num_args,
expression->args);
break;
case IPPFIND_OP_LIST :
result = list_service(service);
break;
case IPPFIND_OP_PRINT_NAME :
_cupsLangPuts(stdout, service->name);
result = 1;
break;
case IPPFIND_OP_PRINT_URI :
_cupsLangPuts(stdout, service->uri);
result = 1;
break;
case IPPFIND_OP_QUIET :
result = 1;
break;
}
if (expression->invert)
result = !result;
if (logic == IPPFIND_OP_AND && !result)
return (0);
else if (logic == IPPFIND_OP_OR && result)
return (1);
}
return (logic == IPPFIND_OP_AND);
}
/*
* 'exec_program()' - Execute a program for a service.
*/
static int /* O - 1 if program terminated
successfully, 0 otherwise. */
exec_program(ippfind_srv_t *service, /* I - Service */
int num_args, /* I - Number of command-line args */
char **args) /* I - Command-line arguments */
{
char **myargv, /* Command-line arguments */
**myenvp, /* Environment variables */
*ptr, /* Pointer into variable */
domain[1024], /* IPPFIND_SERVICE_DOMAIN */
hostname[1024], /* IPPFIND_SERVICE_HOSTNAME */
name[256], /* IPPFIND_SERVICE_NAME */
port[32], /* IPPFIND_SERVICE_PORT */
regtype[256], /* IPPFIND_SERVICE_REGTYPE */
scheme[128], /* IPPFIND_SERVICE_SCHEME */
uri[1024], /* IPPFIND_SERVICE_URI */
txt[100][256]; /* IPPFIND_TXT_foo */
int i, /* Looping var */
myenvc, /* Number of environment variables */
status; /* Exit status of program */
#ifndef _WIN32
char program[1024]; /* Program to execute */
int pid; /* Process ID */
#endif /* !_WIN32 */
/*
* Environment variables...
*/
snprintf(domain, sizeof(domain), "IPPFIND_SERVICE_DOMAIN=%s",
service->domain);
snprintf(hostname, sizeof(hostname), "IPPFIND_SERVICE_HOSTNAME=%s",
service->host);
snprintf(name, sizeof(name), "IPPFIND_SERVICE_NAME=%s", service->name);
snprintf(port, sizeof(port), "IPPFIND_SERVICE_PORT=%d", service->port);
snprintf(regtype, sizeof(regtype), "IPPFIND_SERVICE_REGTYPE=%s",
service->regtype);
snprintf(scheme, sizeof(scheme), "IPPFIND_SERVICE_SCHEME=%s",
!strncmp(service->regtype, "_http._tcp", 10) ? "http" :
!strncmp(service->regtype, "_https._tcp", 11) ? "https" :
!strncmp(service->regtype, "_ipp._tcp", 9) ? "ipp" :
!strncmp(service->regtype, "_ipps._tcp", 10) ? "ipps" : "lpd");
snprintf(uri, sizeof(uri), "IPPFIND_SERVICE_URI=%s", service->uri);
for (i = 0; i < service->num_txt && i < 100; i ++)
{
snprintf(txt[i], sizeof(txt[i]), "IPPFIND_TXT_%s=%s", service->txt[i].name,
service->txt[i].value);
for (ptr = txt[i] + 12; *ptr && *ptr != '='; ptr ++)
*ptr = (char)_cups_toupper(*ptr);
}
for (i = 0, myenvc = 7 + service->num_txt; environ[i]; i ++)
if (strncmp(environ[i], "IPPFIND_", 8))
myenvc ++;
if ((myenvp = calloc(sizeof(char *), (size_t)(myenvc + 1))) == NULL)
{
_cupsLangPuts(stderr, _("ippfind: Out of memory."));
exit(IPPFIND_EXIT_MEMORY);
}
for (i = 0, myenvc = 0; environ[i]; i ++)
if (strncmp(environ[i], "IPPFIND_", 8))
myenvp[myenvc++] = environ[i];
myenvp[myenvc++] = domain;
myenvp[myenvc++] = hostname;
myenvp[myenvc++] = name;
myenvp[myenvc++] = port;
myenvp[myenvc++] = regtype;
myenvp[myenvc++] = scheme;
myenvp[myenvc++] = uri;
for (i = 0; i < service->num_txt && i < 100; i ++)
myenvp[myenvc++] = txt[i];
/*
* Allocate and copy command-line arguments...
*/
if ((myargv = calloc(sizeof(char *), (size_t)(num_args + 1))) == NULL)
{
_cupsLangPuts(stderr, _("ippfind: Out of memory."));
exit(IPPFIND_EXIT_MEMORY);
}
for (i = 0; i < num_args; i ++)
{
if (strchr(args[i], '{'))
{
char temp[2048], /* Temporary string */
*tptr, /* Pointer into temporary string */
keyword[256], /* {keyword} */
*kptr; /* Pointer into keyword */
for (ptr = args[i], tptr = temp; *ptr; ptr ++)
{
if (*ptr == '{')
{
/*
* Do a {var} substitution...
*/
for (kptr = keyword, ptr ++; *ptr && *ptr != '}'; ptr ++)
if (kptr < (keyword + sizeof(keyword) - 1))
*kptr++ = *ptr;
if (*ptr != '}')
{
_cupsLangPuts(stderr,
_("ippfind: Missing close brace in substitution."));
exit(IPPFIND_EXIT_SYNTAX);
}
*kptr = '\0';
if (!keyword[0] || !strcmp(keyword, "service_uri"))
strlcpy(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_domain"))
strlcpy(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_hostname"))
strlcpy(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_name"))
strlcpy(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_path"))
strlcpy(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_port"))
strlcpy(tptr, port + 21, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_scheme"))
strlcpy(tptr, scheme + 22, sizeof(temp) - (size_t)(tptr - temp));
else if (!strncmp(keyword, "txt_", 4))
{
const char *val = cupsGetOption(keyword + 4, service->num_txt, service->txt);
if (val)
strlcpy(tptr, val, sizeof(temp) - (size_t)(tptr - temp));
else
*tptr = '\0';
}
else
{
_cupsLangPrintf(stderr, _("ippfind: Unknown variable \"{%s}\"."),
keyword);
exit(IPPFIND_EXIT_SYNTAX);
}
tptr += strlen(tptr);
}
else if (tptr < (temp + sizeof(temp) - 1))
*tptr++ = *ptr;
}
*tptr = '\0';
myargv[i] = strdup(temp);
}
else
myargv[i] = strdup(args[i]);
}
#ifdef _WIN32
if (getenv("IPPFIND_DEBUG"))
{
printf("\nProgram:\n %s\n", args[0]);
puts("\nArguments:");
for (i = 0; i < num_args; i ++)
printf(" %s\n", myargv[i]);
puts("\nEnvironment:");
for (i = 0; i < myenvc; i ++)
printf(" %s\n", myenvp[i]);
}
status = _spawnvpe(_P_WAIT, args[0], myargv, myenvp);
#else
/*
* Execute the program...
*/
if (strchr(args[0], '/') && !access(args[0], X_OK))
strlcpy(program, args[0], sizeof(program));
else if (!cupsFileFind(args[0], getenv("PATH"), 1, program, sizeof(program)))
{
_cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
args[0], strerror(ENOENT));
exit(IPPFIND_EXIT_SYNTAX);
}
if (getenv("IPPFIND_DEBUG"))
{
printf("\nProgram:\n %s\n", program);
puts("\nArguments:");
for (i = 0; i < num_args; i ++)
printf(" %s\n", myargv[i]);
puts("\nEnvironment:");
for (i = 0; i < myenvc; i ++)
printf(" %s\n", myenvp[i]);
}
if ((pid = fork()) == 0)
{
/*
* Child comes here...
*/
execve(program, myargv, myenvp);
exit(1);
}
else if (pid < 0)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to execute \"%s\": %s"),
args[0], strerror(errno));
exit(IPPFIND_EXIT_SYNTAX);
}
else
{
/*
* Wait for it to complete...
*/
while (wait(&status) != pid)
;
}
#endif /* _WIN32 */
/*
* Free memory...
*/
for (i = 0; i < num_args; i ++)
free(myargv[i]);
free(myargv);
free(myenvp);
/*
* Return whether the program succeeded or crashed...
*/
if (getenv("IPPFIND_DEBUG"))
{
#ifdef _WIN32
printf("Exit Status: %d\n", status);
#else
if (WIFEXITED(status))
printf("Exit Status: %d\n", WEXITSTATUS(status));
else
printf("Terminating Signal: %d\n", WTERMSIG(status));
#endif /* _WIN32 */
}
return (status == 0);
}
/*
* 'get_service()' - Create or update a device.
*/
static ippfind_srv_t * /* O - Service */
get_service(cups_array_t *services, /* I - Service array */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain) /* I - Service domain */
{
ippfind_srv_t key, /* Search key */
*service; /* Service */
char fullName[kDNSServiceMaxDomainName];
/* Full name for query */
/*
* See if this is a new device...
*/
key.name = (char *)serviceName;
key.regtype = (char *)regtype;
for (service = cupsArrayFind(services, &key);
service;
service = cupsArrayNext(services))
if (_cups_strcasecmp(service->name, key.name))
break;
else if (!strcmp(service->regtype, key.regtype))
return (service);
/*
* Yes, add the service...
*/
service = calloc(sizeof(ippfind_srv_t), 1);
service->name = strdup(serviceName);
service->domain = strdup(replyDomain);
service->regtype = strdup(regtype);
cupsArrayAdd(services, service);
/*
* Set the "full name" of this service, which is used for queries and
* resolves...
*/
#ifdef HAVE_DNSSD
DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
#else /* HAVE_AVAHI */
avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName,
regtype, replyDomain);
#endif /* HAVE_DNSSD */
service->fullName = strdup(fullName);
return (service);
}
/*
* 'get_time()' - Get the current time-of-day in seconds.
*/
static double
get_time(void)
{
#ifdef _WIN32
struct _timeb curtime; /* Current Windows time */
_ftime(&curtime);
return (curtime.time + 0.001 * curtime.millitm);
#else
struct timeval curtime; /* Current UNIX time */
if (gettimeofday(&curtime, NULL))
return (0.0);
else
return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
#endif /* _WIN32 */
}
/*
* 'list_service()' - List the contents of a service.
*/
static int /* O - 1 if successful, 0 otherwise */
list_service(ippfind_srv_t *service) /* I - Service */
{
http_addrlist_t *addrlist; /* Address(es) of service */
char port[10]; /* Port number of service */
snprintf(port, sizeof(port), "%d", service->port);
if ((addrlist = httpAddrGetList(service->host, address_family, port)) == NULL)
{
_cupsLangPrintf(stdout, "%s unreachable", service->uri);
return (0);
}
if (!strncmp(service->regtype, "_ipp._tcp", 9) ||
!strncmp(service->regtype, "_ipps._tcp", 10))
{
/*
* IPP/IPPS printer
*/
http_t *http; /* HTTP connection */
ipp_t *request, /* IPP request */
*response; /* IPP response */
ipp_attribute_t *attr; /* IPP attribute */
int i, /* Looping var */
count, /* Number of values */
version, /* IPP version */
paccepting; /* printer-is-accepting-jobs value */
ipp_pstate_t pstate; /* printer-state value */
char preasons[1024], /* Comma-delimited printer-state-reasons */
*ptr, /* Pointer into reasons */
*end; /* End of reasons buffer */
static const char * const rattrs[] =/* Requested attributes */
{
"printer-is-accepting-jobs",
"printer-state",
"printer-state-reasons"
};
/*
* Connect to the printer...
*/
http = httpConnect2(service->host, service->port, addrlist, address_family,
!strncmp(service->regtype, "_ipps._tcp", 10) ?
HTTP_ENCRYPTION_ALWAYS :
HTTP_ENCRYPTION_IF_REQUESTED,
1, 30000, NULL);
httpAddrFreeList(addrlist);
if (!http)
{
_cupsLangPrintf(stdout, "%s unavailable", service->uri);
return (0);
}
/*
* Get the current printer state...
*/
response = NULL;
version = ipp_version;
do
{
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippSetVersion(request, version / 10, version % 10);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
service->uri);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
"requesting-user-name", NULL, cupsUser());
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
"requested-attributes",
(int)(sizeof(rattrs) / sizeof(rattrs[0])), NULL, rattrs);
response = cupsDoRequest(http, request, service->resource);
if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11)
version = 11;
}
while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11);
/*
* Show results...
*/
if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE)
{
_cupsLangPrintf(stdout, "%s: unavailable", service->uri);
return (0);
}
if ((attr = ippFindAttribute(response, "printer-state",
IPP_TAG_ENUM)) != NULL)
pstate = (ipp_pstate_t)ippGetInteger(attr, 0);
else
pstate = IPP_PSTATE_STOPPED;
if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
IPP_TAG_BOOLEAN)) != NULL)
paccepting = ippGetBoolean(attr, 0);
else
paccepting = 0;
if ((attr = ippFindAttribute(response, "printer-state-reasons",
IPP_TAG_KEYWORD)) != NULL)
{
strlcpy(preasons, ippGetString(attr, 0, NULL), sizeof(preasons));
for (i = 1, count = ippGetCount(attr), ptr = preasons + strlen(preasons),
end = preasons + sizeof(preasons) - 1;
i < count && ptr < end;
i ++, ptr += strlen(ptr))
{
*ptr++ = ',';
strlcpy(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1));
}
}
else
strlcpy(preasons, "none", sizeof(preasons));
ippDelete(response);
httpClose(http);
_cupsLangPrintf(stdout, "%s %s %s %s", service->uri, ippEnumString("printer-state", (int)pstate), paccepting ? "accepting-jobs" : "not-accepting-jobs", preasons);
}
else if (!strncmp(service->regtype, "_http._tcp", 10) ||
!strncmp(service->regtype, "_https._tcp", 11))
{
/*
* HTTP/HTTPS web page
*/
http_t *http; /* HTTP connection */
http_status_t status; /* HEAD status */
/*
* Connect to the web server...
*/
http = httpConnect2(service->host, service->port, addrlist, address_family,
!strncmp(service->regtype, "_ipps._tcp", 10) ?
HTTP_ENCRYPTION_ALWAYS :
HTTP_ENCRYPTION_IF_REQUESTED,
1, 30000, NULL);
httpAddrFreeList(addrlist);
if (!http)
{
_cupsLangPrintf(stdout, "%s unavailable", service->uri);
return (0);
}
if (httpGet(http, service->resource))
{
_cupsLangPrintf(stdout, "%s unavailable", service->uri);
return (0);
}
do
{
status = httpUpdate(http);
}
while (status == HTTP_STATUS_CONTINUE);
httpFlush(http);
httpClose(http);
if (status >= HTTP_STATUS_BAD_REQUEST)
{
_cupsLangPrintf(stdout, "%s unavailable", service->uri);
return (0);
}
_cupsLangPrintf(stdout, "%s available", service->uri);
}
else if (!strncmp(service->regtype, "_printer._tcp", 13))
{
/*
* LPD printer
*/
int sock; /* Socket */
if (!httpAddrConnect(addrlist, &sock))
{
_cupsLangPrintf(stdout, "%s unavailable", service->uri);
httpAddrFreeList(addrlist);
return (0);
}
_cupsLangPrintf(stdout, "%s available", service->uri);
httpAddrFreeList(addrlist);
httpAddrClose(NULL, sock);
}
else
{
_cupsLangPrintf(stdout, "%s unsupported", service->uri);
httpAddrFreeList(addrlist);
return (0);
}
return (1);
}
/*
* 'new_expr()' - Create a new expression.
*/
static ippfind_expr_t * /* O - New expression */
new_expr(ippfind_op_t op, /* I - Operation */
int invert, /* I - Invert result? */
const char *value, /* I - TXT key or port range */
const char *regex, /* I - Regular expression */
char **args) /* I - Pointer to argument strings */
{
ippfind_expr_t *temp; /* New expression */
if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL)
return (NULL);
temp->op = op;
temp->invert = invert;
if (op == IPPFIND_OP_TXT_EXISTS || op == IPPFIND_OP_TXT_REGEX || op == IPPFIND_OP_NAME_LITERAL)
temp->name = (char *)value;
else if (op == IPPFIND_OP_PORT_RANGE)
{
/*
* Pull port number range of the form "number", "-number" (0-number),
* "number-" (number-65535), and "number-number".
*/
if (*value == '-')
{
temp->range[1] = atoi(value + 1);
}
else if (strchr(value, '-'))
{
if (sscanf(value, "%d-%d", temp->range, temp->range + 1) == 1)
temp->range[1] = 65535;
}
else
{
temp->range[0] = temp->range[1] = atoi(value);
}
}
if (regex)
{
int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_ICASE | REG_EXTENDED);
if (err)
{
char message[256]; /* Error message */
regerror(err, &(temp->re), message, sizeof(message));
_cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"),
message);
exit(IPPFIND_EXIT_SYNTAX);
}
}
if (args)
{
int num_args; /* Number of arguments */
for (num_args = 1; args[num_args]; num_args ++)
if (!strcmp(args[num_args], ";"))
break;
temp->num_args = num_args;
temp->args = malloc((size_t)num_args * sizeof(char *));
memcpy(temp->args, args, (size_t)num_args * sizeof(char *));
}
return (temp);
}
#ifdef HAVE_AVAHI
/*
* 'poll_callback()' - Wait for input on the specified file descriptors.
*
* Note: This function is needed because avahi_simple_poll_iterate is broken
* and always uses a timeout of 0 (!) milliseconds.
* (Avahi Ticket #364)
*/
static int /* O - Number of file descriptors matching */
poll_callback(
struct pollfd *pollfds, /* I - File descriptors */
unsigned int num_pollfds, /* I - Number of file descriptors */
int timeout, /* I - Timeout in milliseconds (unused) */
void *context) /* I - User data (unused) */
{
int val; /* Return value */
(void)timeout;
(void)context;
val = poll(pollfds, num_pollfds, 500);
if (val > 0)
avahi_got_data = 1;
return (val);
}
#endif /* HAVE_AVAHI */
/*
* 'resolve_callback()' - Process resolve data.
*/
#ifdef HAVE_DNSSD
static void DNSSD_API
resolve_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Data flags */
uint32_t interfaceIndex, /* I - Interface */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *fullName, /* I - Full service name */
const char *hostTarget, /* I - Hostname */
uint16_t port, /* I - Port number (network byte order) */
uint16_t txtLen, /* I - Length of TXT record data */
const unsigned char *txtRecord, /* I - TXT record data */
void *context) /* I - Service */
{
char key[256], /* TXT key value */
*value; /* Value from TXT record */
const unsigned char *txtEnd; /* End of TXT record */
uint8_t valueLen; /* Length of value */
ippfind_srv_t *service = (ippfind_srv_t *)context;
/* Service */
/*
* Only process "add" data...
*/
(void)sdRef;
(void)flags;
(void)interfaceIndex;
(void)fullName;
if (errorCode != kDNSServiceErr_NoError)
{
_cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"),
dnssd_error_string(errorCode));
bonjour_error = 1;
return;
}
service->is_resolved = 1;
service->host = strdup(hostTarget);
service->port = ntohs(port);
value = service->host + strlen(service->host) - 1;
if (value >= service->host && *value == '.')
*value = '\0';
/*
* Loop through the TXT key/value pairs and add them to an array...
*/
for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen)
{
/*
* Ignore bogus strings...
*/
valueLen = *txtRecord++;
memcpy(key, txtRecord, valueLen);
key[valueLen] = '\0';
if ((value = strchr(key, '=')) == NULL)
continue;
*value++ = '\0';
/*
* Add to array of TXT values...
*/
service->num_txt = cupsAddOption(key, value, service->num_txt,
&(service->txt));
}
set_service_uri(service);
}
#elif defined(HAVE_AVAHI)
static void
resolve_callback(
AvahiServiceResolver *resolver, /* I - Resolver */
AvahiIfIndex interface, /* I - Interface */
AvahiProtocol protocol, /* I - Address protocol */
AvahiResolverEvent event, /* I - Event */
const char *serviceName,/* I - Service name */
const char *regtype, /* I - Registration type */
const char *replyDomain,/* I - Domain name */
const char *hostTarget, /* I - FQDN */
const AvahiAddress *address, /* I - Address */
uint16_t port, /* I - Port number */
AvahiStringList *txt, /* I - TXT records */
AvahiLookupResultFlags flags, /* I - Lookup flags */
void *context) /* I - Service */
{
char key[256], /* TXT key */
*value; /* TXT value */
ippfind_srv_t *service = (ippfind_srv_t *)context;
/* Service */
AvahiStringList *current; /* Current TXT key/value pair */
(void)address;
if (event != AVAHI_RESOLVER_FOUND)
{
bonjour_error = 1;
avahi_service_resolver_free(resolver);
avahi_simple_poll_quit(avahi_poll);
return;
}
service->is_resolved = 1;
service->host = strdup(hostTarget);
service->port = port;
value = service->host + strlen(service->host) - 1;
if (value >= service->host && *value == '.')
*value = '\0';
/*
* Loop through the TXT key/value pairs and add them to an array...
*/
for (current = txt; current; current = current->next)
{
/*
* Ignore bogus strings...
*/
if (current->size > (sizeof(key) - 1))
continue;
memcpy(key, current->text, current->size);
key[current->size] = '\0';
if ((value = strchr(key, '=')) == NULL)
continue;
*value++ = '\0';
/*
* Add to array of TXT values...
*/
service->num_txt = cupsAddOption(key, value, service->num_txt,
&(service->txt));
}
set_service_uri(service);
}
#endif /* HAVE_DNSSD */
/*
* 'set_service_uri()' - Set the URI of the service.
*/
static void
set_service_uri(ippfind_srv_t *service) /* I - Service */
{
char uri[1024]; /* URI */
const char *path, /* Resource path */
*scheme; /* URI scheme */
if (!strncmp(service->regtype, "_http.", 6))
{
scheme = "http";
path = cupsGetOption("path", service->num_txt, service->txt);
}
else if (!strncmp(service->regtype, "_https.", 7))
{
scheme = "https";
path = cupsGetOption("path", service->num_txt, service->txt);
}
else if (!strncmp(service->regtype, "_ipp.", 5))
{
scheme = "ipp";
path = cupsGetOption("rp", service->num_txt, service->txt);
}
else if (!strncmp(service->regtype, "_ipps.", 6))
{
scheme = "ipps";
path = cupsGetOption("rp", service->num_txt, service->txt);
}
else if (!strncmp(service->regtype, "_printer.", 9))
{
scheme = "lpd";
path = cupsGetOption("rp", service->num_txt, service->txt);
}
else
return;
if (!path || !*path)
path = "/";
if (*path == '/')
{
service->resource = strdup(path);
}
else
{
snprintf(uri, sizeof(uri), "/%s", path);
service->resource = strdup(uri);
}
httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL,
service->host, service->port, service->resource);
service->uri = strdup(uri);
}
/*
* 'show_usage()' - Show program usage.
*/
static void
show_usage(void)
{
_cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
"[.domain.] ... [expression]\n"
" ippfind [options] name[.regtype[.domain.]] "
"... [expression]\n"
" ippfind --help\n"
" ippfind --version"));
_cupsLangPuts(stderr, _("Options:"));
_cupsLangPuts(stderr, _("-4 Connect using IPv4"));
_cupsLangPuts(stderr, _("-6 Connect using IPv6"));
_cupsLangPuts(stderr, _("-T seconds Set the browse timeout in seconds"));
_cupsLangPuts(stderr, _("-V version Set default IPP version"));
_cupsLangPuts(stderr, _("--version Show program version"));
_cupsLangPuts(stderr, _("Expressions:"));
_cupsLangPuts(stderr, _("-P number[-number] Match port to number or range"));
_cupsLangPuts(stderr, _("-d regex Match domain to regular expression"));
_cupsLangPuts(stderr, _("-h regex Match hostname to regular expression"));
_cupsLangPuts(stderr, _("-l List attributes"));
_cupsLangPuts(stderr, _("-n regex Match service name to regular expression"));
_cupsLangPuts(stderr, _("-p Print URI if true"));
_cupsLangPuts(stderr, _("-q Quietly report match via exit code"));
_cupsLangPuts(stderr, _("-r True if service is remote"));
_cupsLangPuts(stderr, _("-s Print service name if true"));
_cupsLangPuts(stderr, _("-t key True if the TXT record contains the key"));
_cupsLangPuts(stderr, _("-u regex Match URI to regular expression"));
_cupsLangPuts(stderr, _("-x utility [argument ...] ;\n"
" Execute program if true"));
_cupsLangPuts(stderr, _("--domain regex Match domain to regular expression"));
_cupsLangPuts(stderr, _("--exec utility [argument ...] ;\n"
" Execute program if true"));
_cupsLangPuts(stderr, _("--host regex Match hostname to regular expression"));
_cupsLangPuts(stderr, _("--ls List attributes"));
_cupsLangPuts(stderr, _("--local True if service is local"));
_cupsLangPuts(stderr, _("--name regex Match service name to regular expression"));
_cupsLangPuts(stderr, _("--path regex Match resource path to regular expression"));
_cupsLangPuts(stderr, _("--port number[-number] Match port to number or range"));
_cupsLangPuts(stderr, _("--print Print URI if true"));
_cupsLangPuts(stderr, _("--print-name Print service name if true"));
_cupsLangPuts(stderr, _("--quiet Quietly report match via exit code"));
_cupsLangPuts(stderr, _("--remote True if service is remote"));
_cupsLangPuts(stderr, _("--txt key True if the TXT record contains the key"));
_cupsLangPuts(stderr, _("--txt-* regex Match TXT record key to regular expression"));
_cupsLangPuts(stderr, _("--uri regex Match URI to regular expression"));
_cupsLangPuts(stderr, _("Modifiers:"));
_cupsLangPuts(stderr, _("( expressions ) Group expressions"));
_cupsLangPuts(stderr, _("! expression Unary NOT of expression"));
_cupsLangPuts(stderr, _("--not expression Unary NOT of expression"));
_cupsLangPuts(stderr, _("--false Always false"));
_cupsLangPuts(stderr, _("--true Always true"));
_cupsLangPuts(stderr, _("expression expression Logical AND"));
_cupsLangPuts(stderr, _("expression --and expression\n"
" Logical AND"));
_cupsLangPuts(stderr, _("expression --or expression\n"
" Logical OR"));
_cupsLangPuts(stderr, _("Substitutions:"));
_cupsLangPuts(stderr, _("{} URI"));
_cupsLangPuts(stderr, _("{service_domain} Domain name"));
_cupsLangPuts(stderr, _("{service_hostname} Fully-qualified domain name"));
_cupsLangPuts(stderr, _("{service_name} Service instance name"));
_cupsLangPuts(stderr, _("{service_port} Port number"));
_cupsLangPuts(stderr, _("{service_regtype} DNS-SD registration type"));
_cupsLangPuts(stderr, _("{service_scheme} URI scheme"));
_cupsLangPuts(stderr, _("{service_uri} URI"));
_cupsLangPuts(stderr, _("{txt_*} Value of TXT record key"));
_cupsLangPuts(stderr, _("Environment Variables:"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_DOMAIN Domain name"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_HOSTNAME\n"
" Fully-qualified domain name"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_NAME Service instance name"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_PORT Port number"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_SCHEME URI scheme"));
_cupsLangPuts(stderr, _("IPPFIND_SERVICE_URI URI"));
_cupsLangPuts(stderr, _("IPPFIND_TXT_* Value of TXT record key"));
exit(IPPFIND_EXIT_TRUE);
}
/*
* 'show_version()' - Show program version.
*/
static void
show_version(void)
{
_cupsLangPuts(stderr, CUPS_SVERSION);
exit(IPPFIND_EXIT_TRUE);
}