mirror of https://gitee.com/openkylin/cups.git
2848 lines
76 KiB
C
2848 lines
76 KiB
C
/*
|
||
* 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);
|
||
}
|