2 * Utility to find IPP printers via Bonjour/DNS-SD and optionally run
3 * commands such as IPP and Bonjour conformance tests. This tool is
4 * inspired by the UNIX "find" command, thus its name.
6 * Copyright © 2021 by OpenPrinting.
7 * Copyright © 2020 by the IEEE-ISTO Printer Working Group
8 * Copyright © 2008-2018 by Apple Inc.
10 * Licensed under Apache License v2.0. See the file "LICENSE" for more
15 * Include necessary headers.
18 #define _CUPS_NO_DEPRECATED
19 #include <cups/cups-private.h>
22 # include <sys/timeb.h>
24 # include <sys/wait.h>
29 #elif defined(HAVE_AVAHI)
30 # include <avahi-client/client.h>
31 # include <avahi-client/lookup.h>
32 # include <avahi-common/simple-watch.h>
33 # include <avahi-common/domain.h>
34 # include <avahi-common/error.h>
35 # include <avahi-common/malloc.h>
36 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
37 #endif /* HAVE_DNSSD */
40 extern char **environ
; /* Process environment variables */
48 typedef enum ippfind_exit_e
/* Exit codes */
50 IPPFIND_EXIT_TRUE
= 0, /* OK and result is true */
51 IPPFIND_EXIT_FALSE
, /* OK but result is false*/
52 IPPFIND_EXIT_BONJOUR
, /* Browse/resolve failure */
53 IPPFIND_EXIT_SYNTAX
, /* Bad option or syntax error */
54 IPPFIND_EXIT_MEMORY
/* Out of memory */
57 typedef enum ippfind_op_e
/* Operations for expressions */
59 /* "Evaluation" operations */
60 IPPFIND_OP_NONE
, /* No operation */
61 IPPFIND_OP_AND
, /* Logical AND of all children */
62 IPPFIND_OP_OR
, /* Logical OR of all children */
63 IPPFIND_OP_TRUE
, /* Always true */
64 IPPFIND_OP_FALSE
, /* Always false */
65 IPPFIND_OP_IS_LOCAL
, /* Is a local service */
66 IPPFIND_OP_IS_REMOTE
, /* Is a remote service */
67 IPPFIND_OP_DOMAIN_REGEX
, /* Domain matches regular expression */
68 IPPFIND_OP_NAME_REGEX
, /* Name matches regular expression */
69 IPPFIND_OP_NAME_LITERAL
, /* Name matches literal string */
70 IPPFIND_OP_HOST_REGEX
, /* Hostname matches regular expression */
71 IPPFIND_OP_PORT_RANGE
, /* Port matches range */
72 IPPFIND_OP_PATH_REGEX
, /* Path matches regular expression */
73 IPPFIND_OP_TXT_EXISTS
, /* TXT record key exists */
74 IPPFIND_OP_TXT_REGEX
, /* TXT record key matches regular expression */
75 IPPFIND_OP_URI_REGEX
, /* URI matches regular expression */
77 /* "Output" operations */
78 IPPFIND_OP_EXEC
, /* Execute when true */
79 IPPFIND_OP_LIST
, /* List when true */
80 IPPFIND_OP_PRINT_NAME
, /* Print URI when true */
81 IPPFIND_OP_PRINT_URI
, /* Print name when true */
82 IPPFIND_OP_QUIET
/* No output when true */
85 typedef struct ippfind_expr_s
/* Expression */
88 *prev
, /* Previous expression */
89 *next
, /* Next expression */
90 *parent
, /* Parent expressions */
91 *child
; /* Child expressions */
92 ippfind_op_t op
; /* Operation code (see above) */
93 int invert
; /* Invert the result */
94 char *name
; /* TXT record key or literal name */
95 regex_t re
; /* Regular expression for matching */
96 int range
[2]; /* Port number range */
97 int num_args
; /* Number of arguments for exec */
98 char **args
; /* Arguments for exec */
101 typedef struct ippfind_srv_s
/* Service information */
104 DNSServiceRef ref
; /* Service reference for query */
105 #elif defined(HAVE_AVAHI)
106 AvahiServiceResolver
*ref
; /* Resolver */
107 #endif /* HAVE_DNSSD */
108 char *name
, /* Service name */
109 *domain
, /* Domain name */
110 *regtype
, /* Registration type */
111 *fullName
, /* Full name */
112 *host
, /* Hostname */
113 *resource
, /* Resource path */
115 int num_txt
; /* Number of TXT record keys */
116 cups_option_t
*txt
; /* TXT record keys */
117 int port
, /* Port number */
118 is_local
, /* Is a local service? */
119 is_processed
, /* Did we process the service? */
120 is_resolved
; /* Got the resolve data? */
129 static DNSServiceRef dnssd_ref
; /* Master service reference */
130 #elif defined(HAVE_AVAHI)
131 static AvahiClient
*avahi_client
= NULL
;/* Client information */
132 static int avahi_got_data
= 0; /* Got data from poll? */
133 static AvahiSimplePoll
*avahi_poll
= NULL
;
134 /* Poll information */
135 #endif /* HAVE_DNSSD */
137 static int address_family
= AF_UNSPEC
;
138 /* Address family for LIST */
139 static int bonjour_error
= 0; /* Error browsing/resolving? */
140 static double bonjour_timeout
= 1.0; /* Timeout in seconds */
141 static int ipp_version
= 20; /* IPP version for LIST */
149 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);
150 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);
151 #elif defined(HAVE_AVAHI)
152 static void browse_callback(AvahiServiceBrowser
*browser
,
153 AvahiIfIndex interface
,
154 AvahiProtocol protocol
,
155 AvahiBrowserEvent event
,
156 const char *serviceName
,
158 const char *replyDomain
,
159 AvahiLookupResultFlags flags
,
161 static void client_callback(AvahiClient
*client
,
162 AvahiClientState state
,
164 #endif /* HAVE_DNSSD */
166 static int compare_services(ippfind_srv_t
*a
, ippfind_srv_t
*b
);
167 static const char *dnssd_error_string(int error
);
168 static int eval_expr(ippfind_srv_t
*service
,
169 ippfind_expr_t
*expressions
);
170 static int exec_program(ippfind_srv_t
*service
, int num_args
,
172 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);
173 static double get_time(void);
174 static int list_service(ippfind_srv_t
*service
);
175 static ippfind_expr_t
*new_expr(ippfind_op_t op
, int invert
,
176 const char *value
, const char *regex
,
179 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);
180 #elif defined(HAVE_AVAHI)
181 static int poll_callback(struct pollfd
*pollfds
,
182 unsigned int num_pollfds
, int timeout
,
184 static void resolve_callback(AvahiServiceResolver
*res
,
185 AvahiIfIndex interface
,
186 AvahiProtocol protocol
,
187 AvahiResolverEvent event
,
188 const char *serviceName
,
190 const char *replyDomain
,
191 const char *host_name
,
192 const AvahiAddress
*address
,
194 AvahiStringList
*txt
,
195 AvahiLookupResultFlags flags
,
197 #endif /* HAVE_DNSSD */
198 static void set_service_uri(ippfind_srv_t
*service
);
199 static void show_usage(void) _CUPS_NORETURN
;
200 static void show_version(void) _CUPS_NORETURN
;
204 * 'main()' - Browse for printers.
207 int /* O - Exit status */
208 main(int argc
, /* I - Number of command-line args */
209 char *argv
[]) /* I - Command-line arguments */
211 int i
, /* Looping var */
212 have_output
= 0,/* Have output expression */
213 status
= IPPFIND_EXIT_FALSE
;
215 const char *opt
, /* Option character */
216 *search
; /* Current browse/resolve string */
217 cups_array_t
*searches
; /* Things to browse/resolve */
218 cups_array_t
*services
; /* Service array */
219 ippfind_srv_t
*service
; /* Current service */
220 ippfind_expr_t
*expressions
= NULL
,
221 /* Expression tree */
222 *temp
= NULL
, /* New expression */
223 *parent
= NULL
, /* Parent expression */
224 *current
= NULL
,/* Current expression */
225 *parens
[100]; /* Markers for parenthesis */
226 int num_parens
= 0; /* Number of parenthesis */
227 ippfind_op_t logic
= IPPFIND_OP_AND
;
228 /* Logic for next expression */
229 int invert
= 0; /* Invert expression? */
230 int err
; /* DNS-SD error */
232 fd_set sinput
; /* Input set for select() */
233 struct timeval stimeout
; /* Timeout for select() */
234 #endif /* HAVE_DNSSD */
235 double endtime
; /* End time */
236 static const char * const ops
[] = /* Node operation names */
263 * Initialize the locale...
266 _cupsSetLocale(argv
);
269 * Create arrays to track services and things we want to browse/resolve...
272 searches
= cupsArrayNew(NULL
, NULL
);
273 services
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
276 * Parse command-line...
279 if (getenv("IPPFIND_DEBUG"))
280 for (i
= 1; i
< argc
; i
++)
281 fprintf(stderr
, "argv[%d]=\"%s\"\n", i
, argv
[i
]);
283 for (i
= 1; i
< argc
; i
++)
285 if (argv
[i
][0] == '-')
287 if (argv
[i
][1] == '-')
290 * Parse --option options...
293 if (!strcmp(argv
[i
], "--and"))
295 if (logic
== IPPFIND_OP_OR
)
297 _cupsLangPuts(stderr
, _("ippfind: Cannot use --and after --or."));
303 _cupsLangPuts(stderr
,
304 _("ippfind: Missing expression before \"--and\"."));
310 else if (!strcmp(argv
[i
], "--domain"))
315 _cupsLangPrintf(stderr
,
316 _("ippfind: Missing regular expression after %s."),
321 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
, argv
[i
],
323 return (IPPFIND_EXIT_MEMORY
);
325 else if (!strcmp(argv
[i
], "--exec"))
330 _cupsLangPrintf(stderr
, _("ippfind: Expected program after %s."),
335 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
337 return (IPPFIND_EXIT_MEMORY
);
340 if (!strcmp(argv
[i
], ";"))
347 _cupsLangPrintf(stderr
, _("ippfind: Expected semi-colon after %s."),
354 else if (!strcmp(argv
[i
], "--false"))
356 if ((temp
= new_expr(IPPFIND_OP_FALSE
, invert
, NULL
, NULL
,
358 return (IPPFIND_EXIT_MEMORY
);
360 else if (!strcmp(argv
[i
], "--help"))
364 else if (!strcmp(argv
[i
], "--host"))
369 _cupsLangPrintf(stderr
,
370 _("ippfind: Missing regular expression after %s."),
375 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
, argv
[i
],
377 return (IPPFIND_EXIT_MEMORY
);
379 else if (!strcmp(argv
[i
], "--ls"))
381 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
383 return (IPPFIND_EXIT_MEMORY
);
387 else if (!strcmp(argv
[i
], "--local"))
389 if ((temp
= new_expr(IPPFIND_OP_IS_LOCAL
, invert
, NULL
, NULL
,
391 return (IPPFIND_EXIT_MEMORY
);
393 else if (!strcmp(argv
[i
], "--literal-name"))
398 _cupsLangPrintf(stderr
, _("ippfind: Missing name after %s."), "--literal-name");
402 if ((temp
= new_expr(IPPFIND_OP_NAME_LITERAL
, invert
, argv
[i
], NULL
, NULL
)) == NULL
)
403 return (IPPFIND_EXIT_MEMORY
);
405 else if (!strcmp(argv
[i
], "--name"))
410 _cupsLangPrintf(stderr
,
411 _("ippfind: Missing regular expression after %s."),
416 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
, argv
[i
],
418 return (IPPFIND_EXIT_MEMORY
);
420 else if (!strcmp(argv
[i
], "--not"))
424 else if (!strcmp(argv
[i
], "--or"))
428 _cupsLangPuts(stderr
,
429 _("ippfind: Missing expression before \"--or\"."));
433 logic
= IPPFIND_OP_OR
;
435 if (parent
&& parent
->op
== IPPFIND_OP_OR
)
438 * Already setup to do "foo --or bar --or baz"...
443 else if (!current
->prev
&& parent
)
446 * Change parent node into an OR node...
449 parent
->op
= IPPFIND_OP_OR
;
452 else if (!current
->prev
)
455 * Need to group "current" in a new OR node...
458 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
460 return (IPPFIND_EXIT_MEMORY
);
462 temp
->parent
= parent
;
463 temp
->child
= current
;
464 current
->parent
= temp
;
467 parent
->child
= temp
;
477 * Need to group previous expressions in an AND node, and then
478 * put that in an OR node...
481 if ((temp
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
483 return (IPPFIND_EXIT_MEMORY
);
485 while (current
->prev
)
487 current
->parent
= temp
;
488 current
= current
->prev
;
491 current
->parent
= temp
;
492 temp
->child
= current
;
495 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
497 return (IPPFIND_EXIT_MEMORY
);
499 temp
->parent
= parent
;
500 current
->parent
= temp
;
503 parent
->child
= temp
;
511 else if (!strcmp(argv
[i
], "--path"))
516 _cupsLangPrintf(stderr
,
517 _("ippfind: Missing regular expression after %s."),
522 if ((temp
= new_expr(IPPFIND_OP_PATH_REGEX
, invert
, NULL
, argv
[i
],
524 return (IPPFIND_EXIT_MEMORY
);
526 else if (!strcmp(argv
[i
], "--port"))
531 _cupsLangPrintf(stderr
,
532 _("ippfind: Expected port range after %s."),
537 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
], NULL
,
539 return (IPPFIND_EXIT_MEMORY
);
541 else if (!strcmp(argv
[i
], "--print"))
543 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
545 return (IPPFIND_EXIT_MEMORY
);
549 else if (!strcmp(argv
[i
], "--print-name"))
551 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
553 return (IPPFIND_EXIT_MEMORY
);
557 else if (!strcmp(argv
[i
], "--quiet"))
559 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
561 return (IPPFIND_EXIT_MEMORY
);
565 else if (!strcmp(argv
[i
], "--remote"))
567 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
569 return (IPPFIND_EXIT_MEMORY
);
571 else if (!strcmp(argv
[i
], "--true"))
573 if ((temp
= new_expr(IPPFIND_OP_TRUE
, invert
, NULL
, argv
[i
],
575 return (IPPFIND_EXIT_MEMORY
);
577 else if (!strcmp(argv
[i
], "--txt"))
582 _cupsLangPrintf(stderr
, _("ippfind: Expected key name after %s."),
587 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
], NULL
,
589 return (IPPFIND_EXIT_MEMORY
);
591 else if (!strncmp(argv
[i
], "--txt-", 6))
593 const char *key
= argv
[i
] + 6;/* TXT key */
598 _cupsLangPrintf(stderr
,
599 _("ippfind: Missing regular expression after %s."),
604 if ((temp
= new_expr(IPPFIND_OP_TXT_REGEX
, invert
, key
, argv
[i
],
606 return (IPPFIND_EXIT_MEMORY
);
608 else if (!strcmp(argv
[i
], "--uri"))
613 _cupsLangPrintf(stderr
,
614 _("ippfind: Missing regular expression after %s."),
619 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
, argv
[i
],
621 return (IPPFIND_EXIT_MEMORY
);
623 else if (!strcmp(argv
[i
], "--version"))
629 _cupsLangPrintf(stderr
, _("%s: Unknown option \"%s\"."),
637 * Add new expression...
640 if (logic
== IPPFIND_OP_AND
&&
641 current
&& current
->prev
&&
642 parent
&& parent
->op
!= IPPFIND_OP_AND
)
645 * Need to re-group "current" in a new AND node...
648 ippfind_expr_t
*tempand
; /* Temporary AND node */
650 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
652 return (IPPFIND_EXIT_MEMORY
);
655 * Replace "current" with new AND node at the end of this list...
658 current
->prev
->next
= tempand
;
659 tempand
->prev
= current
->prev
;
660 tempand
->parent
= parent
;
663 * Add "current to the new AND node...
666 tempand
->child
= current
;
667 current
->parent
= tempand
;
668 current
->prev
= NULL
;
673 * Add the new node at current level...
676 temp
->parent
= parent
;
677 temp
->prev
= current
;
680 current
->next
= temp
;
682 parent
->child
= temp
;
688 logic
= IPPFIND_OP_AND
;
698 for (opt
= argv
[i
] + 1; *opt
; opt
++)
703 address_family
= AF_INET
;
707 address_family
= AF_INET6
;
710 case 'N' : /* Literal name */
714 _cupsLangPrintf(stderr
, _("ippfind: Missing name after %s."), "-N");
718 if ((temp
= new_expr(IPPFIND_OP_NAME_LITERAL
, invert
, argv
[i
], NULL
, NULL
)) == NULL
)
719 return (IPPFIND_EXIT_MEMORY
);
726 _cupsLangPrintf(stderr
,
727 _("ippfind: Expected port range after %s."),
732 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
],
733 NULL
, NULL
)) == NULL
)
734 return (IPPFIND_EXIT_MEMORY
);
741 _cupsLangPrintf(stderr
,
742 _("%s: Missing timeout for \"-T\"."),
747 bonjour_timeout
= atof(argv
[i
]);
754 _cupsLangPrintf(stderr
,
755 _("%s: Missing version for \"-V\"."),
760 if (!strcmp(argv
[i
], "1.1"))
762 else if (!strcmp(argv
[i
], "2.0"))
764 else if (!strcmp(argv
[i
], "2.1"))
766 else if (!strcmp(argv
[i
], "2.2"))
770 _cupsLangPrintf(stderr
, _("%s: Bad version %s for \"-V\"."),
780 _cupsLangPrintf(stderr
,
781 _("ippfind: Missing regular expression after "
786 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
,
787 argv
[i
], NULL
)) == NULL
)
788 return (IPPFIND_EXIT_MEMORY
);
795 _cupsLangPrintf(stderr
,
796 _("ippfind: Missing regular expression after "
801 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
,
802 argv
[i
], NULL
)) == NULL
)
803 return (IPPFIND_EXIT_MEMORY
);
807 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
809 return (IPPFIND_EXIT_MEMORY
);
818 _cupsLangPrintf(stderr
,
819 _("ippfind: Missing regular expression after "
824 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
,
825 argv
[i
], NULL
)) == NULL
)
826 return (IPPFIND_EXIT_MEMORY
);
830 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
832 return (IPPFIND_EXIT_MEMORY
);
838 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
840 return (IPPFIND_EXIT_MEMORY
);
846 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
848 return (IPPFIND_EXIT_MEMORY
);
852 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
854 return (IPPFIND_EXIT_MEMORY
);
863 _cupsLangPrintf(stderr
,
864 _("ippfind: Missing key name after %s."),
869 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
],
870 NULL
, NULL
)) == NULL
)
871 return (IPPFIND_EXIT_MEMORY
);
878 _cupsLangPrintf(stderr
,
879 _("ippfind: Missing regular expression after "
884 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
,
885 argv
[i
], NULL
)) == NULL
)
886 return (IPPFIND_EXIT_MEMORY
);
893 _cupsLangPrintf(stderr
,
894 _("ippfind: Missing program after %s."),
899 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
901 return (IPPFIND_EXIT_MEMORY
);
904 if (!strcmp(argv
[i
], ";"))
911 _cupsLangPrintf(stderr
,
912 _("ippfind: Missing semi-colon after %s."),
921 _cupsLangPrintf(stderr
, _("%s: Unknown option \"-%c\"."),
929 * Add new expression...
932 if (logic
== IPPFIND_OP_AND
&&
933 current
&& current
->prev
&&
934 parent
&& parent
->op
!= IPPFIND_OP_AND
)
937 * Need to re-group "current" in a new AND node...
940 ippfind_expr_t
*tempand
; /* Temporary AND node */
942 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
944 return (IPPFIND_EXIT_MEMORY
);
947 * Replace "current" with new AND node at the end of this list...
950 current
->prev
->next
= tempand
;
951 tempand
->prev
= current
->prev
;
952 tempand
->parent
= parent
;
955 * Add "current to the new AND node...
958 tempand
->child
= current
;
959 current
->parent
= tempand
;
960 current
->prev
= NULL
;
965 * Add the new node at current level...
968 temp
->parent
= parent
;
969 temp
->prev
= current
;
972 current
->next
= temp
;
974 parent
->child
= temp
;
980 logic
= IPPFIND_OP_AND
;
986 else if (!strcmp(argv
[i
], "("))
988 if (num_parens
>= 100)
990 _cupsLangPuts(stderr
, _("ippfind: Too many parenthesis."));
994 if ((temp
= new_expr(IPPFIND_OP_AND
, invert
, NULL
, NULL
, NULL
)) == NULL
)
995 return (IPPFIND_EXIT_MEMORY
);
997 parens
[num_parens
++] = temp
;
1001 temp
->parent
= current
->parent
;
1002 current
->next
= temp
;
1003 temp
->prev
= current
;
1011 logic
= IPPFIND_OP_AND
;
1013 else if (!strcmp(argv
[i
], ")"))
1015 if (num_parens
<= 0)
1017 _cupsLangPuts(stderr
, _("ippfind: Missing open parenthesis."));
1021 current
= parens
[--num_parens
];
1022 parent
= current
->parent
;
1024 logic
= IPPFIND_OP_AND
;
1026 else if (!strcmp(argv
[i
], "!"))
1033 * _regtype._tcp[,subtype][.domain]
1037 * service-name[._regtype._tcp[.domain]]
1040 cupsArrayAdd(searches
, argv
[i
]);
1046 _cupsLangPuts(stderr
, _("ippfind: Missing close parenthesis."));
1053 * Add an implicit --print-uri to the end...
1056 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, 0, NULL
, NULL
, NULL
)) == NULL
)
1057 return (IPPFIND_EXIT_MEMORY
);
1061 while (current
->parent
)
1062 current
= current
->parent
;
1064 current
->next
= temp
;
1065 temp
->prev
= current
;
1071 if (cupsArrayCount(searches
) == 0)
1074 * Add an implicit browse for IPP printers ("_ipp._tcp")...
1077 cupsArrayAdd(searches
, "_ipp._tcp");
1080 if (getenv("IPPFIND_DEBUG"))
1082 int indent
= 4; /* Indentation */
1084 puts("Expression tree:");
1085 current
= expressions
;
1089 * Print the current node...
1092 printf("%*s%s%s\n", indent
, "", current
->invert
? "!" : "",
1096 * Advance to the next node...
1101 current
= current
->child
;
1104 else if (current
->next
)
1105 current
= current
->next
;
1106 else if (current
->parent
)
1108 while (current
->parent
)
1111 current
= current
->parent
;
1116 current
= current
->next
;
1122 puts("\nSearch items:");
1123 for (search
= (const char *)cupsArrayFirst(searches
);
1125 search
= (const char *)cupsArrayNext(searches
))
1126 printf(" %s\n", search
);
1130 * Start up browsing/resolving...
1134 if ((err
= DNSServiceCreateConnection(&dnssd_ref
)) != kDNSServiceErr_NoError
)
1136 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1137 dnssd_error_string(err
));
1138 return (IPPFIND_EXIT_BONJOUR
);
1141 #elif defined(HAVE_AVAHI)
1142 if ((avahi_poll
= avahi_simple_poll_new()) == NULL
)
1144 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1146 return (IPPFIND_EXIT_BONJOUR
);
1149 avahi_simple_poll_set_func(avahi_poll
, poll_callback
, NULL
);
1151 avahi_client
= avahi_client_new(avahi_simple_poll_get(avahi_poll
),
1152 0, client_callback
, avahi_poll
, &err
);
1155 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1156 dnssd_error_string(err
));
1157 return (IPPFIND_EXIT_BONJOUR
);
1159 #endif /* HAVE_DNSSD */
1161 for (search
= (const char *)cupsArrayFirst(searches
);
1163 search
= (const char *)cupsArrayNext(searches
))
1165 char buf
[1024], /* Full name string */
1166 *name
= NULL
, /* Service instance name */
1167 *regtype
, /* Registration type */
1168 *domain
; /* Domain, if any */
1170 strlcpy(buf
, search
, sizeof(buf
));
1172 if (!strncmp(buf
, "_http._", 7) || !strncmp(buf
, "_https._", 8) || !strncmp(buf
, "_ipp._", 6) || !strncmp(buf
, "_ipps._", 7))
1176 else if ((regtype
= strstr(buf
, "._")) != NULL
)
1178 if (strcmp(regtype
, "._tcp"))
1181 * "something._protocol._tcp" -> search for something with the given
1191 * "_protocol._tcp" -> search for everything with the given protocol...
1201 * "something" -> search for something with IPP protocol...
1205 regtype
= "_ipp._tcp";
1208 for (domain
= regtype
; *domain
; domain
++)
1210 if (*domain
== '.' && domain
[1] != '_')
1223 * Resolve the given service instance name, regtype, and domain...
1229 service
= get_service(services
, name
, regtype
, domain
);
1231 if (getenv("IPPFIND_DEBUG"))
1232 fprintf(stderr
, "Resolving name=\"%s\", regtype=\"%s\", domain=\"%s\"\n", name
, regtype
, domain
);
1235 service
->ref
= dnssd_ref
;
1236 err
= DNSServiceResolve(&(service
->ref
),
1237 kDNSServiceFlagsShareConnection
, 0, name
,
1238 regtype
, domain
, resolve_callback
,
1241 #elif defined(HAVE_AVAHI)
1242 service
->ref
= avahi_service_resolver_new(avahi_client
, AVAHI_IF_UNSPEC
,
1243 AVAHI_PROTO_UNSPEC
, name
,
1245 AVAHI_PROTO_UNSPEC
, 0,
1246 resolve_callback
, service
);
1250 err
= avahi_client_errno(avahi_client
);
1251 #endif /* HAVE_DNSSD */
1256 * Browse for services of the given type...
1259 if (getenv("IPPFIND_DEBUG"))
1260 fprintf(stderr
, "Browsing for regtype=\"%s\", domain=\"%s\"\n", regtype
, domain
);
1263 DNSServiceRef ref
; /* Browse reference */
1266 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
, 0, regtype
,
1267 domain
, browse_callback
, services
);
1272 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
,
1273 kDNSServiceInterfaceIndexLocalOnly
, regtype
,
1274 domain
, browse_local_callback
, services
);
1277 #elif defined(HAVE_AVAHI)
1278 char *subtype
, /* Sub-type, if any */
1279 subtype_buf
[256]; /* Sub-type buffer */
1281 if ((subtype
= strstr(regtype
, ",_")) != NULL
)
1284 snprintf(subtype_buf
, sizeof(subtype_buf
), "%s._sub.%s", subtype
, regtype
);
1285 regtype
= subtype_buf
;
1288 if (avahi_service_browser_new(avahi_client
, AVAHI_IF_UNSPEC
,
1289 AVAHI_PROTO_UNSPEC
, regtype
, domain
, 0,
1290 browse_callback
, services
))
1293 err
= avahi_client_errno(avahi_client
);
1294 #endif /* HAVE_DNSSD */
1299 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
1300 dnssd_error_string(err
));
1302 return (IPPFIND_EXIT_BONJOUR
);
1307 * Process browse/resolve requests...
1310 if (bonjour_timeout
> 1.0)
1311 endtime
= get_time() + bonjour_timeout
;
1313 endtime
= get_time() + 300.0;
1315 while (get_time() < endtime
)
1317 int process
= 0; /* Process services? */
1320 int fd
= DNSServiceRefSockFD(dnssd_ref
);
1321 /* File descriptor for DNS-SD */
1324 FD_SET(fd
, &sinput
);
1326 stimeout
.tv_sec
= 0;
1327 stimeout
.tv_usec
= 500000;
1329 if (select(fd
+ 1, &sinput
, NULL
, NULL
, &stimeout
) < 0)
1332 if (FD_ISSET(fd
, &sinput
))
1335 * Process responses...
1338 DNSServiceProcessResult(dnssd_ref
);
1343 * Time to process services...
1349 #elif defined(HAVE_AVAHI)
1352 if (avahi_simple_poll_iterate(avahi_poll
, 500) > 0)
1355 * We've been told to exit the loop. Perhaps the connection to
1359 return (IPPFIND_EXIT_BONJOUR
);
1362 if (!avahi_got_data
)
1365 * Time to process services...
1370 #endif /* HAVE_DNSSD */
1375 * Process any services that we have found...
1378 int active
= 0, /* Number of active resolves */
1379 resolved
= 0, /* Number of resolved services */
1380 processed
= 0; /* Number of processed services */
1382 for (service
= (ippfind_srv_t
*)cupsArrayFirst(services
);
1384 service
= (ippfind_srv_t
*)cupsArrayNext(services
))
1386 if (service
->is_processed
)
1389 if (service
->is_resolved
)
1392 if (!service
->ref
&& !service
->is_resolved
)
1395 * Found a service, now resolve it (but limit to 50 active resolves...)
1401 service
->ref
= dnssd_ref
;
1402 err
= DNSServiceResolve(&(service
->ref
),
1403 kDNSServiceFlagsShareConnection
, 0,
1404 service
->name
, service
->regtype
,
1405 service
->domain
, resolve_callback
,
1408 #elif defined(HAVE_AVAHI)
1409 service
->ref
= avahi_service_resolver_new(avahi_client
,
1415 AVAHI_PROTO_UNSPEC
, 0,
1421 err
= avahi_client_errno(avahi_client
);
1422 #endif /* HAVE_DNSSD */
1426 _cupsLangPrintf(stderr
,
1427 _("ippfind: Unable to browse or resolve: %s"),
1428 dnssd_error_string(err
));
1429 return (IPPFIND_EXIT_BONJOUR
);
1435 else if (service
->is_resolved
&& !service
->is_processed
)
1438 * Resolved, not process this service against the expressions...
1444 DNSServiceRefDeallocate(service
->ref
);
1446 avahi_service_resolver_free(service
->ref
);
1447 #endif /* HAVE_DNSSD */
1449 service
->ref
= NULL
;
1452 if (eval_expr(service
, expressions
))
1453 status
= IPPFIND_EXIT_TRUE
;
1455 service
->is_processed
= 1;
1457 else if (service
->ref
)
1462 * If we have processed all services we have discovered, then we are done.
1465 if (processed
== cupsArrayCount(services
) && bonjour_timeout
<= 1.0)
1471 return (IPPFIND_EXIT_BONJOUR
);
1479 * 'browse_callback()' - Browse devices.
1482 static void DNSSD_API
1484 DNSServiceRef sdRef
, /* I - Service reference */
1485 DNSServiceFlags flags
, /* I - Option flags */
1486 uint32_t interfaceIndex
, /* I - Interface number */
1487 DNSServiceErrorType errorCode
, /* I - Error, if any */
1488 const char *serviceName
, /* I - Name of service/device */
1489 const char *regtype
, /* I - Type of service */
1490 const char *replyDomain
, /* I - Service domain */
1491 void *context
) /* I - Services array */
1494 * Only process "add" data...
1498 (void)interfaceIndex
;
1500 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1507 get_service((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
1512 * 'browse_local_callback()' - Browse local devices.
1515 static void DNSSD_API
1516 browse_local_callback(
1517 DNSServiceRef sdRef
, /* I - Service reference */
1518 DNSServiceFlags flags
, /* I - Option flags */
1519 uint32_t interfaceIndex
, /* I - Interface number */
1520 DNSServiceErrorType errorCode
, /* I - Error, if any */
1521 const char *serviceName
, /* I - Name of service/device */
1522 const char *regtype
, /* I - Type of service */
1523 const char *replyDomain
, /* I - Service domain */
1524 void *context
) /* I - Services array */
1526 ippfind_srv_t
*service
; /* Service */
1530 * Only process "add" data...
1534 (void)interfaceIndex
;
1536 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1543 service
= get_service((cups_array_t
*)context
, serviceName
, regtype
,
1545 service
->is_local
= 1;
1547 #endif /* HAVE_DNSSD */
1552 * 'browse_callback()' - Browse devices.
1557 AvahiServiceBrowser
*browser
, /* I - Browser */
1558 AvahiIfIndex interface
, /* I - Interface index (unused) */
1559 AvahiProtocol protocol
, /* I - Network protocol (unused) */
1560 AvahiBrowserEvent event
, /* I - What happened */
1561 const char *name
, /* I - Service name */
1562 const char *type
, /* I - Registration type */
1563 const char *domain
, /* I - Domain */
1564 AvahiLookupResultFlags flags
, /* I - Flags */
1565 void *context
) /* I - Services array */
1567 AvahiClient
*client
= avahi_service_browser_get_client(browser
);
1568 /* Client information */
1569 ippfind_srv_t
*service
; /* Service information */
1578 case AVAHI_BROWSER_FAILURE
:
1579 fprintf(stderr
, "DEBUG: browse_callback: %s\n",
1580 avahi_strerror(avahi_client_errno(client
)));
1582 avahi_simple_poll_quit(avahi_poll
);
1585 case AVAHI_BROWSER_NEW
:
1587 * This object is new on the network. Create a device entry for it if
1588 * it doesn't yet exist.
1591 service
= get_service((cups_array_t
*)context
, name
, type
, domain
);
1593 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
1594 service
->is_local
= 1;
1597 case AVAHI_BROWSER_REMOVE
:
1598 case AVAHI_BROWSER_ALL_FOR_NOW
:
1599 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
1606 * 'client_callback()' - Avahi client callback function.
1611 AvahiClient
*client
, /* I - Client information (unused) */
1612 AvahiClientState state
, /* I - Current state */
1613 void *context
) /* I - User data (unused) */
1619 * If the connection drops, quit.
1622 if (state
== AVAHI_CLIENT_FAILURE
)
1624 fputs("DEBUG: Avahi connection failed.\n", stderr
);
1626 avahi_simple_poll_quit(avahi_poll
);
1629 #endif /* HAVE_AVAHI */
1633 * 'compare_services()' - Compare two devices.
1636 static int /* O - Result of comparison */
1637 compare_services(ippfind_srv_t
*a
, /* I - First device */
1638 ippfind_srv_t
*b
) /* I - Second device */
1640 return (strcmp(a
->name
, b
->name
));
1645 * 'dnssd_error_string()' - Return an error string for an error code.
1648 static const char * /* O - Error message */
1649 dnssd_error_string(int error
) /* I - Error number */
1654 case kDNSServiceErr_NoError
:
1658 case kDNSServiceErr_Unknown
:
1659 return ("Unknown error.");
1661 case kDNSServiceErr_NoSuchName
:
1662 return ("Service not found.");
1664 case kDNSServiceErr_NoMemory
:
1665 return ("Out of memory.");
1667 case kDNSServiceErr_BadParam
:
1668 return ("Bad parameter.");
1670 case kDNSServiceErr_BadReference
:
1671 return ("Bad service reference.");
1673 case kDNSServiceErr_BadState
:
1674 return ("Bad state.");
1676 case kDNSServiceErr_BadFlags
:
1677 return ("Bad flags.");
1679 case kDNSServiceErr_Unsupported
:
1680 return ("Unsupported.");
1682 case kDNSServiceErr_NotInitialized
:
1683 return ("Not initialized.");
1685 case kDNSServiceErr_AlreadyRegistered
:
1686 return ("Already registered.");
1688 case kDNSServiceErr_NameConflict
:
1689 return ("Name conflict.");
1691 case kDNSServiceErr_Invalid
:
1692 return ("Invalid name.");
1694 case kDNSServiceErr_Firewall
:
1695 return ("Firewall prevents registration.");
1697 case kDNSServiceErr_Incompatible
:
1698 return ("Client library incompatible.");
1700 case kDNSServiceErr_BadInterfaceIndex
:
1701 return ("Bad interface index.");
1703 case kDNSServiceErr_Refused
:
1704 return ("Server prevents registration.");
1706 case kDNSServiceErr_NoSuchRecord
:
1707 return ("Record not found.");
1709 case kDNSServiceErr_NoAuth
:
1710 return ("Authentication required.");
1712 case kDNSServiceErr_NoSuchKey
:
1713 return ("Encryption key not found.");
1715 case kDNSServiceErr_NATTraversal
:
1716 return ("Unable to traverse NAT boundary.");
1718 case kDNSServiceErr_DoubleNAT
:
1719 return ("Unable to traverse double-NAT boundary.");
1721 case kDNSServiceErr_BadTime
:
1722 return ("Bad system time.");
1724 case kDNSServiceErr_BadSig
:
1725 return ("Bad signature.");
1727 case kDNSServiceErr_BadKey
:
1728 return ("Bad encryption key.");
1730 case kDNSServiceErr_Transient
:
1731 return ("Transient error occurred - please try again.");
1733 case kDNSServiceErr_ServiceNotRunning
:
1734 return ("Server not running.");
1736 case kDNSServiceErr_NATPortMappingUnsupported
:
1737 return ("NAT doesn't support NAT-PMP or UPnP.");
1739 case kDNSServiceErr_NATPortMappingDisabled
:
1740 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1742 case kDNSServiceErr_NoRouter
:
1743 return ("No Internet/default router configured.");
1745 case kDNSServiceErr_PollingMode
:
1746 return ("Service polling mode error.");
1749 case kDNSServiceErr_Timeout
:
1750 return ("Service timeout.");
1751 #endif /* !_WIN32 */
1754 # elif defined(HAVE_AVAHI)
1755 return (avahi_strerror(error
));
1756 # endif /* HAVE_DNSSD */
1761 * 'eval_expr()' - Evaluate the expressions against the specified service.
1763 * Returns 1 for true and 0 for false.
1766 static int /* O - Result of evaluation */
1767 eval_expr(ippfind_srv_t
*service
, /* I - Service */
1768 ippfind_expr_t
*expressions
) /* I - Expressions */
1770 ippfind_op_t logic
; /* Logical operation */
1771 int result
; /* Result of current expression */
1772 ippfind_expr_t
*expression
; /* Current expression */
1773 const char *val
; /* TXT value */
1776 * Loop through the expressions...
1779 if (expressions
&& expressions
->parent
)
1780 logic
= expressions
->parent
->op
;
1782 logic
= IPPFIND_OP_AND
;
1784 for (expression
= expressions
; expression
; expression
= expression
->next
)
1786 switch (expression
->op
)
1789 case IPPFIND_OP_AND
:
1790 case IPPFIND_OP_OR
:
1791 if (expression
->child
)
1792 result
= eval_expr(service
, expression
->child
);
1794 result
= expression
->op
== IPPFIND_OP_AND
;
1796 case IPPFIND_OP_TRUE
:
1799 case IPPFIND_OP_FALSE
:
1802 case IPPFIND_OP_IS_LOCAL
:
1803 result
= service
->is_local
;
1805 case IPPFIND_OP_IS_REMOTE
:
1806 result
= !service
->is_local
;
1808 case IPPFIND_OP_DOMAIN_REGEX
:
1809 result
= !regexec(&(expression
->re
), service
->domain
, 0, NULL
, 0);
1811 case IPPFIND_OP_NAME_REGEX
:
1812 result
= !regexec(&(expression
->re
), service
->name
, 0, NULL
, 0);
1814 case IPPFIND_OP_NAME_LITERAL
:
1815 result
= !_cups_strcasecmp(expression
->name
, service
->name
);
1817 case IPPFIND_OP_HOST_REGEX
:
1818 result
= !regexec(&(expression
->re
), service
->host
, 0, NULL
, 0);
1820 case IPPFIND_OP_PORT_RANGE
:
1821 result
= service
->port
>= expression
->range
[0] &&
1822 service
->port
<= expression
->range
[1];
1824 case IPPFIND_OP_PATH_REGEX
:
1825 result
= !regexec(&(expression
->re
), service
->resource
, 0, NULL
, 0);
1827 case IPPFIND_OP_TXT_EXISTS
:
1828 result
= cupsGetOption(expression
->name
, service
->num_txt
,
1829 service
->txt
) != NULL
;
1831 case IPPFIND_OP_TXT_REGEX
:
1832 val
= cupsGetOption(expression
->name
, service
->num_txt
,
1835 result
= !regexec(&(expression
->re
), val
, 0, NULL
, 0);
1839 if (getenv("IPPFIND_DEBUG"))
1840 printf("TXT_REGEX of \"%s\": %d\n", val
, result
);
1842 case IPPFIND_OP_URI_REGEX
:
1843 result
= !regexec(&(expression
->re
), service
->uri
, 0, NULL
, 0);
1845 case IPPFIND_OP_EXEC
:
1846 result
= exec_program(service
, expression
->num_args
,
1849 case IPPFIND_OP_LIST
:
1850 result
= list_service(service
);
1852 case IPPFIND_OP_PRINT_NAME
:
1853 _cupsLangPuts(stdout
, service
->name
);
1856 case IPPFIND_OP_PRINT_URI
:
1857 _cupsLangPuts(stdout
, service
->uri
);
1860 case IPPFIND_OP_QUIET
:
1865 if (expression
->invert
)
1868 if (logic
== IPPFIND_OP_AND
&& !result
)
1870 else if (logic
== IPPFIND_OP_OR
&& result
)
1874 return (logic
== IPPFIND_OP_AND
);
1879 * 'exec_program()' - Execute a program for a service.
1882 static int /* O - 1 if program terminated
1883 successfully, 0 otherwise. */
1884 exec_program(ippfind_srv_t
*service
, /* I - Service */
1885 int num_args
, /* I - Number of command-line args */
1886 char **args
) /* I - Command-line arguments */
1888 char **myargv
, /* Command-line arguments */
1889 **myenvp
, /* Environment variables */
1890 *ptr
, /* Pointer into variable */
1891 domain
[1024], /* IPPFIND_SERVICE_DOMAIN */
1892 hostname
[1024], /* IPPFIND_SERVICE_HOSTNAME */
1893 name
[256], /* IPPFIND_SERVICE_NAME */
1894 port
[32], /* IPPFIND_SERVICE_PORT */
1895 regtype
[256], /* IPPFIND_SERVICE_REGTYPE */
1896 scheme
[128], /* IPPFIND_SERVICE_SCHEME */
1897 uri
[1024], /* IPPFIND_SERVICE_URI */
1898 txt
[100][256]; /* IPPFIND_TXT_foo */
1899 int i
, /* Looping var */
1900 myenvc
, /* Number of environment variables */
1901 status
; /* Exit status of program */
1903 char program
[1024]; /* Program to execute */
1904 int pid
; /* Process ID */
1905 #endif /* !_WIN32 */
1909 * Environment variables...
1912 snprintf(domain
, sizeof(domain
), "IPPFIND_SERVICE_DOMAIN=%s",
1914 snprintf(hostname
, sizeof(hostname
), "IPPFIND_SERVICE_HOSTNAME=%s",
1916 snprintf(name
, sizeof(name
), "IPPFIND_SERVICE_NAME=%s", service
->name
);
1917 snprintf(port
, sizeof(port
), "IPPFIND_SERVICE_PORT=%d", service
->port
);
1918 snprintf(regtype
, sizeof(regtype
), "IPPFIND_SERVICE_REGTYPE=%s",
1920 snprintf(scheme
, sizeof(scheme
), "IPPFIND_SERVICE_SCHEME=%s",
1921 !strncmp(service
->regtype
, "_http._tcp", 10) ? "http" :
1922 !strncmp(service
->regtype
, "_https._tcp", 11) ? "https" :
1923 !strncmp(service
->regtype
, "_ipp._tcp", 9) ? "ipp" :
1924 !strncmp(service
->regtype
, "_ipps._tcp", 10) ? "ipps" : "lpd");
1925 snprintf(uri
, sizeof(uri
), "IPPFIND_SERVICE_URI=%s", service
->uri
);
1926 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1928 snprintf(txt
[i
], sizeof(txt
[i
]), "IPPFIND_TXT_%s=%s", service
->txt
[i
].name
,
1929 service
->txt
[i
].value
);
1930 for (ptr
= txt
[i
] + 12; *ptr
&& *ptr
!= '='; ptr
++)
1931 *ptr
= (char)_cups_toupper(*ptr
);
1934 for (i
= 0, myenvc
= 7 + service
->num_txt
; environ
[i
]; i
++)
1935 if (strncmp(environ
[i
], "IPPFIND_", 8))
1938 if ((myenvp
= calloc(sizeof(char *), (size_t)(myenvc
+ 1))) == NULL
)
1940 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1941 exit(IPPFIND_EXIT_MEMORY
);
1944 for (i
= 0, myenvc
= 0; environ
[i
]; i
++)
1945 if (strncmp(environ
[i
], "IPPFIND_", 8))
1946 myenvp
[myenvc
++] = environ
[i
];
1948 myenvp
[myenvc
++] = domain
;
1949 myenvp
[myenvc
++] = hostname
;
1950 myenvp
[myenvc
++] = name
;
1951 myenvp
[myenvc
++] = port
;
1952 myenvp
[myenvc
++] = regtype
;
1953 myenvp
[myenvc
++] = scheme
;
1954 myenvp
[myenvc
++] = uri
;
1956 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1957 myenvp
[myenvc
++] = txt
[i
];
1960 * Allocate and copy command-line arguments...
1963 if ((myargv
= calloc(sizeof(char *), (size_t)(num_args
+ 1))) == NULL
)
1965 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1966 exit(IPPFIND_EXIT_MEMORY
);
1969 for (i
= 0; i
< num_args
; i
++)
1971 if (strchr(args
[i
], '{'))
1973 char temp
[2048], /* Temporary string */
1974 *tptr
, /* Pointer into temporary string */
1975 keyword
[256], /* {keyword} */
1976 *kptr
; /* Pointer into keyword */
1978 for (ptr
= args
[i
], tptr
= temp
; *ptr
; ptr
++)
1983 * Do a {var} substitution...
1986 for (kptr
= keyword
, ptr
++; *ptr
&& *ptr
!= '}'; ptr
++)
1987 if (kptr
< (keyword
+ sizeof(keyword
) - 1))
1992 _cupsLangPuts(stderr
,
1993 _("ippfind: Missing close brace in substitution."));
1994 exit(IPPFIND_EXIT_SYNTAX
);
1998 if (!keyword
[0] || !strcmp(keyword
, "service_uri"))
1999 strlcpy(tptr
, service
->uri
, sizeof(temp
) - (size_t)(tptr
- temp
));
2000 else if (!strcmp(keyword
, "service_domain"))
2001 strlcpy(tptr
, service
->domain
, sizeof(temp
) - (size_t)(tptr
- temp
));
2002 else if (!strcmp(keyword
, "service_hostname"))
2003 strlcpy(tptr
, service
->host
, sizeof(temp
) - (size_t)(tptr
- temp
));
2004 else if (!strcmp(keyword
, "service_name"))
2005 strlcpy(tptr
, service
->name
, sizeof(temp
) - (size_t)(tptr
- temp
));
2006 else if (!strcmp(keyword
, "service_path"))
2007 strlcpy(tptr
, service
->resource
, sizeof(temp
) - (size_t)(tptr
- temp
));
2008 else if (!strcmp(keyword
, "service_port"))
2009 strlcpy(tptr
, port
+ 21, sizeof(temp
) - (size_t)(tptr
- temp
));
2010 else if (!strcmp(keyword
, "service_scheme"))
2011 strlcpy(tptr
, scheme
+ 22, sizeof(temp
) - (size_t)(tptr
- temp
));
2012 else if (!strncmp(keyword
, "txt_", 4))
2014 const char *val
= cupsGetOption(keyword
+ 4, service
->num_txt
, service
->txt
);
2016 strlcpy(tptr
, val
, sizeof(temp
) - (size_t)(tptr
- temp
));
2022 _cupsLangPrintf(stderr
, _("ippfind: Unknown variable \"{%s}\"."),
2024 exit(IPPFIND_EXIT_SYNTAX
);
2027 tptr
+= strlen(tptr
);
2029 else if (tptr
< (temp
+ sizeof(temp
) - 1))
2034 myargv
[i
] = strdup(temp
);
2037 myargv
[i
] = strdup(args
[i
]);
2041 if (getenv("IPPFIND_DEBUG"))
2043 printf("\nProgram:\n %s\n", args
[0]);
2044 puts("\nArguments:");
2045 for (i
= 0; i
< num_args
; i
++)
2046 printf(" %s\n", myargv
[i
]);
2047 puts("\nEnvironment:");
2048 for (i
= 0; i
< myenvc
; i
++)
2049 printf(" %s\n", myenvp
[i
]);
2052 status
= _spawnvpe(_P_WAIT
, args
[0], myargv
, myenvp
);
2056 * Execute the program...
2059 if (strchr(args
[0], '/') && !access(args
[0], X_OK
))
2060 strlcpy(program
, args
[0], sizeof(program
));
2061 else if (!cupsFileFind(args
[0], getenv("PATH"), 1, program
, sizeof(program
)))
2063 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2064 args
[0], strerror(ENOENT
));
2065 exit(IPPFIND_EXIT_SYNTAX
);
2068 if (getenv("IPPFIND_DEBUG"))
2070 printf("\nProgram:\n %s\n", program
);
2071 puts("\nArguments:");
2072 for (i
= 0; i
< num_args
; i
++)
2073 printf(" %s\n", myargv
[i
]);
2074 puts("\nEnvironment:");
2075 for (i
= 0; i
< myenvc
; i
++)
2076 printf(" %s\n", myenvp
[i
]);
2079 if ((pid
= fork()) == 0)
2082 * Child comes here...
2085 execve(program
, myargv
, myenvp
);
2090 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2091 args
[0], strerror(errno
));
2092 exit(IPPFIND_EXIT_SYNTAX
);
2097 * Wait for it to complete...
2100 while (wait(&status
) != pid
)
2109 for (i
= 0; i
< num_args
; i
++)
2116 * Return whether the program succeeded or crashed...
2119 if (getenv("IPPFIND_DEBUG"))
2122 printf("Exit Status: %d\n", status
);
2124 if (WIFEXITED(status
))
2125 printf("Exit Status: %d\n", WEXITSTATUS(status
));
2127 printf("Terminating Signal: %d\n", WTERMSIG(status
));
2131 return (status
== 0);
2136 * 'get_service()' - Create or update a device.
2139 static ippfind_srv_t
* /* O - Service */
2140 get_service(cups_array_t
*services
, /* I - Service array */
2141 const char *serviceName
, /* I - Name of service/device */
2142 const char *regtype
, /* I - Type of service */
2143 const char *replyDomain
) /* I - Service domain */
2145 ippfind_srv_t key
, /* Search key */
2146 *service
; /* Service */
2147 char fullName
[kDNSServiceMaxDomainName
];
2148 /* Full name for query */
2152 * See if this is a new device...
2155 key
.name
= (char *)serviceName
;
2156 key
.regtype
= (char *)regtype
;
2158 for (service
= cupsArrayFind(services
, &key
);
2160 service
= cupsArrayNext(services
))
2161 if (_cups_strcasecmp(service
->name
, key
.name
))
2163 else if (!strcmp(service
->regtype
, key
.regtype
))
2167 * Yes, add the service...
2170 service
= calloc(sizeof(ippfind_srv_t
), 1);
2171 service
->name
= strdup(serviceName
);
2172 service
->domain
= strdup(replyDomain
);
2173 service
->regtype
= strdup(regtype
);
2175 cupsArrayAdd(services
, service
);
2178 * Set the "full name" of this service, which is used for queries and
2183 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
2184 #else /* HAVE_AVAHI */
2185 avahi_service_name_join(fullName
, kDNSServiceMaxDomainName
, serviceName
,
2186 regtype
, replyDomain
);
2187 #endif /* HAVE_DNSSD */
2189 service
->fullName
= strdup(fullName
);
2196 * 'get_time()' - Get the current time-of-day in seconds.
2203 struct _timeb curtime
; /* Current Windows time */
2207 return (curtime
.time
+ 0.001 * curtime
.millitm
);
2210 struct timeval curtime
; /* Current UNIX time */
2212 if (gettimeofday(&curtime
, NULL
))
2215 return (curtime
.tv_sec
+ 0.000001 * curtime
.tv_usec
);
2221 * 'list_service()' - List the contents of a service.
2224 static int /* O - 1 if successful, 0 otherwise */
2225 list_service(ippfind_srv_t
*service
) /* I - Service */
2227 http_addrlist_t
*addrlist
; /* Address(es) of service */
2228 char port
[10]; /* Port number of service */
2231 snprintf(port
, sizeof(port
), "%d", service
->port
);
2233 if ((addrlist
= httpAddrGetList(service
->host
, address_family
, port
)) == NULL
)
2235 _cupsLangPrintf(stdout
, "%s unreachable", service
->uri
);
2239 if (!strncmp(service
->regtype
, "_ipp._tcp", 9) ||
2240 !strncmp(service
->regtype
, "_ipps._tcp", 10))
2246 http_t
*http
; /* HTTP connection */
2247 ipp_t
*request
, /* IPP request */
2248 *response
; /* IPP response */
2249 ipp_attribute_t
*attr
; /* IPP attribute */
2250 int i
, /* Looping var */
2251 count
, /* Number of values */
2252 version
, /* IPP version */
2253 paccepting
; /* printer-is-accepting-jobs value */
2254 ipp_pstate_t pstate
; /* printer-state value */
2255 char preasons
[1024], /* Comma-delimited printer-state-reasons */
2256 *ptr
, /* Pointer into reasons */
2257 *end
; /* End of reasons buffer */
2258 static const char * const rattrs
[] =/* Requested attributes */
2260 "printer-is-accepting-jobs",
2262 "printer-state-reasons"
2266 * Connect to the printer...
2269 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2270 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2271 HTTP_ENCRYPTION_ALWAYS
:
2272 HTTP_ENCRYPTION_IF_REQUESTED
,
2275 httpAddrFreeList(addrlist
);
2279 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2284 * Get the current printer state...
2288 version
= ipp_version
;
2292 request
= ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES
);
2293 ippSetVersion(request
, version
/ 10, version
% 10);
2294 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri", NULL
,
2296 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
2297 "requesting-user-name", NULL
, cupsUser());
2298 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
2299 "requested-attributes",
2300 (int)(sizeof(rattrs
) / sizeof(rattrs
[0])), NULL
, rattrs
);
2302 response
= cupsDoRequest(http
, request
, service
->resource
);
2304 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST
&& version
> 11)
2307 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
&& version
> 11);
2313 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
)
2315 _cupsLangPrintf(stdout
, "%s: unavailable", service
->uri
);
2319 if ((attr
= ippFindAttribute(response
, "printer-state",
2320 IPP_TAG_ENUM
)) != NULL
)
2321 pstate
= (ipp_pstate_t
)ippGetInteger(attr
, 0);
2323 pstate
= IPP_PSTATE_STOPPED
;
2325 if ((attr
= ippFindAttribute(response
, "printer-is-accepting-jobs",
2326 IPP_TAG_BOOLEAN
)) != NULL
)
2327 paccepting
= ippGetBoolean(attr
, 0);
2331 if ((attr
= ippFindAttribute(response
, "printer-state-reasons",
2332 IPP_TAG_KEYWORD
)) != NULL
)
2334 strlcpy(preasons
, ippGetString(attr
, 0, NULL
), sizeof(preasons
));
2336 for (i
= 1, count
= ippGetCount(attr
), ptr
= preasons
+ strlen(preasons
),
2337 end
= preasons
+ sizeof(preasons
) - 1;
2338 i
< count
&& ptr
< end
;
2339 i
++, ptr
+= strlen(ptr
))
2342 strlcpy(ptr
, ippGetString(attr
, i
, NULL
), (size_t)(end
- ptr
+ 1));
2346 strlcpy(preasons
, "none", sizeof(preasons
));
2348 ippDelete(response
);
2351 _cupsLangPrintf(stdout
, "%s %s %s %s", service
->uri
, ippEnumString("printer-state", (int)pstate
), paccepting
? "accepting-jobs" : "not-accepting-jobs", preasons
);
2353 else if (!strncmp(service
->regtype
, "_http._tcp", 10) ||
2354 !strncmp(service
->regtype
, "_https._tcp", 11))
2357 * HTTP/HTTPS web page
2360 http_t
*http
; /* HTTP connection */
2361 http_status_t status
; /* HEAD status */
2365 * Connect to the web server...
2368 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2369 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2370 HTTP_ENCRYPTION_ALWAYS
:
2371 HTTP_ENCRYPTION_IF_REQUESTED
,
2374 httpAddrFreeList(addrlist
);
2378 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2382 if (httpGet(http
, service
->resource
))
2384 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2390 status
= httpUpdate(http
);
2392 while (status
== HTTP_STATUS_CONTINUE
);
2397 if (status
>= HTTP_STATUS_BAD_REQUEST
)
2399 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2403 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2405 else if (!strncmp(service
->regtype
, "_printer._tcp", 13))
2411 int sock
; /* Socket */
2414 if (!httpAddrConnect(addrlist
, &sock
))
2416 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2417 httpAddrFreeList(addrlist
);
2421 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2422 httpAddrFreeList(addrlist
);
2424 httpAddrClose(NULL
, sock
);
2428 _cupsLangPrintf(stdout
, "%s unsupported", service
->uri
);
2429 httpAddrFreeList(addrlist
);
2438 * 'new_expr()' - Create a new expression.
2441 static ippfind_expr_t
* /* O - New expression */
2442 new_expr(ippfind_op_t op
, /* I - Operation */
2443 int invert
, /* I - Invert result? */
2444 const char *value
, /* I - TXT key or port range */
2445 const char *regex
, /* I - Regular expression */
2446 char **args
) /* I - Pointer to argument strings */
2448 ippfind_expr_t
*temp
; /* New expression */
2451 if ((temp
= calloc(1, sizeof(ippfind_expr_t
))) == NULL
)
2455 temp
->invert
= invert
;
2457 if (op
== IPPFIND_OP_TXT_EXISTS
|| op
== IPPFIND_OP_TXT_REGEX
|| op
== IPPFIND_OP_NAME_LITERAL
)
2458 temp
->name
= (char *)value
;
2459 else if (op
== IPPFIND_OP_PORT_RANGE
)
2462 * Pull port number range of the form "number", "-number" (0-number),
2463 * "number-" (number-65535), and "number-number".
2468 temp
->range
[1] = atoi(value
+ 1);
2470 else if (strchr(value
, '-'))
2472 if (sscanf(value
, "%d-%d", temp
->range
, temp
->range
+ 1) == 1)
2473 temp
->range
[1] = 65535;
2477 temp
->range
[0] = temp
->range
[1] = atoi(value
);
2483 int err
= regcomp(&(temp
->re
), regex
, REG_NOSUB
| REG_ICASE
| REG_EXTENDED
);
2487 char message
[256]; /* Error message */
2489 regerror(err
, &(temp
->re
), message
, sizeof(message
));
2490 _cupsLangPrintf(stderr
, _("ippfind: Bad regular expression: %s"),
2492 exit(IPPFIND_EXIT_SYNTAX
);
2498 int num_args
; /* Number of arguments */
2500 for (num_args
= 1; args
[num_args
]; num_args
++)
2501 if (!strcmp(args
[num_args
], ";"))
2504 temp
->num_args
= num_args
;
2505 temp
->args
= malloc((size_t)num_args
* sizeof(char *));
2506 memcpy(temp
->args
, args
, (size_t)num_args
* sizeof(char *));
2515 * 'poll_callback()' - Wait for input on the specified file descriptors.
2517 * Note: This function is needed because avahi_simple_poll_iterate is broken
2518 * and always uses a timeout of 0 (!) milliseconds.
2519 * (Avahi Ticket #364)
2522 static int /* O - Number of file descriptors matching */
2524 struct pollfd
*pollfds
, /* I - File descriptors */
2525 unsigned int num_pollfds
, /* I - Number of file descriptors */
2526 int timeout
, /* I - Timeout in milliseconds (unused) */
2527 void *context
) /* I - User data (unused) */
2529 int val
; /* Return value */
2535 val
= poll(pollfds
, num_pollfds
, 500);
2542 #endif /* HAVE_AVAHI */
2546 * 'resolve_callback()' - Process resolve data.
2550 static void DNSSD_API
2552 DNSServiceRef sdRef
, /* I - Service reference */
2553 DNSServiceFlags flags
, /* I - Data flags */
2554 uint32_t interfaceIndex
, /* I - Interface */
2555 DNSServiceErrorType errorCode
, /* I - Error, if any */
2556 const char *fullName
, /* I - Full service name */
2557 const char *hostTarget
, /* I - Hostname */
2558 uint16_t port
, /* I - Port number (network byte order) */
2559 uint16_t txtLen
, /* I - Length of TXT record data */
2560 const unsigned char *txtRecord
, /* I - TXT record data */
2561 void *context
) /* I - Service */
2563 char key
[256], /* TXT key value */
2564 *value
; /* Value from TXT record */
2565 const unsigned char *txtEnd
; /* End of TXT record */
2566 uint8_t valueLen
; /* Length of value */
2567 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2572 * Only process "add" data...
2577 (void)interfaceIndex
;
2580 if (errorCode
!= kDNSServiceErr_NoError
)
2582 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
2583 dnssd_error_string(errorCode
));
2588 service
->is_resolved
= 1;
2589 service
->host
= strdup(hostTarget
);
2590 service
->port
= ntohs(port
);
2592 value
= service
->host
+ strlen(service
->host
) - 1;
2593 if (value
>= service
->host
&& *value
== '.')
2597 * Loop through the TXT key/value pairs and add them to an array...
2600 for (txtEnd
= txtRecord
+ txtLen
; txtRecord
< txtEnd
; txtRecord
+= valueLen
)
2603 * Ignore bogus strings...
2606 valueLen
= *txtRecord
++;
2608 memcpy(key
, txtRecord
, valueLen
);
2609 key
[valueLen
] = '\0';
2611 if ((value
= strchr(key
, '=')) == NULL
)
2617 * Add to array of TXT values...
2620 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2624 set_service_uri(service
);
2628 #elif defined(HAVE_AVAHI)
2631 AvahiServiceResolver
*resolver
, /* I - Resolver */
2632 AvahiIfIndex interface
, /* I - Interface */
2633 AvahiProtocol protocol
, /* I - Address protocol */
2634 AvahiResolverEvent event
, /* I - Event */
2635 const char *serviceName
,/* I - Service name */
2636 const char *regtype
, /* I - Registration type */
2637 const char *replyDomain
,/* I - Domain name */
2638 const char *hostTarget
, /* I - FQDN */
2639 const AvahiAddress
*address
, /* I - Address */
2640 uint16_t port
, /* I - Port number */
2641 AvahiStringList
*txt
, /* I - TXT records */
2642 AvahiLookupResultFlags flags
, /* I - Lookup flags */
2643 void *context
) /* I - Service */
2645 char key
[256], /* TXT key */
2646 *value
; /* TXT value */
2647 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2649 AvahiStringList
*current
; /* Current TXT key/value pair */
2654 if (event
!= AVAHI_RESOLVER_FOUND
)
2658 avahi_service_resolver_free(resolver
);
2659 avahi_simple_poll_quit(avahi_poll
);
2663 service
->is_resolved
= 1;
2664 service
->host
= strdup(hostTarget
);
2665 service
->port
= port
;
2667 value
= service
->host
+ strlen(service
->host
) - 1;
2668 if (value
>= service
->host
&& *value
== '.')
2672 * Loop through the TXT key/value pairs and add them to an array...
2675 for (current
= txt
; current
; current
= current
->next
)
2678 * Ignore bogus strings...
2681 if (current
->size
> (sizeof(key
) - 1))
2684 memcpy(key
, current
->text
, current
->size
);
2685 key
[current
->size
] = '\0';
2687 if ((value
= strchr(key
, '=')) == NULL
)
2693 * Add to array of TXT values...
2696 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2700 set_service_uri(service
);
2702 #endif /* HAVE_DNSSD */
2706 * 'set_service_uri()' - Set the URI of the service.
2710 set_service_uri(ippfind_srv_t
*service
) /* I - Service */
2712 char uri
[1024]; /* URI */
2713 const char *path
, /* Resource path */
2714 *scheme
; /* URI scheme */
2717 if (!strncmp(service
->regtype
, "_http.", 6))
2720 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2722 else if (!strncmp(service
->regtype
, "_https.", 7))
2725 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2727 else if (!strncmp(service
->regtype
, "_ipp.", 5))
2730 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2732 else if (!strncmp(service
->regtype
, "_ipps.", 6))
2735 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2737 else if (!strncmp(service
->regtype
, "_printer.", 9))
2740 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2745 if (!path
|| !*path
)
2750 service
->resource
= strdup(path
);
2754 snprintf(uri
, sizeof(uri
), "/%s", path
);
2755 service
->resource
= strdup(uri
);
2758 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
,
2759 service
->host
, service
->port
, service
->resource
);
2760 service
->uri
= strdup(uri
);
2765 * 'show_usage()' - Show program usage.
2771 _cupsLangPuts(stderr
, _("Usage: ippfind [options] regtype[,subtype]"
2772 "[.domain.] ... [expression]\n"
2773 " ippfind [options] name[.regtype[.domain.]] "
2774 "... [expression]\n"
2776 " ippfind --version"));
2777 _cupsLangPuts(stderr
, _("Options:"));
2778 _cupsLangPuts(stderr
, _("-4 Connect using IPv4"));
2779 _cupsLangPuts(stderr
, _("-6 Connect using IPv6"));
2780 _cupsLangPuts(stderr
, _("-T seconds Set the browse timeout in seconds"));
2781 _cupsLangPuts(stderr
, _("-V version Set default IPP version"));
2782 _cupsLangPuts(stderr
, _("--version Show program version"));
2783 _cupsLangPuts(stderr
, _("Expressions:"));
2784 _cupsLangPuts(stderr
, _("-P number[-number] Match port to number or range"));
2785 _cupsLangPuts(stderr
, _("-d regex Match domain to regular expression"));
2786 _cupsLangPuts(stderr
, _("-h regex Match hostname to regular expression"));
2787 _cupsLangPuts(stderr
, _("-l List attributes"));
2788 _cupsLangPuts(stderr
, _("-n regex Match service name to regular expression"));
2789 _cupsLangPuts(stderr
, _("-N name Match service name to literal name value"));
2790 _cupsLangPuts(stderr
, _("-p Print URI if true"));
2791 _cupsLangPuts(stderr
, _("-q Quietly report match via exit code"));
2792 _cupsLangPuts(stderr
, _("-r True if service is remote"));
2793 _cupsLangPuts(stderr
, _("-s Print service name if true"));
2794 _cupsLangPuts(stderr
, _("-t key True if the TXT record contains the key"));
2795 _cupsLangPuts(stderr
, _("-u regex Match URI to regular expression"));
2796 _cupsLangPuts(stderr
, _("-x utility [argument ...] ;\n"
2797 " Execute program if true"));
2798 _cupsLangPuts(stderr
, _("--domain regex Match domain to regular expression"));
2799 _cupsLangPuts(stderr
, _("--exec utility [argument ...] ;\n"
2800 " Execute program if true"));
2801 _cupsLangPuts(stderr
, _("--host regex Match hostname to regular expression"));
2802 _cupsLangPuts(stderr
, _("--literal-name name Match service name to literal name value"));
2803 _cupsLangPuts(stderr
, _("--local True if service is local"));
2804 _cupsLangPuts(stderr
, _("--ls List attributes"));
2805 _cupsLangPuts(stderr
, _("--name regex Match service name to regular expression"));
2806 _cupsLangPuts(stderr
, _("--path regex Match resource path to regular expression"));
2807 _cupsLangPuts(stderr
, _("--port number[-number] Match port to number or range"));
2808 _cupsLangPuts(stderr
, _("--print Print URI if true"));
2809 _cupsLangPuts(stderr
, _("--print-name Print service name if true"));
2810 _cupsLangPuts(stderr
, _("--quiet Quietly report match via exit code"));
2811 _cupsLangPuts(stderr
, _("--remote True if service is remote"));
2812 _cupsLangPuts(stderr
, _("--txt key True if the TXT record contains the key"));
2813 _cupsLangPuts(stderr
, _("--txt-* regex Match TXT record key to regular expression"));
2814 _cupsLangPuts(stderr
, _("--uri regex Match URI to regular expression"));
2815 _cupsLangPuts(stderr
, _("Modifiers:"));
2816 _cupsLangPuts(stderr
, _("( expressions ) Group expressions"));
2817 _cupsLangPuts(stderr
, _("! expression Unary NOT of expression"));
2818 _cupsLangPuts(stderr
, _("--not expression Unary NOT of expression"));
2819 _cupsLangPuts(stderr
, _("--false Always false"));
2820 _cupsLangPuts(stderr
, _("--true Always true"));
2821 _cupsLangPuts(stderr
, _("expression expression Logical AND"));
2822 _cupsLangPuts(stderr
, _("expression --and expression\n"
2824 _cupsLangPuts(stderr
, _("expression --or expression\n"
2826 _cupsLangPuts(stderr
, _("Substitutions:"));
2827 _cupsLangPuts(stderr
, _("{} URI"));
2828 _cupsLangPuts(stderr
, _("{service_domain} Domain name"));
2829 _cupsLangPuts(stderr
, _("{service_hostname} Fully-qualified domain name"));
2830 _cupsLangPuts(stderr
, _("{service_name} Service instance name"));
2831 _cupsLangPuts(stderr
, _("{service_port} Port number"));
2832 _cupsLangPuts(stderr
, _("{service_regtype} DNS-SD registration type"));
2833 _cupsLangPuts(stderr
, _("{service_scheme} URI scheme"));
2834 _cupsLangPuts(stderr
, _("{service_uri} URI"));
2835 _cupsLangPuts(stderr
, _("{txt_*} Value of TXT record key"));
2836 _cupsLangPuts(stderr
, _("Environment Variables:"));
2837 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_DOMAIN Domain name"));
2838 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_HOSTNAME\n"
2839 " Fully-qualified domain name"));
2840 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_NAME Service instance name"));
2841 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_PORT Port number"));
2842 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
2843 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_SCHEME URI scheme"));
2844 _cupsLangPuts(stderr
, _("IPPFIND_SERVICE_URI URI"));
2845 _cupsLangPuts(stderr
, _("IPPFIND_TXT_* Value of TXT record key"));
2847 exit(IPPFIND_EXIT_TRUE
);
2852 * 'show_version()' - Show program version.
2858 _cupsLangPuts(stderr
, CUPS_SVERSION
);
2860 exit(IPPFIND_EXIT_TRUE
);