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 © 2008-2018 by Apple Inc.
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
13 * Include necessary headers.
16 #define _CUPS_NO_DEPRECATED
17 #include <cups/cups-private.h>
20 # include <sys/timeb.h>
22 # include <sys/wait.h>
27 #elif defined(HAVE_AVAHI)
28 # include <avahi-client/client.h>
29 # include <avahi-client/lookup.h>
30 # include <avahi-common/simple-watch.h>
31 # include <avahi-common/domain.h>
32 # include <avahi-common/error.h>
33 # include <avahi-common/malloc.h>
34 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
35 #endif /* HAVE_DNSSD */
38 extern char **environ
; /* Process environment variables */
46 typedef enum ippfind_exit_e
/* Exit codes */
48 IPPFIND_EXIT_TRUE
= 0, /* OK and result is true */
49 IPPFIND_EXIT_FALSE
, /* OK but result is false*/
50 IPPFIND_EXIT_BONJOUR
, /* Browse/resolve failure */
51 IPPFIND_EXIT_SYNTAX
, /* Bad option or syntax error */
52 IPPFIND_EXIT_MEMORY
/* Out of memory */
55 typedef enum ippfind_op_e
/* Operations for expressions */
57 /* "Evaluation" operations */
58 IPPFIND_OP_NONE
, /* No operation */
59 IPPFIND_OP_AND
, /* Logical AND of all children */
60 IPPFIND_OP_OR
, /* Logical OR of all children */
61 IPPFIND_OP_TRUE
, /* Always true */
62 IPPFIND_OP_FALSE
, /* Always false */
63 IPPFIND_OP_IS_LOCAL
, /* Is a local service */
64 IPPFIND_OP_IS_REMOTE
, /* Is a remote service */
65 IPPFIND_OP_DOMAIN_REGEX
, /* Domain matches regular expression */
66 IPPFIND_OP_NAME_REGEX
, /* Name matches regular expression */
67 IPPFIND_OP_NAME_LITERAL
, /* Name matches literal string */
68 IPPFIND_OP_HOST_REGEX
, /* Hostname matches regular expression */
69 IPPFIND_OP_PORT_RANGE
, /* Port matches range */
70 IPPFIND_OP_PATH_REGEX
, /* Path matches regular expression */
71 IPPFIND_OP_TXT_EXISTS
, /* TXT record key exists */
72 IPPFIND_OP_TXT_REGEX
, /* TXT record key matches regular expression */
73 IPPFIND_OP_URI_REGEX
, /* URI matches regular expression */
75 /* "Output" operations */
76 IPPFIND_OP_EXEC
, /* Execute when true */
77 IPPFIND_OP_LIST
, /* List when true */
78 IPPFIND_OP_PRINT_NAME
, /* Print URI when true */
79 IPPFIND_OP_PRINT_URI
, /* Print name when true */
80 IPPFIND_OP_QUIET
/* No output when true */
83 typedef struct ippfind_expr_s
/* Expression */
86 *prev
, /* Previous expression */
87 *next
, /* Next expression */
88 *parent
, /* Parent expressions */
89 *child
; /* Child expressions */
90 ippfind_op_t op
; /* Operation code (see above) */
91 int invert
; /* Invert the result */
92 char *name
; /* TXT record key or literal name */
93 regex_t re
; /* Regular expression for matching */
94 int range
[2]; /* Port number range */
95 int num_args
; /* Number of arguments for exec */
96 char **args
; /* Arguments for exec */
99 typedef struct ippfind_srv_s
/* Service information */
102 DNSServiceRef ref
; /* Service reference for query */
103 #elif defined(HAVE_AVAHI)
104 AvahiServiceResolver
*ref
; /* Resolver */
105 #endif /* HAVE_DNSSD */
106 char *name
, /* Service name */
107 *domain
, /* Domain name */
108 *regtype
, /* Registration type */
109 *fullName
, /* Full name */
110 *host
, /* Hostname */
111 *resource
, /* Resource path */
113 int num_txt
; /* Number of TXT record keys */
114 cups_option_t
*txt
; /* TXT record keys */
115 int port
, /* Port number */
116 is_local
, /* Is a local service? */
117 is_processed
, /* Did we process the service? */
118 is_resolved
; /* Got the resolve data? */
127 static DNSServiceRef dnssd_ref
; /* Master service reference */
128 #elif defined(HAVE_AVAHI)
129 static AvahiClient
*avahi_client
= NULL
;/* Client information */
130 static int avahi_got_data
= 0; /* Got data from poll? */
131 static AvahiSimplePoll
*avahi_poll
= NULL
;
132 /* Poll information */
133 #endif /* HAVE_DNSSD */
135 static int address_family
= AF_UNSPEC
;
136 /* Address family for LIST */
137 static int bonjour_error
= 0; /* Error browsing/resolving? */
138 static double bonjour_timeout
= 1.0; /* Timeout in seconds */
139 static int ipp_version
= 20; /* IPP version for LIST */
147 static void DNSSD_API
browse_callback(DNSServiceRef sdRef
,
148 DNSServiceFlags flags
,
149 uint32_t interfaceIndex
,
150 DNSServiceErrorType errorCode
,
151 const char *serviceName
,
153 const char *replyDomain
, void *context
)
154 __attribute__((nonnull(1,5,6,7,8)));
155 static void DNSSD_API
browse_local_callback(DNSServiceRef sdRef
,
156 DNSServiceFlags flags
,
157 uint32_t interfaceIndex
,
158 DNSServiceErrorType errorCode
,
159 const char *serviceName
,
161 const char *replyDomain
,
163 __attribute__((nonnull(1,5,6,7,8)));
164 #elif defined(HAVE_AVAHI)
165 static void browse_callback(AvahiServiceBrowser
*browser
,
166 AvahiIfIndex interface
,
167 AvahiProtocol protocol
,
168 AvahiBrowserEvent event
,
169 const char *serviceName
,
171 const char *replyDomain
,
172 AvahiLookupResultFlags flags
,
174 static void client_callback(AvahiClient
*client
,
175 AvahiClientState state
,
177 #endif /* HAVE_AVAHI */
179 static int compare_services(ippfind_srv_t
*a
, ippfind_srv_t
*b
);
180 static const char *dnssd_error_string(int error
);
181 static int eval_expr(ippfind_srv_t
*service
,
182 ippfind_expr_t
*expressions
);
183 static int exec_program(ippfind_srv_t
*service
, int num_args
,
185 static ippfind_srv_t
*get_service(cups_array_t
*services
,
186 const char *serviceName
,
188 const char *replyDomain
)
189 __attribute__((nonnull(1,2,3,4)));
190 static double get_time(void);
191 static int list_service(ippfind_srv_t
*service
);
192 static ippfind_expr_t
*new_expr(ippfind_op_t op
, int invert
,
193 const char *value
, const char *regex
,
196 static void DNSSD_API
resolve_callback(DNSServiceRef sdRef
,
197 DNSServiceFlags flags
,
198 uint32_t interfaceIndex
,
199 DNSServiceErrorType errorCode
,
200 const char *fullName
,
201 const char *hostTarget
, uint16_t port
,
203 const unsigned char *txtRecord
,
205 __attribute__((nonnull(1,5,6,9, 10)));
206 #elif defined(HAVE_AVAHI)
207 static int poll_callback(struct pollfd
*pollfds
,
208 unsigned int num_pollfds
, int timeout
,
210 static void resolve_callback(AvahiServiceResolver
*res
,
211 AvahiIfIndex interface
,
212 AvahiProtocol protocol
,
213 AvahiResolverEvent event
,
214 const char *serviceName
,
216 const char *replyDomain
,
217 const char *host_name
,
218 const AvahiAddress
*address
,
220 AvahiStringList
*txt
,
221 AvahiLookupResultFlags flags
,
223 #endif /* HAVE_DNSSD */
224 static void set_service_uri(ippfind_srv_t
*service
);
225 static void show_usage(void) __attribute__((noreturn
));
226 static void show_version(void) __attribute__((noreturn
));
230 * 'main()' - Browse for printers.
233 int /* O - Exit status */
234 main(int argc
, /* I - Number of command-line args */
235 char *argv
[]) /* I - Command-line arguments */
237 int i
, /* Looping var */
238 have_output
= 0,/* Have output expression */
239 status
= IPPFIND_EXIT_FALSE
;
241 const char *opt
, /* Option character */
242 *search
; /* Current browse/resolve string */
243 cups_array_t
*searches
; /* Things to browse/resolve */
244 cups_array_t
*services
; /* Service array */
245 ippfind_srv_t
*service
; /* Current service */
246 ippfind_expr_t
*expressions
= NULL
,
247 /* Expression tree */
248 *temp
= NULL
, /* New expression */
249 *parent
= NULL
, /* Parent expression */
250 *current
= NULL
,/* Current expression */
251 *parens
[100]; /* Markers for parenthesis */
252 int num_parens
= 0; /* Number of parenthesis */
253 ippfind_op_t logic
= IPPFIND_OP_AND
;
254 /* Logic for next expression */
255 int invert
= 0; /* Invert expression? */
256 int err
; /* DNS-SD error */
258 fd_set sinput
; /* Input set for select() */
259 struct timeval stimeout
; /* Timeout for select() */
260 #endif /* HAVE_DNSSD */
261 double endtime
; /* End time */
262 static const char * const ops
[] = /* Node operation names */
289 * Initialize the locale...
292 _cupsSetLocale(argv
);
295 * Create arrays to track services and things we want to browse/resolve...
298 searches
= cupsArrayNew(NULL
, NULL
);
299 services
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
302 * Parse command-line...
305 if (getenv("IPPFIND_DEBUG"))
306 for (i
= 1; i
< argc
; i
++)
307 fprintf(stderr
, "argv[%d]=\"%s\"\n", i
, argv
[i
]);
309 for (i
= 1; i
< argc
; i
++)
311 if (argv
[i
][0] == '-')
313 if (argv
[i
][1] == '-')
316 * Parse --option options...
319 if (!strcmp(argv
[i
], "--and"))
321 if (logic
== IPPFIND_OP_OR
)
323 _cupsLangPuts(stderr
, _("ippfind: Cannot use --and after --or."));
329 _cupsLangPuts(stderr
,
330 _("ippfind: Missing expression before \"--and\"."));
336 else if (!strcmp(argv
[i
], "--domain"))
341 _cupsLangPrintf(stderr
,
342 _("ippfind: Missing regular expression after %s."),
347 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
, argv
[i
],
349 return (IPPFIND_EXIT_MEMORY
);
351 else if (!strcmp(argv
[i
], "--exec"))
356 _cupsLangPrintf(stderr
, _("ippfind: Expected program after %s."),
361 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
363 return (IPPFIND_EXIT_MEMORY
);
366 if (!strcmp(argv
[i
], ";"))
373 _cupsLangPrintf(stderr
, _("ippfind: Expected semi-colon after %s."),
380 else if (!strcmp(argv
[i
], "--false"))
382 if ((temp
= new_expr(IPPFIND_OP_FALSE
, invert
, NULL
, NULL
,
384 return (IPPFIND_EXIT_MEMORY
);
386 else if (!strcmp(argv
[i
], "--help"))
390 else if (!strcmp(argv
[i
], "--host"))
395 _cupsLangPrintf(stderr
,
396 _("ippfind: Missing regular expression after %s."),
401 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
, argv
[i
],
403 return (IPPFIND_EXIT_MEMORY
);
405 else if (!strcmp(argv
[i
], "--ls"))
407 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
409 return (IPPFIND_EXIT_MEMORY
);
413 else if (!strcmp(argv
[i
], "--local"))
415 if ((temp
= new_expr(IPPFIND_OP_IS_LOCAL
, invert
, NULL
, NULL
,
417 return (IPPFIND_EXIT_MEMORY
);
419 else if (!strcmp(argv
[i
], "--literal-name"))
424 _cupsLangPrintf(stderr
, _("ippfind: Missing name after %s."), "--literal-name");
428 if ((temp
= new_expr(IPPFIND_OP_NAME_LITERAL
, invert
, argv
[i
], NULL
, NULL
)) == NULL
)
429 return (IPPFIND_EXIT_MEMORY
);
431 else if (!strcmp(argv
[i
], "--name"))
436 _cupsLangPrintf(stderr
,
437 _("ippfind: Missing regular expression after %s."),
442 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
, argv
[i
],
444 return (IPPFIND_EXIT_MEMORY
);
446 else if (!strcmp(argv
[i
], "--not"))
450 else if (!strcmp(argv
[i
], "--or"))
454 _cupsLangPuts(stderr
,
455 _("ippfind: Missing expression before \"--or\"."));
459 logic
= IPPFIND_OP_OR
;
461 if (parent
&& parent
->op
== IPPFIND_OP_OR
)
464 * Already setup to do "foo --or bar --or baz"...
469 else if (!current
->prev
&& parent
)
472 * Change parent node into an OR node...
475 parent
->op
= IPPFIND_OP_OR
;
478 else if (!current
->prev
)
481 * Need to group "current" in a new OR node...
484 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
486 return (IPPFIND_EXIT_MEMORY
);
488 temp
->parent
= parent
;
489 temp
->child
= current
;
490 current
->parent
= temp
;
493 parent
->child
= temp
;
503 * Need to group previous expressions in an AND node, and then
504 * put that in an OR node...
507 if ((temp
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
509 return (IPPFIND_EXIT_MEMORY
);
511 while (current
->prev
)
513 current
->parent
= temp
;
514 current
= current
->prev
;
517 current
->parent
= temp
;
518 temp
->child
= current
;
521 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
523 return (IPPFIND_EXIT_MEMORY
);
525 temp
->parent
= parent
;
526 current
->parent
= temp
;
529 parent
->child
= temp
;
537 else if (!strcmp(argv
[i
], "--path"))
542 _cupsLangPrintf(stderr
,
543 _("ippfind: Missing regular expression after %s."),
548 if ((temp
= new_expr(IPPFIND_OP_PATH_REGEX
, invert
, NULL
, argv
[i
],
550 return (IPPFIND_EXIT_MEMORY
);
552 else if (!strcmp(argv
[i
], "--port"))
557 _cupsLangPrintf(stderr
,
558 _("ippfind: Expected port range after %s."),
563 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
], NULL
,
565 return (IPPFIND_EXIT_MEMORY
);
567 else if (!strcmp(argv
[i
], "--print"))
569 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
571 return (IPPFIND_EXIT_MEMORY
);
575 else if (!strcmp(argv
[i
], "--print-name"))
577 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
579 return (IPPFIND_EXIT_MEMORY
);
583 else if (!strcmp(argv
[i
], "--quiet"))
585 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
587 return (IPPFIND_EXIT_MEMORY
);
591 else if (!strcmp(argv
[i
], "--remote"))
593 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
595 return (IPPFIND_EXIT_MEMORY
);
597 else if (!strcmp(argv
[i
], "--true"))
599 if ((temp
= new_expr(IPPFIND_OP_TRUE
, invert
, NULL
, argv
[i
],
601 return (IPPFIND_EXIT_MEMORY
);
603 else if (!strcmp(argv
[i
], "--txt"))
608 _cupsLangPrintf(stderr
, _("ippfind: Expected key name after %s."),
613 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
], NULL
,
615 return (IPPFIND_EXIT_MEMORY
);
617 else if (!strncmp(argv
[i
], "--txt-", 6))
619 const char *key
= argv
[i
] + 6;/* TXT key */
624 _cupsLangPrintf(stderr
,
625 _("ippfind: Missing regular expression after %s."),
630 if ((temp
= new_expr(IPPFIND_OP_TXT_REGEX
, invert
, key
, argv
[i
],
632 return (IPPFIND_EXIT_MEMORY
);
634 else if (!strcmp(argv
[i
], "--uri"))
639 _cupsLangPrintf(stderr
,
640 _("ippfind: Missing regular expression after %s."),
645 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
, argv
[i
],
647 return (IPPFIND_EXIT_MEMORY
);
649 else if (!strcmp(argv
[i
], "--version"))
655 _cupsLangPrintf(stderr
, _("%s: Unknown option \"%s\"."),
663 * Add new expression...
666 if (logic
== IPPFIND_OP_AND
&&
667 current
&& current
->prev
&&
668 parent
&& parent
->op
!= IPPFIND_OP_AND
)
671 * Need to re-group "current" in a new AND node...
674 ippfind_expr_t
*tempand
; /* Temporary AND node */
676 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
678 return (IPPFIND_EXIT_MEMORY
);
681 * Replace "current" with new AND node at the end of this list...
684 current
->prev
->next
= tempand
;
685 tempand
->prev
= current
->prev
;
686 tempand
->parent
= parent
;
689 * Add "current to the new AND node...
692 tempand
->child
= current
;
693 current
->parent
= tempand
;
694 current
->prev
= NULL
;
699 * Add the new node at current level...
702 temp
->parent
= parent
;
703 temp
->prev
= current
;
706 current
->next
= temp
;
708 parent
->child
= temp
;
714 logic
= IPPFIND_OP_AND
;
724 for (opt
= argv
[i
] + 1; *opt
; opt
++)
729 address_family
= AF_INET
;
733 address_family
= AF_INET6
;
736 case 'N' : /* Literal name */
740 _cupsLangPrintf(stderr
, _("ippfind: Missing name after %s."), "-N");
744 if ((temp
= new_expr(IPPFIND_OP_NAME_LITERAL
, invert
, argv
[i
], NULL
, NULL
)) == NULL
)
745 return (IPPFIND_EXIT_MEMORY
);
752 _cupsLangPrintf(stderr
,
753 _("ippfind: Expected port range after %s."),
758 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
],
759 NULL
, NULL
)) == NULL
)
760 return (IPPFIND_EXIT_MEMORY
);
767 _cupsLangPrintf(stderr
,
768 _("%s: Missing timeout for \"-T\"."),
773 bonjour_timeout
= atof(argv
[i
]);
780 _cupsLangPrintf(stderr
,
781 _("%s: Missing version for \"-V\"."),
786 if (!strcmp(argv
[i
], "1.1"))
788 else if (!strcmp(argv
[i
], "2.0"))
790 else if (!strcmp(argv
[i
], "2.1"))
792 else if (!strcmp(argv
[i
], "2.2"))
796 _cupsLangPrintf(stderr
, _("%s: Bad version %s for \"-V\"."),
806 _cupsLangPrintf(stderr
,
807 _("ippfind: Missing regular expression after "
812 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
,
813 argv
[i
], NULL
)) == NULL
)
814 return (IPPFIND_EXIT_MEMORY
);
821 _cupsLangPrintf(stderr
,
822 _("ippfind: Missing regular expression after "
827 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
,
828 argv
[i
], NULL
)) == NULL
)
829 return (IPPFIND_EXIT_MEMORY
);
833 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
835 return (IPPFIND_EXIT_MEMORY
);
844 _cupsLangPrintf(stderr
,
845 _("ippfind: Missing regular expression after "
850 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
,
851 argv
[i
], NULL
)) == NULL
)
852 return (IPPFIND_EXIT_MEMORY
);
856 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
858 return (IPPFIND_EXIT_MEMORY
);
864 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
866 return (IPPFIND_EXIT_MEMORY
);
872 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
874 return (IPPFIND_EXIT_MEMORY
);
878 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
880 return (IPPFIND_EXIT_MEMORY
);
889 _cupsLangPrintf(stderr
,
890 _("ippfind: Missing key name after %s."),
895 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
],
896 NULL
, NULL
)) == NULL
)
897 return (IPPFIND_EXIT_MEMORY
);
904 _cupsLangPrintf(stderr
,
905 _("ippfind: Missing regular expression after "
910 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
,
911 argv
[i
], NULL
)) == NULL
)
912 return (IPPFIND_EXIT_MEMORY
);
919 _cupsLangPrintf(stderr
,
920 _("ippfind: Missing program after %s."),
925 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
927 return (IPPFIND_EXIT_MEMORY
);
930 if (!strcmp(argv
[i
], ";"))
937 _cupsLangPrintf(stderr
,
938 _("ippfind: Missing semi-colon after %s."),
947 _cupsLangPrintf(stderr
, _("%s: Unknown option \"-%c\"."),
955 * Add new expression...
958 if (logic
== IPPFIND_OP_AND
&&
959 current
&& current
->prev
&&
960 parent
&& parent
->op
!= IPPFIND_OP_AND
)
963 * Need to re-group "current" in a new AND node...
966 ippfind_expr_t
*tempand
; /* Temporary AND node */
968 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
970 return (IPPFIND_EXIT_MEMORY
);
973 * Replace "current" with new AND node at the end of this list...
976 current
->prev
->next
= tempand
;
977 tempand
->prev
= current
->prev
;
978 tempand
->parent
= parent
;
981 * Add "current to the new AND node...
984 tempand
->child
= current
;
985 current
->parent
= tempand
;
986 current
->prev
= NULL
;
991 * Add the new node at current level...
994 temp
->parent
= parent
;
995 temp
->prev
= current
;
998 current
->next
= temp
;
1000 parent
->child
= temp
;
1006 logic
= IPPFIND_OP_AND
;
1012 else if (!strcmp(argv
[i
], "("))
1014 if (num_parens
>= 100)
1016 _cupsLangPuts(stderr
, _("ippfind: Too many parenthesis."));
1020 if ((temp
= new_expr(IPPFIND_OP_AND
, invert
, NULL
, NULL
, NULL
)) == NULL
)
1021 return (IPPFIND_EXIT_MEMORY
);
1023 parens
[num_parens
++] = temp
;
1027 temp
->parent
= current
->parent
;
1028 current
->next
= temp
;
1029 temp
->prev
= current
;
1037 logic
= IPPFIND_OP_AND
;
1039 else if (!strcmp(argv
[i
], ")"))
1041 if (num_parens
<= 0)
1043 _cupsLangPuts(stderr
, _("ippfind: Missing open parenthesis."));
1047 current
= parens
[--num_parens
];
1048 parent
= current
->parent
;
1050 logic
= IPPFIND_OP_AND
;
1052 else if (!strcmp(argv
[i
], "!"))
1059 * _regtype._tcp[,subtype][.domain]
1063 * service-name[._regtype._tcp[.domain]]
1066 cupsArrayAdd(searches
, argv
[i
]);
1072 _cupsLangPuts(stderr
, _("ippfind: Missing close parenthesis."));
1079 * Add an implicit --print-uri to the end...
1082 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, 0, NULL
, NULL
, NULL
)) == NULL
)
1083 return (IPPFIND_EXIT_MEMORY
);
1087 while (current
->parent
)
1088 current
= current
->parent
;
1090 current
->next
= temp
;
1091 temp
->prev
= current
;
1097 if (cupsArrayCount(searches
) == 0)
1100 * Add an implicit browse for IPP printers ("_ipp._tcp")...
1103 cupsArrayAdd(searches
, "_ipp._tcp");
1106 if (getenv("IPPFIND_DEBUG"))
1108 int indent
= 4; /* Indentation */
1110 puts("Expression tree:");
1111 current
= expressions
;
1115 * Print the current node...
1118 printf("%*s%s%s\n", indent
, "", current
->invert
? "!" : "",
1122 * Advance to the next node...
1127 current
= current
->child
;
1130 else if (current
->next
)
1131 current
= current
->next
;
1132 else if (current
->parent
)
1134 while (current
->parent
)
1137 current
= current
->parent
;
1142 current
= current
->next
;
1148 puts("\nSearch items:");
1149 for (search
= (const char *)cupsArrayFirst(searches
);
1151 search
= (const char *)cupsArrayNext(searches
))
1152 printf(" %s\n", search
);
1156 * Start up browsing/resolving...
1160 if ((err
= DNSServiceCreateConnection(&dnssd_ref
)) != kDNSServiceErr_NoError
)
1162 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1163 dnssd_error_string(err
));
1164 return (IPPFIND_EXIT_BONJOUR
);
1167 #elif defined(HAVE_AVAHI)
1168 if ((avahi_poll
= avahi_simple_poll_new()) == NULL
)
1170 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1172 return (IPPFIND_EXIT_BONJOUR
);
1175 avahi_simple_poll_set_func(avahi_poll
, poll_callback
, NULL
);
1177 avahi_client
= avahi_client_new(avahi_simple_poll_get(avahi_poll
),
1178 0, client_callback
, avahi_poll
, &err
);
1181 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1182 dnssd_error_string(err
));
1183 return (IPPFIND_EXIT_BONJOUR
);
1185 #endif /* HAVE_DNSSD */
1187 for (search
= (const char *)cupsArrayFirst(searches
);
1189 search
= (const char *)cupsArrayNext(searches
))
1191 char buf
[1024], /* Full name string */
1192 *name
= NULL
, /* Service instance name */
1193 *regtype
, /* Registration type */
1194 *domain
; /* Domain, if any */
1196 strlcpy(buf
, search
, sizeof(buf
));
1197 if ((regtype
= strstr(buf
, "._")) != NULL
)
1199 if (strcmp(regtype
, "._tcp"))
1202 * "something._protocol._tcp" -> search for something with the given
1212 * "_protocol._tcp" -> search for everything with the given protocol...
1222 * "something" -> search for something with IPP protocol...
1226 regtype
= "_ipp._tcp";
1229 for (domain
= regtype
; *domain
; domain
++)
1231 if (*domain
== '.' && domain
[1] != '_')
1244 * Resolve the given service instance name, regtype, and domain...
1250 service
= get_service(services
, name
, regtype
, domain
);
1253 service
->ref
= dnssd_ref
;
1254 err
= DNSServiceResolve(&(service
->ref
),
1255 kDNSServiceFlagsShareConnection
, 0, name
,
1256 regtype
, domain
, resolve_callback
,
1259 #elif defined(HAVE_AVAHI)
1260 service
->ref
= avahi_service_resolver_new(avahi_client
, AVAHI_IF_UNSPEC
,
1261 AVAHI_PROTO_UNSPEC
, name
,
1263 AVAHI_PROTO_UNSPEC
, 0,
1264 resolve_callback
, service
);
1268 err
= avahi_client_errno(avahi_client
);
1269 #endif /* HAVE_DNSSD */
1274 * Browse for services of the given type...
1278 DNSServiceRef ref
; /* Browse reference */
1281 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
, 0, regtype
,
1282 domain
, browse_callback
, services
);
1287 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
,
1288 kDNSServiceInterfaceIndexLocalOnly
, regtype
,
1289 domain
, browse_local_callback
, services
);
1292 #elif defined(HAVE_AVAHI)
1293 if (avahi_service_browser_new(avahi_client
, AVAHI_IF_UNSPEC
,
1294 AVAHI_PROTO_UNSPEC
, regtype
, domain
, 0,
1295 browse_callback
, services
))
1298 err
= avahi_client_errno(avahi_client
);
1299 #endif /* HAVE_DNSSD */
1304 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
1305 dnssd_error_string(err
));
1307 return (IPPFIND_EXIT_BONJOUR
);
1312 * Process browse/resolve requests...
1315 if (bonjour_timeout
> 1.0)
1316 endtime
= get_time() + bonjour_timeout
;
1318 endtime
= get_time() + 300.0;
1320 while (get_time() < endtime
)
1322 int process
= 0; /* Process services? */
1325 int fd
= DNSServiceRefSockFD(dnssd_ref
);
1326 /* File descriptor for DNS-SD */
1329 FD_SET(fd
, &sinput
);
1331 stimeout
.tv_sec
= 0;
1332 stimeout
.tv_usec
= 500000;
1334 if (select(fd
+ 1, &sinput
, NULL
, NULL
, &stimeout
) < 0)
1337 if (FD_ISSET(fd
, &sinput
))
1340 * Process responses...
1343 DNSServiceProcessResult(dnssd_ref
);
1348 * Time to process services...
1354 #elif defined(HAVE_AVAHI)
1357 if (avahi_simple_poll_iterate(avahi_poll
, 500) > 0)
1360 * We've been told to exit the loop. Perhaps the connection to
1364 return (IPPFIND_EXIT_BONJOUR
);
1367 if (!avahi_got_data
)
1370 * Time to process services...
1375 #endif /* HAVE_DNSSD */
1380 * Process any services that we have found...
1383 int active
= 0, /* Number of active resolves */
1384 resolved
= 0, /* Number of resolved services */
1385 processed
= 0; /* Number of processed services */
1387 for (service
= (ippfind_srv_t
*)cupsArrayFirst(services
);
1389 service
= (ippfind_srv_t
*)cupsArrayNext(services
))
1391 if (service
->is_processed
)
1394 if (service
->is_resolved
)
1397 if (!service
->ref
&& !service
->is_resolved
)
1400 * Found a service, now resolve it (but limit to 50 active resolves...)
1406 service
->ref
= dnssd_ref
;
1407 err
= DNSServiceResolve(&(service
->ref
),
1408 kDNSServiceFlagsShareConnection
, 0,
1409 service
->name
, service
->regtype
,
1410 service
->domain
, resolve_callback
,
1413 #elif defined(HAVE_AVAHI)
1414 service
->ref
= avahi_service_resolver_new(avahi_client
,
1420 AVAHI_PROTO_UNSPEC
, 0,
1426 err
= avahi_client_errno(avahi_client
);
1427 #endif /* HAVE_DNSSD */
1431 _cupsLangPrintf(stderr
,
1432 _("ippfind: Unable to browse or resolve: %s"),
1433 dnssd_error_string(err
));
1434 return (IPPFIND_EXIT_BONJOUR
);
1440 else if (service
->is_resolved
&& !service
->is_processed
)
1443 * Resolved, not process this service against the expressions...
1449 DNSServiceRefDeallocate(service
->ref
);
1451 avahi_service_resolver_free(service
->ref
);
1452 #endif /* HAVE_DNSSD */
1454 service
->ref
= NULL
;
1457 if (eval_expr(service
, expressions
))
1458 status
= IPPFIND_EXIT_TRUE
;
1460 service
->is_processed
= 1;
1462 else if (service
->ref
)
1467 * If we have processed all services we have discovered, then we are done.
1470 if (processed
== cupsArrayCount(services
) && bonjour_timeout
<= 1.0)
1476 return (IPPFIND_EXIT_BONJOUR
);
1484 * 'browse_callback()' - Browse devices.
1487 static void DNSSD_API
1489 DNSServiceRef sdRef
, /* I - Service reference */
1490 DNSServiceFlags flags
, /* I - Option flags */
1491 uint32_t interfaceIndex
, /* I - Interface number */
1492 DNSServiceErrorType errorCode
, /* I - Error, if any */
1493 const char *serviceName
, /* I - Name of service/device */
1494 const char *regtype
, /* I - Type of service */
1495 const char *replyDomain
, /* I - Service domain */
1496 void *context
) /* I - Services array */
1499 * Only process "add" data...
1503 (void)interfaceIndex
;
1505 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1512 get_service((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
1517 * 'browse_local_callback()' - Browse local devices.
1520 static void DNSSD_API
1521 browse_local_callback(
1522 DNSServiceRef sdRef
, /* I - Service reference */
1523 DNSServiceFlags flags
, /* I - Option flags */
1524 uint32_t interfaceIndex
, /* I - Interface number */
1525 DNSServiceErrorType errorCode
, /* I - Error, if any */
1526 const char *serviceName
, /* I - Name of service/device */
1527 const char *regtype
, /* I - Type of service */
1528 const char *replyDomain
, /* I - Service domain */
1529 void *context
) /* I - Services array */
1531 ippfind_srv_t
*service
; /* Service */
1535 * Only process "add" data...
1539 (void)interfaceIndex
;
1541 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1548 service
= get_service((cups_array_t
*)context
, serviceName
, regtype
,
1550 service
->is_local
= 1;
1552 #endif /* HAVE_DNSSD */
1557 * 'browse_callback()' - Browse devices.
1562 AvahiServiceBrowser
*browser
, /* I - Browser */
1563 AvahiIfIndex interface
, /* I - Interface index (unused) */
1564 AvahiProtocol protocol
, /* I - Network protocol (unused) */
1565 AvahiBrowserEvent event
, /* I - What happened */
1566 const char *name
, /* I - Service name */
1567 const char *type
, /* I - Registration type */
1568 const char *domain
, /* I - Domain */
1569 AvahiLookupResultFlags flags
, /* I - Flags */
1570 void *context
) /* I - Services array */
1572 AvahiClient
*client
= avahi_service_browser_get_client(browser
);
1573 /* Client information */
1574 ippfind_srv_t
*service
; /* Service information */
1583 case AVAHI_BROWSER_FAILURE
:
1584 fprintf(stderr
, "DEBUG: browse_callback: %s\n",
1585 avahi_strerror(avahi_client_errno(client
)));
1587 avahi_simple_poll_quit(avahi_poll
);
1590 case AVAHI_BROWSER_NEW
:
1592 * This object is new on the network. Create a device entry for it if
1593 * it doesn't yet exist.
1596 service
= get_service((cups_array_t
*)context
, name
, type
, domain
);
1598 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
1599 service
->is_local
= 1;
1602 case AVAHI_BROWSER_REMOVE
:
1603 case AVAHI_BROWSER_ALL_FOR_NOW
:
1604 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
1611 * 'client_callback()' - Avahi client callback function.
1616 AvahiClient
*client
, /* I - Client information (unused) */
1617 AvahiClientState state
, /* I - Current state */
1618 void *context
) /* I - User data (unused) */
1624 * If the connection drops, quit.
1627 if (state
== AVAHI_CLIENT_FAILURE
)
1629 fputs("DEBUG: Avahi connection failed.\n", stderr
);
1631 avahi_simple_poll_quit(avahi_poll
);
1634 #endif /* HAVE_AVAHI */
1638 * 'compare_services()' - Compare two devices.
1641 static int /* O - Result of comparison */
1642 compare_services(ippfind_srv_t
*a
, /* I - First device */
1643 ippfind_srv_t
*b
) /* I - Second device */
1645 return (strcmp(a
->name
, b
->name
));
1650 * 'dnssd_error_string()' - Return an error string for an error code.
1653 static const char * /* O - Error message */
1654 dnssd_error_string(int error
) /* I - Error number */
1659 case kDNSServiceErr_NoError
:
1663 case kDNSServiceErr_Unknown
:
1664 return ("Unknown error.");
1666 case kDNSServiceErr_NoSuchName
:
1667 return ("Service not found.");
1669 case kDNSServiceErr_NoMemory
:
1670 return ("Out of memory.");
1672 case kDNSServiceErr_BadParam
:
1673 return ("Bad parameter.");
1675 case kDNSServiceErr_BadReference
:
1676 return ("Bad service reference.");
1678 case kDNSServiceErr_BadState
:
1679 return ("Bad state.");
1681 case kDNSServiceErr_BadFlags
:
1682 return ("Bad flags.");
1684 case kDNSServiceErr_Unsupported
:
1685 return ("Unsupported.");
1687 case kDNSServiceErr_NotInitialized
:
1688 return ("Not initialized.");
1690 case kDNSServiceErr_AlreadyRegistered
:
1691 return ("Already registered.");
1693 case kDNSServiceErr_NameConflict
:
1694 return ("Name conflict.");
1696 case kDNSServiceErr_Invalid
:
1697 return ("Invalid name.");
1699 case kDNSServiceErr_Firewall
:
1700 return ("Firewall prevents registration.");
1702 case kDNSServiceErr_Incompatible
:
1703 return ("Client library incompatible.");
1705 case kDNSServiceErr_BadInterfaceIndex
:
1706 return ("Bad interface index.");
1708 case kDNSServiceErr_Refused
:
1709 return ("Server prevents registration.");
1711 case kDNSServiceErr_NoSuchRecord
:
1712 return ("Record not found.");
1714 case kDNSServiceErr_NoAuth
:
1715 return ("Authentication required.");
1717 case kDNSServiceErr_NoSuchKey
:
1718 return ("Encryption key not found.");
1720 case kDNSServiceErr_NATTraversal
:
1721 return ("Unable to traverse NAT boundary.");
1723 case kDNSServiceErr_DoubleNAT
:
1724 return ("Unable to traverse double-NAT boundary.");
1726 case kDNSServiceErr_BadTime
:
1727 return ("Bad system time.");
1729 case kDNSServiceErr_BadSig
:
1730 return ("Bad signature.");
1732 case kDNSServiceErr_BadKey
:
1733 return ("Bad encryption key.");
1735 case kDNSServiceErr_Transient
:
1736 return ("Transient error occurred - please try again.");
1738 case kDNSServiceErr_ServiceNotRunning
:
1739 return ("Server not running.");
1741 case kDNSServiceErr_NATPortMappingUnsupported
:
1742 return ("NAT doesn't support NAT-PMP or UPnP.");
1744 case kDNSServiceErr_NATPortMappingDisabled
:
1745 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1747 case kDNSServiceErr_NoRouter
:
1748 return ("No Internet/default router configured.");
1750 case kDNSServiceErr_PollingMode
:
1751 return ("Service polling mode error.");
1754 case kDNSServiceErr_Timeout
:
1755 return ("Service timeout.");
1759 # elif defined(HAVE_AVAHI)
1760 return (avahi_strerror(error
));
1761 # endif /* HAVE_DNSSD */
1766 * 'eval_expr()' - Evaluate the expressions against the specified service.
1768 * Returns 1 for true and 0 for false.
1771 static int /* O - Result of evaluation */
1772 eval_expr(ippfind_srv_t
*service
, /* I - Service */
1773 ippfind_expr_t
*expressions
) /* I - Expressions */
1775 int logic
, /* Logical operation */
1776 result
; /* Result of current expression */
1777 ippfind_expr_t
*expression
; /* Current expression */
1778 const char *val
; /* TXT value */
1781 * Loop through the expressions...
1784 if (expressions
&& expressions
->parent
)
1785 logic
= expressions
->parent
->op
;
1787 logic
= IPPFIND_OP_AND
;
1789 for (expression
= expressions
; expression
; expression
= expression
->next
)
1791 switch (expression
->op
)
1794 case IPPFIND_OP_AND
:
1795 case IPPFIND_OP_OR
:
1796 if (expression
->child
)
1797 result
= eval_expr(service
, expression
->child
);
1799 result
= expression
->op
== IPPFIND_OP_AND
;
1801 case IPPFIND_OP_TRUE
:
1804 case IPPFIND_OP_FALSE
:
1807 case IPPFIND_OP_IS_LOCAL
:
1808 result
= service
->is_local
;
1810 case IPPFIND_OP_IS_REMOTE
:
1811 result
= !service
->is_local
;
1813 case IPPFIND_OP_DOMAIN_REGEX
:
1814 result
= !regexec(&(expression
->re
), service
->domain
, 0, NULL
, 0);
1816 case IPPFIND_OP_NAME_REGEX
:
1817 result
= !regexec(&(expression
->re
), service
->name
, 0, NULL
, 0);
1819 case IPPFIND_OP_NAME_LITERAL
:
1820 result
= !_cups_strcasecmp(expression
->name
, service
->name
);
1822 case IPPFIND_OP_HOST_REGEX
:
1823 result
= !regexec(&(expression
->re
), service
->host
, 0, NULL
, 0);
1825 case IPPFIND_OP_PORT_RANGE
:
1826 result
= service
->port
>= expression
->range
[0] &&
1827 service
->port
<= expression
->range
[1];
1829 case IPPFIND_OP_PATH_REGEX
:
1830 result
= !regexec(&(expression
->re
), service
->resource
, 0, NULL
, 0);
1832 case IPPFIND_OP_TXT_EXISTS
:
1833 result
= cupsGetOption(expression
->name
, service
->num_txt
,
1834 service
->txt
) != NULL
;
1836 case IPPFIND_OP_TXT_REGEX
:
1837 val
= cupsGetOption(expression
->name
, service
->num_txt
,
1840 result
= !regexec(&(expression
->re
), val
, 0, NULL
, 0);
1844 if (getenv("IPPFIND_DEBUG"))
1845 printf("TXT_REGEX of \"%s\": %d\n", val
, result
);
1847 case IPPFIND_OP_URI_REGEX
:
1848 result
= !regexec(&(expression
->re
), service
->uri
, 0, NULL
, 0);
1850 case IPPFIND_OP_EXEC
:
1851 result
= exec_program(service
, expression
->num_args
,
1854 case IPPFIND_OP_LIST
:
1855 result
= list_service(service
);
1857 case IPPFIND_OP_PRINT_NAME
:
1858 _cupsLangPuts(stdout
, service
->name
);
1861 case IPPFIND_OP_PRINT_URI
:
1862 _cupsLangPuts(stdout
, service
->uri
);
1865 case IPPFIND_OP_QUIET
:
1870 if (expression
->invert
)
1873 if (logic
== IPPFIND_OP_AND
&& !result
)
1875 else if (logic
== IPPFIND_OP_OR
&& result
)
1879 return (logic
== IPPFIND_OP_AND
);
1884 * 'exec_program()' - Execute a program for a service.
1887 static int /* O - 1 if program terminated
1888 successfully, 0 otherwise. */
1889 exec_program(ippfind_srv_t
*service
, /* I - Service */
1890 int num_args
, /* I - Number of command-line args */
1891 char **args
) /* I - Command-line arguments */
1893 char **myargv
, /* Command-line arguments */
1894 **myenvp
, /* Environment variables */
1895 *ptr
, /* Pointer into variable */
1896 domain
[1024], /* IPPFIND_SERVICE_DOMAIN */
1897 hostname
[1024], /* IPPFIND_SERVICE_HOSTNAME */
1898 name
[256], /* IPPFIND_SERVICE_NAME */
1899 port
[32], /* IPPFIND_SERVICE_PORT */
1900 regtype
[256], /* IPPFIND_SERVICE_REGTYPE */
1901 scheme
[128], /* IPPFIND_SERVICE_SCHEME */
1902 uri
[1024], /* IPPFIND_SERVICE_URI */
1903 txt
[100][256]; /* IPPFIND_TXT_foo */
1904 int i
, /* Looping var */
1905 myenvc
, /* Number of environment variables */
1906 status
; /* Exit status of program */
1908 char program
[1024]; /* Program to execute */
1909 int pid
; /* Process ID */
1914 * Environment variables...
1917 snprintf(domain
, sizeof(domain
), "IPPFIND_SERVICE_DOMAIN=%s",
1919 snprintf(hostname
, sizeof(hostname
), "IPPFIND_SERVICE_HOSTNAME=%s",
1921 snprintf(name
, sizeof(name
), "IPPFIND_SERVICE_NAME=%s", service
->name
);
1922 snprintf(port
, sizeof(port
), "IPPFIND_SERVICE_PORT=%d", service
->port
);
1923 snprintf(regtype
, sizeof(regtype
), "IPPFIND_SERVICE_REGTYPE=%s",
1925 snprintf(scheme
, sizeof(scheme
), "IPPFIND_SERVICE_SCHEME=%s",
1926 !strncmp(service
->regtype
, "_http._tcp", 10) ? "http" :
1927 !strncmp(service
->regtype
, "_https._tcp", 11) ? "https" :
1928 !strncmp(service
->regtype
, "_ipp._tcp", 9) ? "ipp" :
1929 !strncmp(service
->regtype
, "_ipps._tcp", 10) ? "ipps" : "lpd");
1930 snprintf(uri
, sizeof(uri
), "IPPFIND_SERVICE_URI=%s", service
->uri
);
1931 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1933 snprintf(txt
[i
], sizeof(txt
[i
]), "IPPFIND_TXT_%s=%s", service
->txt
[i
].name
,
1934 service
->txt
[i
].value
);
1935 for (ptr
= txt
[i
] + 12; *ptr
&& *ptr
!= '='; ptr
++)
1936 *ptr
= (char)_cups_toupper(*ptr
);
1939 for (i
= 0, myenvc
= 7 + service
->num_txt
; environ
[i
]; i
++)
1940 if (strncmp(environ
[i
], "IPPFIND_", 8))
1943 if ((myenvp
= calloc(sizeof(char *), (size_t)(myenvc
+ 1))) == NULL
)
1945 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1946 exit(IPPFIND_EXIT_MEMORY
);
1949 for (i
= 0, myenvc
= 0; environ
[i
]; i
++)
1950 if (strncmp(environ
[i
], "IPPFIND_", 8))
1951 myenvp
[myenvc
++] = environ
[i
];
1953 myenvp
[myenvc
++] = domain
;
1954 myenvp
[myenvc
++] = hostname
;
1955 myenvp
[myenvc
++] = name
;
1956 myenvp
[myenvc
++] = port
;
1957 myenvp
[myenvc
++] = regtype
;
1958 myenvp
[myenvc
++] = scheme
;
1959 myenvp
[myenvc
++] = uri
;
1961 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1962 myenvp
[myenvc
++] = txt
[i
];
1965 * Allocate and copy command-line arguments...
1968 if ((myargv
= calloc(sizeof(char *), (size_t)(num_args
+ 1))) == NULL
)
1970 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1971 exit(IPPFIND_EXIT_MEMORY
);
1974 for (i
= 0; i
< num_args
; i
++)
1976 if (strchr(args
[i
], '{'))
1978 char temp
[2048], /* Temporary string */
1979 *tptr
, /* Pointer into temporary string */
1980 keyword
[256], /* {keyword} */
1981 *kptr
; /* Pointer into keyword */
1983 for (ptr
= args
[i
], tptr
= temp
; *ptr
; ptr
++)
1988 * Do a {var} substitution...
1991 for (kptr
= keyword
, ptr
++; *ptr
&& *ptr
!= '}'; ptr
++)
1992 if (kptr
< (keyword
+ sizeof(keyword
) - 1))
1997 _cupsLangPuts(stderr
,
1998 _("ippfind: Missing close brace in substitution."));
1999 exit(IPPFIND_EXIT_SYNTAX
);
2003 if (!keyword
[0] || !strcmp(keyword
, "service_uri"))
2004 strlcpy(tptr
, service
->uri
, sizeof(temp
) - (size_t)(tptr
- temp
));
2005 else if (!strcmp(keyword
, "service_domain"))
2006 strlcpy(tptr
, service
->domain
, sizeof(temp
) - (size_t)(tptr
- temp
));
2007 else if (!strcmp(keyword
, "service_hostname"))
2008 strlcpy(tptr
, service
->host
, sizeof(temp
) - (size_t)(tptr
- temp
));
2009 else if (!strcmp(keyword
, "service_name"))
2010 strlcpy(tptr
, service
->name
, sizeof(temp
) - (size_t)(tptr
- temp
));
2011 else if (!strcmp(keyword
, "service_path"))
2012 strlcpy(tptr
, service
->resource
, sizeof(temp
) - (size_t)(tptr
- temp
));
2013 else if (!strcmp(keyword
, "service_port"))
2014 strlcpy(tptr
, port
+ 21, sizeof(temp
) - (size_t)(tptr
- temp
));
2015 else if (!strcmp(keyword
, "service_scheme"))
2016 strlcpy(tptr
, scheme
+ 22, sizeof(temp
) - (size_t)(tptr
- temp
));
2017 else if (!strncmp(keyword
, "txt_", 4))
2019 const char *val
= cupsGetOption(keyword
+ 4, service
->num_txt
, service
->txt
);
2021 strlcpy(tptr
, val
, sizeof(temp
) - (size_t)(tptr
- temp
));
2027 _cupsLangPrintf(stderr
, _("ippfind: Unknown variable \"{%s}\"."),
2029 exit(IPPFIND_EXIT_SYNTAX
);
2032 tptr
+= strlen(tptr
);
2034 else if (tptr
< (temp
+ sizeof(temp
) - 1))
2039 myargv
[i
] = strdup(temp
);
2042 myargv
[i
] = strdup(args
[i
]);
2046 if (getenv("IPPFIND_DEBUG"))
2048 printf("\nProgram:\n %s\n", args
[0]);
2049 puts("\nArguments:");
2050 for (i
= 0; i
< num_args
; i
++)
2051 printf(" %s\n", myargv
[i
]);
2052 puts("\nEnvironment:");
2053 for (i
= 0; i
< myenvc
; i
++)
2054 printf(" %s\n", myenvp
[i
]);
2057 status
= _spawnvpe(_P_WAIT
, args
[0], myargv
, myenvp
);
2061 * Execute the program...
2064 if (strchr(args
[0], '/') && !access(args
[0], X_OK
))
2065 strlcpy(program
, args
[0], sizeof(program
));
2066 else if (!cupsFileFind(args
[0], getenv("PATH"), 1, program
, sizeof(program
)))
2068 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2069 args
[0], strerror(ENOENT
));
2070 exit(IPPFIND_EXIT_SYNTAX
);
2073 if (getenv("IPPFIND_DEBUG"))
2075 printf("\nProgram:\n %s\n", program
);
2076 puts("\nArguments:");
2077 for (i
= 0; i
< num_args
; i
++)
2078 printf(" %s\n", myargv
[i
]);
2079 puts("\nEnvironment:");
2080 for (i
= 0; i
< myenvc
; i
++)
2081 printf(" %s\n", myenvp
[i
]);
2084 if ((pid
= fork()) == 0)
2087 * Child comes here...
2090 execve(program
, myargv
, myenvp
);
2095 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2096 args
[0], strerror(errno
));
2097 exit(IPPFIND_EXIT_SYNTAX
);
2102 * Wait for it to complete...
2105 while (wait(&status
) != pid
)
2114 for (i
= 0; i
< num_args
; i
++)
2121 * Return whether the program succeeded or crashed...
2124 if (getenv("IPPFIND_DEBUG"))
2127 printf("Exit Status: %d\n", status
);
2129 if (WIFEXITED(status
))
2130 printf("Exit Status: %d\n", WEXITSTATUS(status
));
2132 printf("Terminating Signal: %d\n", WTERMSIG(status
));
2136 return (status
== 0);
2141 * 'get_service()' - Create or update a device.
2144 static ippfind_srv_t
* /* O - Service */
2145 get_service(cups_array_t
*services
, /* I - Service array */
2146 const char *serviceName
, /* I - Name of service/device */
2147 const char *regtype
, /* I - Type of service */
2148 const char *replyDomain
) /* I - Service domain */
2150 ippfind_srv_t key
, /* Search key */
2151 *service
; /* Service */
2152 char fullName
[kDNSServiceMaxDomainName
];
2153 /* Full name for query */
2157 * See if this is a new device...
2160 key
.name
= (char *)serviceName
;
2161 key
.regtype
= (char *)regtype
;
2163 for (service
= cupsArrayFind(services
, &key
);
2165 service
= cupsArrayNext(services
))
2166 if (_cups_strcasecmp(service
->name
, key
.name
))
2168 else if (!strcmp(service
->regtype
, key
.regtype
))
2172 * Yes, add the service...
2175 service
= calloc(sizeof(ippfind_srv_t
), 1);
2176 service
->name
= strdup(serviceName
);
2177 service
->domain
= strdup(replyDomain
);
2178 service
->regtype
= strdup(regtype
);
2180 cupsArrayAdd(services
, service
);
2183 * Set the "full name" of this service, which is used for queries and
2188 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
2189 #else /* HAVE_AVAHI */
2190 avahi_service_name_join(fullName
, kDNSServiceMaxDomainName
, serviceName
,
2191 regtype
, replyDomain
);
2192 #endif /* HAVE_DNSSD */
2194 service
->fullName
= strdup(fullName
);
2201 * 'get_time()' - Get the current time-of-day in seconds.
2208 struct _timeb curtime
; /* Current Windows time */
2212 return (curtime
.time
+ 0.001 * curtime
.millitm
);
2215 struct timeval curtime
; /* Current UNIX time */
2217 if (gettimeofday(&curtime
, NULL
))
2220 return (curtime
.tv_sec
+ 0.000001 * curtime
.tv_usec
);
2226 * 'list_service()' - List the contents of a service.
2229 static int /* O - 1 if successful, 0 otherwise */
2230 list_service(ippfind_srv_t
*service
) /* I - Service */
2232 http_addrlist_t
*addrlist
; /* Address(es) of service */
2233 char port
[10]; /* Port number of service */
2236 snprintf(port
, sizeof(port
), "%d", service
->port
);
2238 if ((addrlist
= httpAddrGetList(service
->host
, address_family
, port
)) == NULL
)
2240 _cupsLangPrintf(stdout
, "%s unreachable", service
->uri
);
2244 if (!strncmp(service
->regtype
, "_ipp._tcp", 9) ||
2245 !strncmp(service
->regtype
, "_ipps._tcp", 10))
2251 http_t
*http
; /* HTTP connection */
2252 ipp_t
*request
, /* IPP request */
2253 *response
; /* IPP response */
2254 ipp_attribute_t
*attr
; /* IPP attribute */
2255 int i
, /* Looping var */
2256 count
, /* Number of values */
2257 version
, /* IPP version */
2258 paccepting
; /* printer-is-accepting-jobs value */
2259 ipp_pstate_t pstate
; /* printer-state value */
2260 char preasons
[1024], /* Comma-delimited printer-state-reasons */
2261 *ptr
, /* Pointer into reasons */
2262 *end
; /* End of reasons buffer */
2263 static const char * const rattrs
[] =/* Requested attributes */
2265 "printer-is-accepting-jobs",
2267 "printer-state-reasons"
2271 * Connect to the printer...
2274 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2275 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2276 HTTP_ENCRYPTION_ALWAYS
:
2277 HTTP_ENCRYPTION_IF_REQUESTED
,
2280 httpAddrFreeList(addrlist
);
2284 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2289 * Get the current printer state...
2293 version
= ipp_version
;
2297 request
= ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES
);
2298 ippSetVersion(request
, version
/ 10, version
% 10);
2299 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri", NULL
,
2301 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
2302 "requesting-user-name", NULL
, cupsUser());
2303 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
2304 "requested-attributes",
2305 (int)(sizeof(rattrs
) / sizeof(rattrs
[0])), NULL
, rattrs
);
2307 response
= cupsDoRequest(http
, request
, service
->resource
);
2309 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST
&& version
> 11)
2312 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
&& version
> 11);
2318 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
)
2320 _cupsLangPrintf(stdout
, "%s: unavailable", service
->uri
);
2324 if ((attr
= ippFindAttribute(response
, "printer-state",
2325 IPP_TAG_ENUM
)) != NULL
)
2326 pstate
= (ipp_pstate_t
)ippGetInteger(attr
, 0);
2328 pstate
= IPP_PSTATE_STOPPED
;
2330 if ((attr
= ippFindAttribute(response
, "printer-is-accepting-jobs",
2331 IPP_TAG_BOOLEAN
)) != NULL
)
2332 paccepting
= ippGetBoolean(attr
, 0);
2336 if ((attr
= ippFindAttribute(response
, "printer-state-reasons",
2337 IPP_TAG_KEYWORD
)) != NULL
)
2339 strlcpy(preasons
, ippGetString(attr
, 0, NULL
), sizeof(preasons
));
2341 for (i
= 1, count
= ippGetCount(attr
), ptr
= preasons
+ strlen(preasons
),
2342 end
= preasons
+ sizeof(preasons
) - 1;
2343 i
< count
&& ptr
< end
;
2344 i
++, ptr
+= strlen(ptr
))
2347 strlcpy(ptr
, ippGetString(attr
, i
, NULL
), (size_t)(end
- ptr
+ 1));
2351 strlcpy(preasons
, "none", sizeof(preasons
));
2353 ippDelete(response
);
2356 _cupsLangPrintf(stdout
, "%s %s %s %s", service
->uri
,
2357 ippEnumString("printer-state", pstate
),
2358 paccepting
? "accepting-jobs" : "not-accepting-jobs",
2361 else if (!strncmp(service
->regtype
, "_http._tcp", 10) ||
2362 !strncmp(service
->regtype
, "_https._tcp", 11))
2365 * HTTP/HTTPS web page
2368 http_t
*http
; /* HTTP connection */
2369 http_status_t status
; /* HEAD status */
2373 * Connect to the web server...
2376 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2377 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2378 HTTP_ENCRYPTION_ALWAYS
:
2379 HTTP_ENCRYPTION_IF_REQUESTED
,
2382 httpAddrFreeList(addrlist
);
2386 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2390 if (httpGet(http
, service
->resource
))
2392 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2398 status
= httpUpdate(http
);
2400 while (status
== HTTP_STATUS_CONTINUE
);
2405 if (status
>= HTTP_STATUS_BAD_REQUEST
)
2407 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2411 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2413 else if (!strncmp(service
->regtype
, "_printer._tcp", 13))
2419 int sock
; /* Socket */
2422 if (!httpAddrConnect(addrlist
, &sock
))
2424 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2425 httpAddrFreeList(addrlist
);
2429 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2430 httpAddrFreeList(addrlist
);
2432 httpAddrClose(NULL
, sock
);
2436 _cupsLangPrintf(stdout
, "%s unsupported", service
->uri
);
2437 httpAddrFreeList(addrlist
);
2446 * 'new_expr()' - Create a new expression.
2449 static ippfind_expr_t
* /* O - New expression */
2450 new_expr(ippfind_op_t op
, /* I - Operation */
2451 int invert
, /* I - Invert result? */
2452 const char *value
, /* I - TXT key or port range */
2453 const char *regex
, /* I - Regular expression */
2454 char **args
) /* I - Pointer to argument strings */
2456 ippfind_expr_t
*temp
; /* New expression */
2459 if ((temp
= calloc(1, sizeof(ippfind_expr_t
))) == NULL
)
2463 temp
->invert
= invert
;
2465 if (op
== IPPFIND_OP_TXT_EXISTS
|| op
== IPPFIND_OP_TXT_REGEX
|| op
== IPPFIND_OP_NAME_LITERAL
)
2466 temp
->name
= (char *)value
;
2467 else if (op
== IPPFIND_OP_PORT_RANGE
)
2470 * Pull port number range of the form "number", "-number" (0-number),
2471 * "number-" (number-65535), and "number-number".
2476 temp
->range
[1] = atoi(value
+ 1);
2478 else if (strchr(value
, '-'))
2480 if (sscanf(value
, "%d-%d", temp
->range
, temp
->range
+ 1) == 1)
2481 temp
->range
[1] = 65535;
2485 temp
->range
[0] = temp
->range
[1] = atoi(value
);
2491 int err
= regcomp(&(temp
->re
), regex
, REG_NOSUB
| REG_ICASE
| REG_EXTENDED
);
2495 char message
[256]; /* Error message */
2497 regerror(err
, &(temp
->re
), message
, sizeof(message
));
2498 _cupsLangPrintf(stderr
, _("ippfind: Bad regular expression: %s"),
2500 exit(IPPFIND_EXIT_SYNTAX
);
2506 int num_args
; /* Number of arguments */
2508 for (num_args
= 1; args
[num_args
]; num_args
++)
2509 if (!strcmp(args
[num_args
], ";"))
2512 temp
->num_args
= num_args
;
2513 temp
->args
= malloc((size_t)num_args
* sizeof(char *));
2514 memcpy(temp
->args
, args
, (size_t)num_args
* sizeof(char *));
2523 * 'poll_callback()' - Wait for input on the specified file descriptors.
2525 * Note: This function is needed because avahi_simple_poll_iterate is broken
2526 * and always uses a timeout of 0 (!) milliseconds.
2527 * (Avahi Ticket #364)
2530 static int /* O - Number of file descriptors matching */
2532 struct pollfd
*pollfds
, /* I - File descriptors */
2533 unsigned int num_pollfds
, /* I - Number of file descriptors */
2534 int timeout
, /* I - Timeout in milliseconds (unused) */
2535 void *context
) /* I - User data (unused) */
2537 int val
; /* Return value */
2543 val
= poll(pollfds
, num_pollfds
, 500);
2550 #endif /* HAVE_AVAHI */
2554 * 'resolve_callback()' - Process resolve data.
2558 static void DNSSD_API
2560 DNSServiceRef sdRef
, /* I - Service reference */
2561 DNSServiceFlags flags
, /* I - Data flags */
2562 uint32_t interfaceIndex
, /* I - Interface */
2563 DNSServiceErrorType errorCode
, /* I - Error, if any */
2564 const char *fullName
, /* I - Full service name */
2565 const char *hostTarget
, /* I - Hostname */
2566 uint16_t port
, /* I - Port number (network byte order) */
2567 uint16_t txtLen
, /* I - Length of TXT record data */
2568 const unsigned char *txtRecord
, /* I - TXT record data */
2569 void *context
) /* I - Service */
2571 char key
[256], /* TXT key value */
2572 *value
; /* Value from TXT record */
2573 const unsigned char *txtEnd
; /* End of TXT record */
2574 uint8_t valueLen
; /* Length of value */
2575 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2580 * Only process "add" data...
2585 (void)interfaceIndex
;
2588 if (errorCode
!= kDNSServiceErr_NoError
)
2590 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
2591 dnssd_error_string(errorCode
));
2596 service
->is_resolved
= 1;
2597 service
->host
= strdup(hostTarget
);
2598 service
->port
= ntohs(port
);
2600 value
= service
->host
+ strlen(service
->host
) - 1;
2601 if (value
>= service
->host
&& *value
== '.')
2605 * Loop through the TXT key/value pairs and add them to an array...
2608 for (txtEnd
= txtRecord
+ txtLen
; txtRecord
< txtEnd
; txtRecord
+= valueLen
)
2611 * Ignore bogus strings...
2614 valueLen
= *txtRecord
++;
2616 memcpy(key
, txtRecord
, valueLen
);
2617 key
[valueLen
] = '\0';
2619 if ((value
= strchr(key
, '=')) == NULL
)
2625 * Add to array of TXT values...
2628 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2632 set_service_uri(service
);
2636 #elif defined(HAVE_AVAHI)
2639 AvahiServiceResolver
*resolver
, /* I - Resolver */
2640 AvahiIfIndex interface
, /* I - Interface */
2641 AvahiProtocol protocol
, /* I - Address protocol */
2642 AvahiResolverEvent event
, /* I - Event */
2643 const char *serviceName
,/* I - Service name */
2644 const char *regtype
, /* I - Registration type */
2645 const char *replyDomain
,/* I - Domain name */
2646 const char *hostTarget
, /* I - FQDN */
2647 const AvahiAddress
*address
, /* I - Address */
2648 uint16_t port
, /* I - Port number */
2649 AvahiStringList
*txt
, /* I - TXT records */
2650 AvahiLookupResultFlags flags
, /* I - Lookup flags */
2651 void *context
) /* I - Service */
2653 char key
[256], /* TXT key */
2654 *value
; /* TXT value */
2655 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2657 AvahiStringList
*current
; /* Current TXT key/value pair */
2662 if (event
!= AVAHI_RESOLVER_FOUND
)
2666 avahi_service_resolver_free(resolver
);
2667 avahi_simple_poll_quit(avahi_poll
);
2671 service
->is_resolved
= 1;
2672 service
->host
= strdup(hostTarget
);
2673 service
->port
= port
;
2675 value
= service
->host
+ strlen(service
->host
) - 1;
2676 if (value
>= service
->host
&& *value
== '.')
2680 * Loop through the TXT key/value pairs and add them to an array...
2683 for (current
= txt
; current
; current
= current
->next
)
2686 * Ignore bogus strings...
2689 if (current
->size
> (sizeof(key
) - 1))
2692 memcpy(key
, current
->text
, current
->size
);
2693 key
[current
->size
] = '\0';
2695 if ((value
= strchr(key
, '=')) == NULL
)
2701 * Add to array of TXT values...
2704 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2708 set_service_uri(service
);
2710 #endif /* HAVE_DNSSD */
2714 * 'set_service_uri()' - Set the URI of the service.
2718 set_service_uri(ippfind_srv_t
*service
) /* I - Service */
2720 char uri
[1024]; /* URI */
2721 const char *path
, /* Resource path */
2722 *scheme
; /* URI scheme */
2725 if (!strncmp(service
->regtype
, "_http.", 6))
2728 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2730 else if (!strncmp(service
->regtype
, "_https.", 7))
2733 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2735 else if (!strncmp(service
->regtype
, "_ipp.", 5))
2738 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2740 else if (!strncmp(service
->regtype
, "_ipps.", 6))
2743 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2745 else if (!strncmp(service
->regtype
, "_printer.", 9))
2748 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2753 if (!path
|| !*path
)
2758 service
->resource
= strdup(path
);
2762 snprintf(uri
, sizeof(uri
), "/%s", path
);
2763 service
->resource
= strdup(uri
);
2766 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
,
2767 service
->host
, service
->port
, service
->resource
);
2768 service
->uri
= strdup(uri
);
2773 * 'show_usage()' - Show program usage.
2779 _cupsLangPuts(stderr
, _("Usage: ippfind [options] regtype[,subtype]"
2780 "[.domain.] ... [expression]\n"
2781 " ippfind [options] name[.regtype[.domain.]] "
2782 "... [expression]\n"
2784 " ippfind --version"));
2785 _cupsLangPuts(stderr
, "");
2786 _cupsLangPuts(stderr
, _("Options:"));
2787 _cupsLangPuts(stderr
, _(" -4 Connect using IPv4."));
2788 _cupsLangPuts(stderr
, _(" -6 Connect using IPv6."));
2789 _cupsLangPuts(stderr
, _(" -T seconds Set the browse timeout in "
2791 _cupsLangPuts(stderr
, _(" -V version Set default IPP "
2793 _cupsLangPuts(stderr
, _(" --help Show this help."));
2794 _cupsLangPuts(stderr
, _(" --version Show program version."));
2795 _cupsLangPuts(stderr
, "");
2796 _cupsLangPuts(stderr
, _("Expressions:"));
2797 _cupsLangPuts(stderr
, _(" -P number[-number] Match port to number or range."));
2798 _cupsLangPuts(stderr
, _(" -d regex Match domain to regular expression."));
2799 _cupsLangPuts(stderr
, _(" -h regex Match hostname to regular expression."));
2800 _cupsLangPuts(stderr
, _(" -l List attributes."));
2801 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
2802 _cupsLangPuts(stderr
, _(" -p Print URI if true."));
2803 _cupsLangPuts(stderr
, _(" -q Quietly report match via exit code."));
2804 _cupsLangPuts(stderr
, _(" -r True if service is remote."));
2805 _cupsLangPuts(stderr
, _(" -s Print service name if true."));
2806 _cupsLangPuts(stderr
, _(" -t key True if the TXT record contains the key."));
2807 _cupsLangPuts(stderr
, _(" -u regex Match URI to regular expression."));
2808 _cupsLangPuts(stderr
, _(" -x utility [argument ...] ;\n"
2809 " Execute program if true."));
2810 _cupsLangPuts(stderr
, _(" --domain regex Match domain to regular expression."));
2811 _cupsLangPuts(stderr
, _(" --exec utility [argument ...] ;\n"
2812 " Execute program if true."));
2813 _cupsLangPuts(stderr
, _(" --host regex Match hostname to regular expression."));
2814 _cupsLangPuts(stderr
, _(" --ls List attributes."));
2815 _cupsLangPuts(stderr
, _(" --local True if service is local."));
2816 _cupsLangPuts(stderr
, _(" --name regex Match service name to regular expression."));
2817 _cupsLangPuts(stderr
, _(" --path regex Match resource path to regular expression."));
2818 _cupsLangPuts(stderr
, _(" --port number[-number] Match port to number or range."));
2819 _cupsLangPuts(stderr
, _(" --print Print URI if true."));
2820 _cupsLangPuts(stderr
, _(" --print-name Print service name if true."));
2821 _cupsLangPuts(stderr
, _(" --quiet Quietly report match via exit code."));
2822 _cupsLangPuts(stderr
, _(" --remote True if service is remote."));
2823 _cupsLangPuts(stderr
, _(" --txt key True if the TXT record contains the key."));
2824 _cupsLangPuts(stderr
, _(" --txt-* regex Match TXT record key to regular expression."));
2825 _cupsLangPuts(stderr
, _(" --uri regex Match URI to regular expression."));
2826 _cupsLangPuts(stderr
, "");
2827 _cupsLangPuts(stderr
, _("Modifiers:"));
2828 _cupsLangPuts(stderr
, _(" ( expressions ) Group expressions."));
2829 _cupsLangPuts(stderr
, _(" ! expression Unary NOT of expression."));
2830 _cupsLangPuts(stderr
, _(" --not expression Unary NOT of expression."));
2831 _cupsLangPuts(stderr
, _(" --false Always false."));
2832 _cupsLangPuts(stderr
, _(" --true Always true."));
2833 _cupsLangPuts(stderr
, _(" expression expression Logical AND."));
2834 _cupsLangPuts(stderr
, _(" expression --and expression\n"
2836 _cupsLangPuts(stderr
, _(" expression --or expression\n"
2838 _cupsLangPuts(stderr
, "");
2839 _cupsLangPuts(stderr
, _("Substitutions:"));
2840 _cupsLangPuts(stderr
, _(" {} URI"));
2841 _cupsLangPuts(stderr
, _(" {service_domain} Domain name"));
2842 _cupsLangPuts(stderr
, _(" {service_hostname} Fully-qualified domain name"));
2843 _cupsLangPuts(stderr
, _(" {service_name} Service instance name"));
2844 _cupsLangPuts(stderr
, _(" {service_port} Port number"));
2845 _cupsLangPuts(stderr
, _(" {service_regtype} DNS-SD registration type"));
2846 _cupsLangPuts(stderr
, _(" {service_scheme} URI scheme"));
2847 _cupsLangPuts(stderr
, _(" {service_uri} URI"));
2848 _cupsLangPuts(stderr
, _(" {txt_*} Value of TXT record key"));
2849 _cupsLangPuts(stderr
, "");
2850 _cupsLangPuts(stderr
, _("Environment Variables:"));
2851 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_DOMAIN Domain name"));
2852 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_HOSTNAME\n"
2853 " Fully-qualified domain name"));
2854 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_NAME Service instance name"));
2855 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_PORT Port number"));
2856 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
2857 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_SCHEME URI scheme"));
2858 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_URI URI"));
2859 _cupsLangPuts(stderr
, _(" IPPFIND_TXT_* Value of TXT record key"));
2861 exit(IPPFIND_EXIT_TRUE
);
2866 * 'show_version()' - Show program version.
2872 _cupsLangPuts(stderr
, CUPS_SVERSION
);
2874 exit(IPPFIND_EXIT_TRUE
);