You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2848 lines
76 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* 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);
}