tools/ippfind.c
Rama master · réplica de lectura del repositorio original.
//
// 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 © 2020-2025 by OpenPrinting.
// Copyright © 2020 by the IEEE-ISTO Printer Working Group
// Copyright © 2008-2018 by Apple Inc.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more
// information.
//
#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>
#include <cups/dnssd.h>
#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)
bool 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
size_t num_args; // Number of arguments for exec
char **args; // Arguments for exec
} ippfind_expr_t;
typedef struct ippfind_srvs_s // Services array
{
cups_rwlock_t rwlock; // R/W lock
cups_array_t *services; // Services array
} ippfind_srvs_t;
typedef struct ippfind_srv_s // Service information
{
cups_dnssd_resolve_t *resolve; // Resolve request
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
bool is_local, // Is a local service?
is_processed, // Did we process the service?
is_resolved; // Got the resolve data?
} ippfind_srv_t;
//
// Local globals...
//
static cups_dnssd_t *dnssd; // DNS-SD context
static int address_family = AF_UNSPEC;
// Address family for LIST
static bool bonjour_error = false; // Error browsing/resolving?
static double bonjour_timeout = 1.0; // Timeout in seconds
static int ipp_version = 20; // IPP version for LIST
static double last_update = 0.0; // Last update time
//
// Local functions...
//
static void browse_callback(cups_dnssd_browse_t *browse, void *context, cups_dnssd_flags_t flags, uint32_t if_index, const char *serviceName, const char *regtype, const char *replyDomain);
static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b, void *data);
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(ippfind_srvs_t *services, const char *serviceName, const char *regtype, const char *replyDomain) _CUPS_NONNULL(1,2,3,4);
static int list_service(ippfind_srv_t *service);
static ippfind_expr_t *new_expr(ippfind_op_t op, bool invert, const char *value, const char *regex, char **args);
static void resolve_callback(cups_dnssd_resolve_t *resolve, void *context, cups_dnssd_flags_t flags, uint32_t if_index, const char *fullName, const char *hostTarget, uint16_t port, int num_txt, cups_option_t *txt);
static void set_service_uri(ippfind_srv_t *service);
static void show_usage(void) _CUPS_NORETURN;
static void show_version(void) _CUPS_NORETURN;
#if _WIN32
static char *win32_escape_dup(const char *s);
#endif // _WIN32
//
// '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
ippfind_srvs_t services; // Services 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?
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 = cupsArrayNew3(NULL, NULL, NULL, 0, NULL, NULL);
cupsRWInit(&services.rwlock);
services.services = cupsArrayNew3((cups_array_cb_t)compare_services, NULL, NULL, 0, NULL, 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)
exit(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)
exit(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)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--ls"))
{
if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
NULL)) == NULL)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--print"))
{
if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
NULL)) == NULL)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
have_output = 1;
}
else if (!strcmp(argv[i], "--quiet"))
{
if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
NULL)) == NULL)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
}
else if (!strcmp(argv[i], "--true"))
{
if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i],
NULL)) == NULL)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
break;
case 'l' :
if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL,
NULL)) == NULL)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
break;
case 'p' :
if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL,
NULL)) == NULL)
exit(IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 'q' :
if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL,
NULL)) == NULL)
exit(IPPFIND_EXIT_MEMORY);
have_output = 1;
break;
case 'r' :
if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL,
NULL)) == NULL)
exit(IPPFIND_EXIT_MEMORY);
break;
case 's' :
if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL,
NULL)) == NULL)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(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)
exit(IPPFIND_EXIT_MEMORY);
if (current)
{
while (current->parent)
current = current->parent;
current->next = temp;
temp->prev = current;
}
else
expressions = temp;
}
if (cupsArrayGetCount(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 *)cupsArrayGetFirst(searches); search; search = (const char *)cupsArrayGetNext(searches))
printf(" %s\n", search);
}
// Start up browsing/resolving...
if ((dnssd = cupsDNSSDNew(NULL, NULL)) == NULL)
exit(IPPFIND_EXIT_BONJOUR);
for (search = (const char *)cupsArrayGetFirst(searches); search; search = (const char *)cupsArrayGetNext(searches))
{
char buf[1024], // Full name string
*name = NULL, // Service instance name
*regtype, // Registration type
*domain; // Domain, if any
cupsCopyString(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);
if ((service->resolve = cupsDNSSDResolveNew(dnssd, CUPS_DNSSD_IF_INDEX_ANY, name, regtype, domain, resolve_callback, service)) == NULL)
exit(IPPFIND_EXIT_BONJOUR);
}
else
{
// Browse for services of the given type...
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "Browsing for regtype=\"%s\", domain=\"%s\"\n", regtype, domain);
if (!cupsDNSSDBrowseNew(dnssd, CUPS_DNSSD_IF_INDEX_ANY, regtype, domain, browse_callback, &services))
exit(IPPFIND_EXIT_BONJOUR);
}
}
// Process browse/resolve requests...
if (bonjour_timeout > 1.0)
endtime = cupsGetClock() + bonjour_timeout;
else
endtime = cupsGetClock() + 300.0;
while (cupsGetClock() < endtime)
{
// Process any services that we have found...
int j, // Looping var
count, // Number of services
active = 0, // Number of active resolves
resolved = 0, // Number of resolved services
processed = 0; // Number of processed services
cupsRWLockRead(&services.rwlock);
for (j = 0, count = cupsArrayGetCount(services.services); j < count; j ++)
{
service = (ippfind_srv_t *)cupsArrayGetElement(services.services, j);
if (service->is_processed)
processed ++;
if (service->is_resolved)
resolved ++;
if (!service->resolve && !service->is_resolved)
{
// Found a service, now resolve it (but limit to 50 active resolves...)
if (active < 50)
{
if ((service->resolve = cupsDNSSDResolveNew(dnssd, CUPS_DNSSD_IF_INDEX_ANY, service->name, service->regtype, service->domain, resolve_callback, service)) == NULL)
exit(IPPFIND_EXIT_BONJOUR);
active ++;
}
}
else if (service->is_resolved && !service->is_processed)
{
// Resolved, not process this service against the expressions...
cupsDNSSDResolveDelete(service->resolve);
service->resolve = NULL;
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "EVAL %s\n", service->uri);
if (eval_expr(service, expressions))
status = IPPFIND_EXIT_TRUE;
service->is_processed = true;
}
else if (service->resolve)
active ++;
}
cupsRWUnlock(&services.rwlock);
// If we have processed all services we have discovered, then we are done.
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "STATUS processed=%u, resolved=%u, count=%u\n", (unsigned)processed, (unsigned)resolved, (unsigned)count);
if (bonjour_timeout <= 1.0 && cupsGetClock() >= 2.5 && (processed == cupsArrayGetCount(services.services) || (cupsGetClock() - last_update) >= 1.0))
break;
// Give the browsers/resolvers some time...
usleep(250000);
}
if (bonjour_error)
exit(IPPFIND_EXIT_BONJOUR);
else
exit(status);
}
//
// 'browse_callback()' - Browse devices.
//
static void
browse_callback(
cups_dnssd_browse_t *browse, // I - Browse request
void *context, // I - Services array
cups_dnssd_flags_t flags, // I - Flags
uint32_t if_index, // I - Interface
const char *serviceName, // I - Name of service/device
const char *regtype, // I - Type of service
const char *replyDomain) // I - Service domain
{
ippfind_srv_t *service; // Service
last_update = cupsGetClock();
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "B flags=0x%04X, if_index=%u, serviceName=\"%s\", regtype=\"%s\", replyDomain=\"%s\"\n", flags, if_index, serviceName, regtype, replyDomain);
(void)browse;
// Only process "add" data...
if ((flags & CUPS_DNSSD_FLAGS_ERROR) || !(flags & CUPS_DNSSD_FLAGS_ADD))
return;
// Get the device...
service = get_service((ippfind_srvs_t *)context, serviceName, regtype, replyDomain);
if (if_index == CUPS_DNSSD_IF_INDEX_LOCAL)
service->is_local = 1;
}
//
// '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
void *data) // I - Callback data (unused)
{
(void)data;
return (_cups_strcasecmp(a->name, b->name));
}
//
// '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
int 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"))
cupsCopyString(tptr, service->uri, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_domain"))
cupsCopyString(tptr, service->domain, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_hostname"))
cupsCopyString(tptr, service->host, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_name"))
cupsCopyString(tptr, service->name, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_path"))
cupsCopyString(tptr, service->resource, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_port"))
cupsCopyString(tptr, port + 21, sizeof(temp) - (size_t)(tptr - temp));
else if (!strcmp(keyword, "service_scheme"))
cupsCopyString(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)
cupsCopyString(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';
#if _WIN32
myargv[i] = win32_escape_dup(temp);
}
else
myargv[i] = win32_escape_dup(args[i]);
#else
myargv[i] = strdup(temp);
}
else
myargv[i] = strdup(args[i]);
#endif // _WIN32
}
#if _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))
{
cupsCopyString(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(ippfind_srvs_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
{
int i, // Looping var
count; // Number of services
ippfind_srv_t key, // Search key
*service; // Service
char fullName[1024]; // Full name for query
// See if this is a new device...
key.name = (char *)serviceName;
key.regtype = (char *)regtype;
cupsRWLockRead(&services->rwlock);
for (i = 0, count = cupsArrayGetCount(services->services); i < count; i ++)
{
service = (ippfind_srv_t *)cupsArrayGetElement(services->services, i);
if (!_cups_strcasecmp(service->name, key.name) && !strcmp(service->regtype, key.regtype))
{
cupsRWUnlock(&services->rwlock);
return (service);
}
else if (_cups_strcasecmp(service->name, key.name) > 0)
{
break;
}
}
cupsRWUnlock(&services->rwlock);
// Yes, add the service...
if ((service = calloc(sizeof(ippfind_srv_t), 1)) == NULL)
return (NULL);
service->name = strdup(serviceName);
service->domain = strdup(replyDomain);
service->regtype = strdup(regtype);
cupsRWLockWrite(&services->rwlock);
cupsArrayAdd(services->services, service);
cupsRWUnlock(&services->rwlock);
// Set the "full name" of this service, which is used for queries and resolves...
cupsDNSSDAssembleFullName(fullName, sizeof(fullName), serviceName, regtype, replyDomain);
service->fullName = strdup(fullName);
return (service);
}
//
// '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
int version; // IPP version
bool 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, cupsGetUser());
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", sizeof(rattrs) / sizeof(rattrs[0]), NULL, rattrs);
response = cupsDoRequest(http, request, service->resource);
if (cupsGetError() == IPP_STATUS_ERROR_BAD_REQUEST && version > 11)
version = 11;
}
while (cupsGetError() > IPP_STATUS_OK_EVENTS_COMPLETE && version > 11);
// Show results...
if (cupsGetError() > 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)
{
cupsCopyString(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++ = ',';
cupsCopyString(ptr, ippGetString(attr, i, NULL), (size_t)(end - ptr + 1));
}
}
else
{
cupsCopyString(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 (!httpWriteRequest(http, "GET", 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 (!httpAddrConnect2(addrlist, &sock, 30000, NULL))
{
_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
bool 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)
{
size_t 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;
if ((temp->args = malloc(num_args * sizeof(char *))) == NULL)
{
regfree(&temp->re);
free(temp);
return (NULL);
}
memcpy(temp->args, args, (size_t)num_args * sizeof(char *));
}
return (temp);
}
//
// 'resolve_callback()' - Process resolve data.
//
static void
resolve_callback(
cups_dnssd_resolve_t *resolve, // I - Resolver
void *context, // I - Service
cups_dnssd_flags_t flags, // I - Flags
uint32_t if_index, // I - Interface index
const char *fullName, // I - Full service name
const char *hostTarget, // I - Hostname
uint16_t port, // I - Port number
int num_txt, // I - Number of TXT key/value pairs
cups_option_t *txt) // I - TXT key/value pairs
{
ippfind_srv_t *service = (ippfind_srv_t *)context;
// Service
int i; // Looping var
char *value; // Pointer into value
last_update = cupsGetClock();
if (getenv("IPPFIND_DEBUG"))
fprintf(stderr, "R flags=0x%04X, if_index=%u, fullName=\"%s\", hostTarget=\"%s\", port=%u, num_txt=%d, txt=%p\n", flags, if_index, fullName, hostTarget, port, num_txt, (void *)txt);
(void)resolve;
(void)if_index;
(void)fullName;
// Only process "add" data...
if (flags & CUPS_DNSSD_FLAGS_ERROR)
{
bonjour_error = true;
return;
}
service->is_resolved = true;
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 (i = 0; i < num_txt; i ++)
service->num_txt = cupsAddOption(txt[i].name, txt[i].value, service->num_txt, &service->txt);
set_service_uri(service);
}
//
// '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, _("-N name Match service name to literal name value"));
_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, _("--literal-name name Match service name to literal name value"));
_cupsLangPuts(stderr, _("--local True if service is local"));
_cupsLangPuts(stderr, _("--ls List attributes"));
_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);
}
//
// 'win32_escape_dup()' - Escape and duplicate a string.
//
// This function puts the string in double quotes, escaping characters in the
// string as needed using Windows' insane command-line argument parsing rules.
//
#if _WIN32
static char * // O - Duplicated string
win32_escape_dup(const char *s) // I - Original string
{
char *d, // Output string
*dptr; // Pointer into output string
size_t dlen; // Length of output string
const char *sptr; // Pointer into original string
// Figure out the length of the escaped string...
for (dlen = 2, sptr = s; *sptr; sptr ++)
{
if (*sptr == '\\')
{
if (sptr[1] == '\"' || sptr[1] == '\\')
{
// \" and \\ need to be converted to \\\" or \\\\...
dlen += 4;
sptr ++;
}
else
{
// A lone \ can stand on its own...
dlen ++;
}
}
else if (*sptr == '\"')
{
// Need to replace " with ""...
dlen += 2;
}
else
{
// Not a special character so it is fine on its own...
dlen ++;
}
}
// Allocate memory (plus nul)...
if ((d = calloc(1, dlen + 1)) == NULL)
return (NULL);
// Copy and escape...
*d = '\"';
for (dptr = d + 1, sptr = s; *sptr; sptr ++)
{
if (*sptr == '\\')
{
if (sptr[1] == '\"' || sptr[1] == '\\')
{
// \" and \\ need to be converted to \\\" or \\\\...
sptr ++;
*dptr++ = '\\';
*dptr++ = '\\';
*dptr++ = '\\';
*dptr++ = *sptr;
}
else
{
// A lone \ can stand on its own...
*dptr++ = '\\';
}
}
else if (*sptr == '\"')
{
// Need to replace " with ""...
*dptr++ = '\"';
*dptr++ = '\"';
}
else
{
// Not a special character so it is fine on its own...
*dptr++ = *sptr;
}
}
*dptr++ = '\"';
*dptr = '\0';
return (d);
}
#endif // _WIN32