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-2015 by Apple Inc.
8 * These coded instructions, statements, and computer programs are the
9 * property of Apple Inc. and are protected by Federal copyright
10 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
11 * which should have been included with this file. If this file is
12 * missing or damaged, see the license at "http://www.cups.org/".
14 * This file is subject to the Apple OS-Developed Software exception.
18 * Include necessary headers.
21 #define _CUPS_NO_DEPRECATED
22 #include <cups/cups-private.h>
25 # include <sys/timeb.h>
27 # include <sys/wait.h>
32 #elif defined(HAVE_AVAHI)
33 # include <avahi-client/client.h>
34 # include <avahi-client/lookup.h>
35 # include <avahi-common/simple-watch.h>
36 # include <avahi-common/domain.h>
37 # include <avahi-common/error.h>
38 # include <avahi-common/malloc.h>
39 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
40 #endif /* HAVE_DNSSD */
43 extern char **environ
; /* Process environment variables */
51 typedef enum ippfind_exit_e
/* Exit codes */
53 IPPFIND_EXIT_TRUE
= 0, /* OK and result is true */
54 IPPFIND_EXIT_FALSE
, /* OK but result is false*/
55 IPPFIND_EXIT_BONJOUR
, /* Browse/resolve failure */
56 IPPFIND_EXIT_SYNTAX
, /* Bad option or syntax error */
57 IPPFIND_EXIT_MEMORY
/* Out of memory */
60 typedef enum ippfind_op_e
/* Operations for expressions */
62 /* "Evaluation" operations */
63 IPPFIND_OP_NONE
, /* No operation */
64 IPPFIND_OP_AND
, /* Logical AND of all children */
65 IPPFIND_OP_OR
, /* Logical OR of all children */
66 IPPFIND_OP_TRUE
, /* Always true */
67 IPPFIND_OP_FALSE
, /* Always false */
68 IPPFIND_OP_IS_LOCAL
, /* Is a local service */
69 IPPFIND_OP_IS_REMOTE
, /* Is a remote service */
70 IPPFIND_OP_DOMAIN_REGEX
, /* Domain matches regular expression */
71 IPPFIND_OP_NAME_REGEX
, /* Name matches regular expression */
72 IPPFIND_OP_HOST_REGEX
, /* Hostname matches regular expression */
73 IPPFIND_OP_PORT_RANGE
, /* Port matches range */
74 IPPFIND_OP_PATH_REGEX
, /* Path matches regular expression */
75 IPPFIND_OP_TXT_EXISTS
, /* TXT record key exists */
76 IPPFIND_OP_TXT_REGEX
, /* TXT record key matches regular expression */
77 IPPFIND_OP_URI_REGEX
, /* URI matches regular expression */
79 /* "Output" operations */
80 IPPFIND_OP_EXEC
, /* Execute when true */
81 IPPFIND_OP_LIST
, /* List when true */
82 IPPFIND_OP_PRINT_NAME
, /* Print URI when true */
83 IPPFIND_OP_PRINT_URI
, /* Print name when true */
84 IPPFIND_OP_QUIET
/* No output when true */
87 typedef struct ippfind_expr_s
/* Expression */
90 *prev
, /* Previous expression */
91 *next
, /* Next expression */
92 *parent
, /* Parent expressions */
93 *child
; /* Child expressions */
94 ippfind_op_t op
; /* Operation code (see above) */
95 int invert
; /* Invert the result */
96 char *key
; /* TXT record key */
97 regex_t re
; /* Regular expression for matching */
98 int range
[2]; /* Port number range */
99 int num_args
; /* Number of arguments for exec */
100 char **args
; /* Arguments for exec */
103 typedef struct ippfind_srv_s
/* Service information */
106 DNSServiceRef ref
; /* Service reference for query */
107 #elif defined(HAVE_AVAHI)
108 AvahiServiceResolver
*ref
; /* Resolver */
109 #endif /* HAVE_DNSSD */
110 char *name
, /* Service name */
111 *domain
, /* Domain name */
112 *regtype
, /* Registration type */
113 *fullName
, /* Full name */
114 *host
, /* Hostname */
115 *resource
, /* Resource path */
117 int num_txt
; /* Number of TXT record keys */
118 cups_option_t
*txt
; /* TXT record keys */
119 int port
, /* Port number */
120 is_local
, /* Is a local service? */
121 is_processed
, /* Did we process the service? */
122 is_resolved
; /* Got the resolve data? */
131 static DNSServiceRef dnssd_ref
; /* Master service reference */
132 #elif defined(HAVE_AVAHI)
133 static AvahiClient
*avahi_client
= NULL
;/* Client information */
134 static int avahi_got_data
= 0; /* Got data from poll? */
135 static AvahiSimplePoll
*avahi_poll
= NULL
;
136 /* Poll information */
137 #endif /* HAVE_DNSSD */
139 static int address_family
= AF_UNSPEC
;
140 /* Address family for LIST */
141 static int bonjour_error
= 0; /* Error browsing/resolving? */
142 static double bonjour_timeout
= 1.0; /* Timeout in seconds */
143 static int ipp_version
= 20; /* IPP version for LIST */
151 static void DNSSD_API
browse_callback(DNSServiceRef sdRef
,
152 DNSServiceFlags flags
,
153 uint32_t interfaceIndex
,
154 DNSServiceErrorType errorCode
,
155 const char *serviceName
,
157 const char *replyDomain
, void *context
)
158 __attribute__((nonnull(1,5,6,7,8)));
159 static void DNSSD_API
browse_local_callback(DNSServiceRef sdRef
,
160 DNSServiceFlags flags
,
161 uint32_t interfaceIndex
,
162 DNSServiceErrorType errorCode
,
163 const char *serviceName
,
165 const char *replyDomain
,
167 __attribute__((nonnull(1,5,6,7,8)));
168 #elif defined(HAVE_AVAHI)
169 static void browse_callback(AvahiServiceBrowser
*browser
,
170 AvahiIfIndex interface
,
171 AvahiProtocol protocol
,
172 AvahiBrowserEvent event
,
173 const char *serviceName
,
175 const char *replyDomain
,
176 AvahiLookupResultFlags flags
,
178 static void client_callback(AvahiClient
*client
,
179 AvahiClientState state
,
181 #endif /* HAVE_AVAHI */
183 static int compare_services(ippfind_srv_t
*a
, ippfind_srv_t
*b
);
184 static const char *dnssd_error_string(int error
);
185 static int eval_expr(ippfind_srv_t
*service
,
186 ippfind_expr_t
*expressions
);
187 static int exec_program(ippfind_srv_t
*service
, int num_args
,
189 static ippfind_srv_t
*get_service(cups_array_t
*services
,
190 const char *serviceName
,
192 const char *replyDomain
)
193 __attribute__((nonnull(1,2,3,4)));
194 static double get_time(void);
195 static int list_service(ippfind_srv_t
*service
);
196 static ippfind_expr_t
*new_expr(ippfind_op_t op
, int invert
,
197 const char *value
, const char *regex
,
200 static void DNSSD_API
resolve_callback(DNSServiceRef sdRef
,
201 DNSServiceFlags flags
,
202 uint32_t interfaceIndex
,
203 DNSServiceErrorType errorCode
,
204 const char *fullName
,
205 const char *hostTarget
, uint16_t port
,
207 const unsigned char *txtRecord
,
209 __attribute__((nonnull(1,5,6,9, 10)));
210 #elif defined(HAVE_AVAHI)
211 static int poll_callback(struct pollfd
*pollfds
,
212 unsigned int num_pollfds
, int timeout
,
214 static void resolve_callback(AvahiServiceResolver
*res
,
215 AvahiIfIndex interface
,
216 AvahiProtocol protocol
,
217 AvahiResolverEvent event
,
218 const char *serviceName
,
220 const char *replyDomain
,
221 const char *host_name
,
222 const AvahiAddress
*address
,
224 AvahiStringList
*txt
,
225 AvahiLookupResultFlags flags
,
227 #endif /* HAVE_DNSSD */
228 static void set_service_uri(ippfind_srv_t
*service
);
229 static void show_usage(void) __attribute__((noreturn
));
230 static void show_version(void) __attribute__((noreturn
));
234 * 'main()' - Browse for printers.
237 int /* O - Exit status */
238 main(int argc
, /* I - Number of command-line args */
239 char *argv
[]) /* I - Command-line arguments */
241 int i
, /* Looping var */
242 have_output
= 0,/* Have output expression */
243 status
= IPPFIND_EXIT_FALSE
;
245 const char *opt
, /* Option character */
246 *search
; /* Current browse/resolve string */
247 cups_array_t
*searches
; /* Things to browse/resolve */
248 cups_array_t
*services
; /* Service array */
249 ippfind_srv_t
*service
; /* Current service */
250 ippfind_expr_t
*expressions
= NULL
,
251 /* Expression tree */
252 *temp
= NULL
, /* New expression */
253 *parent
= NULL
, /* Parent expression */
254 *current
= NULL
,/* Current expression */
255 *parens
[100]; /* Markers for parenthesis */
256 int num_parens
= 0; /* Number of parenthesis */
257 ippfind_op_t logic
= IPPFIND_OP_AND
;
258 /* Logic for next expression */
259 int invert
= 0; /* Invert expression? */
260 int err
; /* DNS-SD error */
262 fd_set sinput
; /* Input set for select() */
263 struct timeval stimeout
; /* Timeout for select() */
264 #endif /* HAVE_DNSSD */
265 double endtime
; /* End time */
266 static const char * const ops
[] = /* Node operation names */
292 * Initialize the locale...
295 _cupsSetLocale(argv
);
298 * Create arrays to track services and things we want to browse/resolve...
301 searches
= cupsArrayNew(NULL
, NULL
);
302 services
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
305 * Parse command-line...
308 if (getenv("IPPFIND_DEBUG"))
309 for (i
= 1; i
< argc
; i
++)
310 fprintf(stderr
, "argv[%d]=\"%s\"\n", i
, argv
[i
]);
312 for (i
= 1; i
< argc
; i
++)
314 if (argv
[i
][0] == '-')
316 if (argv
[i
][1] == '-')
319 * Parse --option options...
322 if (!strcmp(argv
[i
], "--and"))
324 if (logic
== IPPFIND_OP_OR
)
326 _cupsLangPuts(stderr
, _("ippfind: Cannot use --and after --or."));
332 _cupsLangPuts(stderr
,
333 _("ippfind: Missing expression before \"--and\"."));
339 else if (!strcmp(argv
[i
], "--domain"))
344 _cupsLangPrintf(stderr
,
345 _("ippfind: Missing regular expression after %s."),
350 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
, argv
[i
],
352 return (IPPFIND_EXIT_MEMORY
);
354 else if (!strcmp(argv
[i
], "--exec"))
359 _cupsLangPrintf(stderr
, _("ippfind: Expected program after %s."),
364 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
366 return (IPPFIND_EXIT_MEMORY
);
369 if (!strcmp(argv
[i
], ";"))
376 _cupsLangPrintf(stderr
, _("ippfind: Expected semi-colon after %s."),
383 else if (!strcmp(argv
[i
], "--false"))
385 if ((temp
= new_expr(IPPFIND_OP_FALSE
, invert
, NULL
, NULL
,
387 return (IPPFIND_EXIT_MEMORY
);
389 else if (!strcmp(argv
[i
], "--help"))
393 else if (!strcmp(argv
[i
], "--host"))
398 _cupsLangPrintf(stderr
,
399 _("ippfind: Missing regular expression after %s."),
404 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
, argv
[i
],
406 return (IPPFIND_EXIT_MEMORY
);
408 else if (!strcmp(argv
[i
], "--ls"))
410 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
412 return (IPPFIND_EXIT_MEMORY
);
416 else if (!strcmp(argv
[i
], "--local"))
418 if ((temp
= new_expr(IPPFIND_OP_IS_LOCAL
, invert
, NULL
, NULL
,
420 return (IPPFIND_EXIT_MEMORY
);
422 else if (!strcmp(argv
[i
], "--name"))
427 _cupsLangPrintf(stderr
,
428 _("ippfind: Missing regular expression after %s."),
433 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
, argv
[i
],
435 return (IPPFIND_EXIT_MEMORY
);
437 else if (!strcmp(argv
[i
], "--not"))
441 else if (!strcmp(argv
[i
], "--or"))
445 _cupsLangPuts(stderr
,
446 _("ippfind: Missing expression before \"--or\"."));
450 logic
= IPPFIND_OP_OR
;
452 if (parent
&& parent
->op
== IPPFIND_OP_OR
)
455 * Already setup to do "foo --or bar --or baz"...
460 else if (!current
->prev
&& parent
)
463 * Change parent node into an OR node...
466 parent
->op
= IPPFIND_OP_OR
;
469 else if (!current
->prev
)
472 * Need to group "current" in a new OR node...
475 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
477 return (IPPFIND_EXIT_MEMORY
);
479 temp
->parent
= parent
;
480 temp
->child
= current
;
481 current
->parent
= temp
;
484 parent
->child
= temp
;
494 * Need to group previous expressions in an AND node, and then
495 * put that in an OR node...
498 if ((temp
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
500 return (IPPFIND_EXIT_MEMORY
);
502 while (current
->prev
)
504 current
->parent
= temp
;
505 current
= current
->prev
;
508 current
->parent
= temp
;
509 temp
->child
= current
;
512 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
514 return (IPPFIND_EXIT_MEMORY
);
516 temp
->parent
= parent
;
517 current
->parent
= temp
;
520 parent
->child
= temp
;
528 else if (!strcmp(argv
[i
], "--path"))
533 _cupsLangPrintf(stderr
,
534 _("ippfind: Missing regular expression after %s."),
539 if ((temp
= new_expr(IPPFIND_OP_PATH_REGEX
, invert
, NULL
, argv
[i
],
541 return (IPPFIND_EXIT_MEMORY
);
543 else if (!strcmp(argv
[i
], "--port"))
548 _cupsLangPrintf(stderr
,
549 _("ippfind: Expected port range after %s."),
554 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
], NULL
,
556 return (IPPFIND_EXIT_MEMORY
);
558 else if (!strcmp(argv
[i
], "--print"))
560 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
562 return (IPPFIND_EXIT_MEMORY
);
566 else if (!strcmp(argv
[i
], "--print-name"))
568 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
570 return (IPPFIND_EXIT_MEMORY
);
574 else if (!strcmp(argv
[i
], "--quiet"))
576 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
578 return (IPPFIND_EXIT_MEMORY
);
582 else if (!strcmp(argv
[i
], "--remote"))
584 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
586 return (IPPFIND_EXIT_MEMORY
);
588 else if (!strcmp(argv
[i
], "--true"))
590 if ((temp
= new_expr(IPPFIND_OP_TRUE
, invert
, NULL
, argv
[i
],
592 return (IPPFIND_EXIT_MEMORY
);
594 else if (!strcmp(argv
[i
], "--txt"))
599 _cupsLangPrintf(stderr
, _("ippfind: Expected key name after %s."),
604 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
], NULL
,
606 return (IPPFIND_EXIT_MEMORY
);
608 else if (!strncmp(argv
[i
], "--txt-", 6))
610 const char *key
= argv
[i
] + 6;/* TXT key */
615 _cupsLangPrintf(stderr
,
616 _("ippfind: Missing regular expression after %s."),
621 if ((temp
= new_expr(IPPFIND_OP_TXT_REGEX
, invert
, key
, argv
[i
],
623 return (IPPFIND_EXIT_MEMORY
);
625 else if (!strcmp(argv
[i
], "--uri"))
630 _cupsLangPrintf(stderr
,
631 _("ippfind: Missing regular expression after %s."),
636 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
, argv
[i
],
638 return (IPPFIND_EXIT_MEMORY
);
640 else if (!strcmp(argv
[i
], "--version"))
646 _cupsLangPrintf(stderr
, _("%s: Unknown option \"%s\"."),
654 * Add new expression...
657 if (logic
== IPPFIND_OP_AND
&&
658 current
&& current
->prev
&&
659 parent
&& parent
->op
!= IPPFIND_OP_AND
)
662 * Need to re-group "current" in a new AND node...
665 ippfind_expr_t
*tempand
; /* Temporary AND node */
667 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
669 return (IPPFIND_EXIT_MEMORY
);
672 * Replace "current" with new AND node at the end of this list...
675 current
->prev
->next
= tempand
;
676 tempand
->prev
= current
->prev
;
677 tempand
->parent
= parent
;
680 * Add "current to the new AND node...
683 tempand
->child
= current
;
684 current
->parent
= tempand
;
685 current
->prev
= NULL
;
690 * Add the new node at current level...
693 temp
->parent
= parent
;
694 temp
->prev
= current
;
697 current
->next
= temp
;
699 parent
->child
= temp
;
705 logic
= IPPFIND_OP_AND
;
715 for (opt
= argv
[i
] + 1; *opt
; opt
++)
720 address_family
= AF_INET
;
724 address_family
= AF_INET6
;
731 _cupsLangPrintf(stderr
,
732 _("ippfind: Expected port range after %s."),
737 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
],
738 NULL
, NULL
)) == NULL
)
739 return (IPPFIND_EXIT_MEMORY
);
746 _cupsLangPrintf(stderr
,
747 _("%s: Missing timeout for \"-T\"."),
752 bonjour_timeout
= atof(argv
[i
]);
759 _cupsLangPrintf(stderr
,
760 _("%s: Missing version for \"-V\"."),
765 if (!strcmp(argv
[i
], "1.1"))
767 else if (!strcmp(argv
[i
], "2.0"))
769 else if (!strcmp(argv
[i
], "2.1"))
771 else if (!strcmp(argv
[i
], "2.2"))
775 _cupsLangPrintf(stderr
, _("%s: Bad version %s for \"-V\"."),
785 _cupsLangPrintf(stderr
,
786 _("ippfind: Missing regular expression after "
791 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
,
792 argv
[i
], NULL
)) == NULL
)
793 return (IPPFIND_EXIT_MEMORY
);
800 _cupsLangPrintf(stderr
,
801 _("ippfind: Missing regular expression after "
806 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
,
807 argv
[i
], NULL
)) == NULL
)
808 return (IPPFIND_EXIT_MEMORY
);
812 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
814 return (IPPFIND_EXIT_MEMORY
);
823 _cupsLangPrintf(stderr
,
824 _("ippfind: Missing regular expression after "
829 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
,
830 argv
[i
], NULL
)) == NULL
)
831 return (IPPFIND_EXIT_MEMORY
);
835 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
837 return (IPPFIND_EXIT_MEMORY
);
843 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
845 return (IPPFIND_EXIT_MEMORY
);
851 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
853 return (IPPFIND_EXIT_MEMORY
);
857 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
859 return (IPPFIND_EXIT_MEMORY
);
868 _cupsLangPrintf(stderr
,
869 _("ippfind: Missing key name after %s."),
874 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
],
875 NULL
, NULL
)) == NULL
)
876 return (IPPFIND_EXIT_MEMORY
);
883 _cupsLangPrintf(stderr
,
884 _("ippfind: Missing regular expression after "
889 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
,
890 argv
[i
], NULL
)) == NULL
)
891 return (IPPFIND_EXIT_MEMORY
);
898 _cupsLangPrintf(stderr
,
899 _("ippfind: Missing program after %s."),
904 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
906 return (IPPFIND_EXIT_MEMORY
);
909 if (!strcmp(argv
[i
], ";"))
916 _cupsLangPrintf(stderr
,
917 _("ippfind: Missing semi-colon after %s."),
926 _cupsLangPrintf(stderr
, _("%s: Unknown option \"-%c\"."),
934 * Add new expression...
937 if (logic
== IPPFIND_OP_AND
&&
938 current
&& current
->prev
&&
939 parent
&& parent
->op
!= IPPFIND_OP_AND
)
942 * Need to re-group "current" in a new AND node...
945 ippfind_expr_t
*tempand
; /* Temporary AND node */
947 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
949 return (IPPFIND_EXIT_MEMORY
);
952 * Replace "current" with new AND node at the end of this list...
955 current
->prev
->next
= tempand
;
956 tempand
->prev
= current
->prev
;
957 tempand
->parent
= parent
;
960 * Add "current to the new AND node...
963 tempand
->child
= current
;
964 current
->parent
= tempand
;
965 current
->prev
= NULL
;
970 * Add the new node at current level...
973 temp
->parent
= parent
;
974 temp
->prev
= current
;
977 current
->next
= temp
;
979 parent
->child
= temp
;
985 logic
= IPPFIND_OP_AND
;
991 else if (!strcmp(argv
[i
], "("))
993 if (num_parens
>= 100)
995 _cupsLangPuts(stderr
, _("ippfind: Too many parenthesis."));
999 if ((temp
= new_expr(IPPFIND_OP_AND
, invert
, NULL
, NULL
, NULL
)) == NULL
)
1000 return (IPPFIND_EXIT_MEMORY
);
1002 parens
[num_parens
++] = temp
;
1006 temp
->parent
= current
->parent
;
1007 current
->next
= temp
;
1008 temp
->prev
= current
;
1016 logic
= IPPFIND_OP_AND
;
1018 else if (!strcmp(argv
[i
], ")"))
1020 if (num_parens
<= 0)
1022 _cupsLangPuts(stderr
, _("ippfind: Missing open parenthesis."));
1026 current
= parens
[--num_parens
];
1027 parent
= current
->parent
;
1029 logic
= IPPFIND_OP_AND
;
1031 else if (!strcmp(argv
[i
], "!"))
1038 * _regtype._tcp[,subtype][.domain]
1042 * service-name[._regtype._tcp[.domain]]
1045 cupsArrayAdd(searches
, argv
[i
]);
1051 _cupsLangPuts(stderr
, _("ippfind: Missing close parenthesis."));
1058 * Add an implicit --print-uri to the end...
1061 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, 0, NULL
, NULL
, NULL
)) == NULL
)
1062 return (IPPFIND_EXIT_MEMORY
);
1066 while (current
->parent
)
1067 current
= current
->parent
;
1069 current
->next
= temp
;
1070 temp
->prev
= current
;
1076 if (cupsArrayCount(searches
) == 0)
1079 * Add an implicit browse for IPP printers ("_ipp._tcp")...
1082 cupsArrayAdd(searches
, "_ipp._tcp");
1085 if (getenv("IPPFIND_DEBUG"))
1087 int indent
= 4; /* Indentation */
1089 puts("Expression tree:");
1090 current
= expressions
;
1094 * Print the current node...
1097 printf("%*s%s%s\n", indent
, "", current
->invert
? "!" : "",
1101 * Advance to the next node...
1106 current
= current
->child
;
1109 else if (current
->next
)
1110 current
= current
->next
;
1111 else if (current
->parent
)
1113 while (current
->parent
)
1116 current
= current
->parent
;
1121 current
= current
->next
;
1127 puts("\nSearch items:");
1128 for (search
= (const char *)cupsArrayFirst(searches
);
1130 search
= (const char *)cupsArrayNext(searches
))
1131 printf(" %s\n", search
);
1135 * Start up browsing/resolving...
1139 if ((err
= DNSServiceCreateConnection(&dnssd_ref
)) != kDNSServiceErr_NoError
)
1141 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1142 dnssd_error_string(err
));
1143 return (IPPFIND_EXIT_BONJOUR
);
1146 #elif defined(HAVE_AVAHI)
1147 if ((avahi_poll
= avahi_simple_poll_new()) == NULL
)
1149 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1151 return (IPPFIND_EXIT_BONJOUR
);
1154 avahi_simple_poll_set_func(avahi_poll
, poll_callback
, NULL
);
1156 avahi_client
= avahi_client_new(avahi_simple_poll_get(avahi_poll
),
1157 0, client_callback
, avahi_poll
, &err
);
1160 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1161 dnssd_error_string(err
));
1162 return (IPPFIND_EXIT_BONJOUR
);
1164 #endif /* HAVE_DNSSD */
1166 for (search
= (const char *)cupsArrayFirst(searches
);
1168 search
= (const char *)cupsArrayNext(searches
))
1170 char buf
[1024], /* Full name string */
1171 *name
= NULL
, /* Service instance name */
1172 *regtype
, /* Registration type */
1173 *domain
; /* Domain, if any */
1175 strlcpy(buf
, search
, sizeof(buf
));
1180 else if ((regtype
= strstr(buf
, "._")) != NULL
)
1188 regtype
= "_ipp._tcp";
1191 for (domain
= regtype
; *domain
; domain
++)
1192 if (*domain
== '.' && domain
[1] != '_')
1204 * Resolve the given service instance name, regtype, and domain...
1210 service
= get_service(services
, name
, regtype
, domain
);
1213 service
->ref
= dnssd_ref
;
1214 err
= DNSServiceResolve(&(service
->ref
),
1215 kDNSServiceFlagsShareConnection
, 0, name
,
1216 regtype
, domain
, resolve_callback
,
1219 #elif defined(HAVE_AVAHI)
1220 service
->ref
= avahi_service_resolver_new(avahi_client
, AVAHI_IF_UNSPEC
,
1221 AVAHI_PROTO_UNSPEC
, name
,
1223 AVAHI_PROTO_UNSPEC
, 0,
1224 resolve_callback
, service
);
1228 err
= avahi_client_errno(avahi_client
);
1229 #endif /* HAVE_DNSSD */
1234 * Browse for services of the given type...
1238 DNSServiceRef ref
; /* Browse reference */
1241 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
, 0, regtype
,
1242 domain
, browse_callback
, services
);
1247 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
,
1248 kDNSServiceInterfaceIndexLocalOnly
, regtype
,
1249 domain
, browse_local_callback
, services
);
1252 #elif defined(HAVE_AVAHI)
1253 if (avahi_service_browser_new(avahi_client
, AVAHI_IF_UNSPEC
,
1254 AVAHI_PROTO_UNSPEC
, regtype
, domain
, 0,
1255 browse_callback
, services
))
1258 err
= avahi_client_errno(avahi_client
);
1259 #endif /* HAVE_DNSSD */
1264 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
1265 dnssd_error_string(err
));
1268 printf("name=\"%s\"\n", name
);
1270 printf("regtype=\"%s\"\n", regtype
);
1273 printf("domain=\"%s\"\n", domain
);
1275 return (IPPFIND_EXIT_BONJOUR
);
1280 * Process browse/resolve requests...
1283 if (bonjour_timeout
> 1.0)
1284 endtime
= get_time() + bonjour_timeout
;
1286 endtime
= get_time() + 300.0;
1288 while (get_time() < endtime
)
1290 int process
= 0; /* Process services? */
1293 int fd
= DNSServiceRefSockFD(dnssd_ref
);
1294 /* File descriptor for DNS-SD */
1297 FD_SET(fd
, &sinput
);
1299 stimeout
.tv_sec
= 0;
1300 stimeout
.tv_usec
= 500000;
1302 if (select(fd
+ 1, &sinput
, NULL
, NULL
, &stimeout
) < 0)
1305 if (FD_ISSET(fd
, &sinput
))
1308 * Process responses...
1311 DNSServiceProcessResult(dnssd_ref
);
1316 * Time to process services...
1322 #elif defined(HAVE_AVAHI)
1325 if (avahi_simple_poll_iterate(avahi_poll
, 500) > 0)
1328 * We've been told to exit the loop. Perhaps the connection to
1332 return (IPPFIND_EXIT_BONJOUR
);
1335 if (!avahi_got_data
)
1338 * Time to process services...
1343 #endif /* HAVE_DNSSD */
1348 * Process any services that we have found...
1351 int active
= 0, /* Number of active resolves */
1352 resolved
= 0, /* Number of resolved services */
1353 processed
= 0; /* Number of processed services */
1355 for (service
= (ippfind_srv_t
*)cupsArrayFirst(services
);
1357 service
= (ippfind_srv_t
*)cupsArrayNext(services
))
1359 if (service
->is_processed
)
1362 if (service
->is_resolved
)
1365 if (!service
->ref
&& !service
->is_resolved
)
1368 * Found a service, now resolve it (but limit to 50 active resolves...)
1374 service
->ref
= dnssd_ref
;
1375 err
= DNSServiceResolve(&(service
->ref
),
1376 kDNSServiceFlagsShareConnection
, 0,
1377 service
->name
, service
->regtype
,
1378 service
->domain
, resolve_callback
,
1381 #elif defined(HAVE_AVAHI)
1382 service
->ref
= avahi_service_resolver_new(avahi_client
,
1388 AVAHI_PROTO_UNSPEC
, 0,
1394 err
= avahi_client_errno(avahi_client
);
1395 #endif /* HAVE_DNSSD */
1399 _cupsLangPrintf(stderr
,
1400 _("ippfind: Unable to browse or resolve: %s"),
1401 dnssd_error_string(err
));
1402 return (IPPFIND_EXIT_BONJOUR
);
1408 else if (service
->is_resolved
&& !service
->is_processed
)
1411 * Resolved, not process this service against the expressions...
1417 DNSServiceRefDeallocate(service
->ref
);
1419 avahi_service_resolver_free(service
->ref
);
1420 #endif /* HAVE_DNSSD */
1422 service
->ref
= NULL
;
1425 if (eval_expr(service
, expressions
))
1426 status
= IPPFIND_EXIT_TRUE
;
1428 service
->is_processed
= 1;
1430 else if (service
->ref
)
1435 * If we have processed all services we have discovered, then we are done.
1438 if (processed
== cupsArrayCount(services
) && bonjour_timeout
<= 1.0)
1444 return (IPPFIND_EXIT_BONJOUR
);
1452 * 'browse_callback()' - Browse devices.
1455 static void DNSSD_API
1457 DNSServiceRef sdRef
, /* I - Service reference */
1458 DNSServiceFlags flags
, /* I - Option flags */
1459 uint32_t interfaceIndex
, /* I - Interface number */
1460 DNSServiceErrorType errorCode
, /* I - Error, if any */
1461 const char *serviceName
, /* I - Name of service/device */
1462 const char *regtype
, /* I - Type of service */
1463 const char *replyDomain
, /* I - Service domain */
1464 void *context
) /* I - Services array */
1467 * Only process "add" data...
1471 (void)interfaceIndex
;
1473 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1480 get_service((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
1485 * 'browse_local_callback()' - Browse local devices.
1488 static void DNSSD_API
1489 browse_local_callback(
1490 DNSServiceRef sdRef
, /* I - Service reference */
1491 DNSServiceFlags flags
, /* I - Option flags */
1492 uint32_t interfaceIndex
, /* I - Interface number */
1493 DNSServiceErrorType errorCode
, /* I - Error, if any */
1494 const char *serviceName
, /* I - Name of service/device */
1495 const char *regtype
, /* I - Type of service */
1496 const char *replyDomain
, /* I - Service domain */
1497 void *context
) /* I - Services array */
1499 ippfind_srv_t
*service
; /* Service */
1503 * Only process "add" data...
1507 (void)interfaceIndex
;
1509 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1516 service
= get_service((cups_array_t
*)context
, serviceName
, regtype
,
1518 service
->is_local
= 1;
1520 #endif /* HAVE_DNSSD */
1525 * 'browse_callback()' - Browse devices.
1530 AvahiServiceBrowser
*browser
, /* I - Browser */
1531 AvahiIfIndex interface
, /* I - Interface index (unused) */
1532 AvahiProtocol protocol
, /* I - Network protocol (unused) */
1533 AvahiBrowserEvent event
, /* I - What happened */
1534 const char *name
, /* I - Service name */
1535 const char *type
, /* I - Registration type */
1536 const char *domain
, /* I - Domain */
1537 AvahiLookupResultFlags flags
, /* I - Flags */
1538 void *context
) /* I - Services array */
1540 AvahiClient
*client
= avahi_service_browser_get_client(browser
);
1541 /* Client information */
1542 ippfind_srv_t
*service
; /* Service information */
1551 case AVAHI_BROWSER_FAILURE
:
1552 fprintf(stderr
, "DEBUG: browse_callback: %s\n",
1553 avahi_strerror(avahi_client_errno(client
)));
1555 avahi_simple_poll_quit(avahi_poll
);
1558 case AVAHI_BROWSER_NEW
:
1560 * This object is new on the network. Create a device entry for it if
1561 * it doesn't yet exist.
1564 service
= get_service((cups_array_t
*)context
, name
, type
, domain
);
1566 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
1567 service
->is_local
= 1;
1570 case AVAHI_BROWSER_REMOVE
:
1571 case AVAHI_BROWSER_ALL_FOR_NOW
:
1572 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
1579 * 'client_callback()' - Avahi client callback function.
1584 AvahiClient
*client
, /* I - Client information (unused) */
1585 AvahiClientState state
, /* I - Current state */
1586 void *context
) /* I - User data (unused) */
1592 * If the connection drops, quit.
1595 if (state
== AVAHI_CLIENT_FAILURE
)
1597 fputs("DEBUG: Avahi connection failed.\n", stderr
);
1599 avahi_simple_poll_quit(avahi_poll
);
1602 #endif /* HAVE_AVAHI */
1606 * 'compare_services()' - Compare two devices.
1609 static int /* O - Result of comparison */
1610 compare_services(ippfind_srv_t
*a
, /* I - First device */
1611 ippfind_srv_t
*b
) /* I - Second device */
1613 return (strcmp(a
->name
, b
->name
));
1618 * 'dnssd_error_string()' - Return an error string for an error code.
1621 static const char * /* O - Error message */
1622 dnssd_error_string(int error
) /* I - Error number */
1627 case kDNSServiceErr_NoError
:
1631 case kDNSServiceErr_Unknown
:
1632 return ("Unknown error.");
1634 case kDNSServiceErr_NoSuchName
:
1635 return ("Service not found.");
1637 case kDNSServiceErr_NoMemory
:
1638 return ("Out of memory.");
1640 case kDNSServiceErr_BadParam
:
1641 return ("Bad parameter.");
1643 case kDNSServiceErr_BadReference
:
1644 return ("Bad service reference.");
1646 case kDNSServiceErr_BadState
:
1647 return ("Bad state.");
1649 case kDNSServiceErr_BadFlags
:
1650 return ("Bad flags.");
1652 case kDNSServiceErr_Unsupported
:
1653 return ("Unsupported.");
1655 case kDNSServiceErr_NotInitialized
:
1656 return ("Not initialized.");
1658 case kDNSServiceErr_AlreadyRegistered
:
1659 return ("Already registered.");
1661 case kDNSServiceErr_NameConflict
:
1662 return ("Name conflict.");
1664 case kDNSServiceErr_Invalid
:
1665 return ("Invalid name.");
1667 case kDNSServiceErr_Firewall
:
1668 return ("Firewall prevents registration.");
1670 case kDNSServiceErr_Incompatible
:
1671 return ("Client library incompatible.");
1673 case kDNSServiceErr_BadInterfaceIndex
:
1674 return ("Bad interface index.");
1676 case kDNSServiceErr_Refused
:
1677 return ("Server prevents registration.");
1679 case kDNSServiceErr_NoSuchRecord
:
1680 return ("Record not found.");
1682 case kDNSServiceErr_NoAuth
:
1683 return ("Authentication required.");
1685 case kDNSServiceErr_NoSuchKey
:
1686 return ("Encryption key not found.");
1688 case kDNSServiceErr_NATTraversal
:
1689 return ("Unable to traverse NAT boundary.");
1691 case kDNSServiceErr_DoubleNAT
:
1692 return ("Unable to traverse double-NAT boundary.");
1694 case kDNSServiceErr_BadTime
:
1695 return ("Bad system time.");
1697 case kDNSServiceErr_BadSig
:
1698 return ("Bad signature.");
1700 case kDNSServiceErr_BadKey
:
1701 return ("Bad encryption key.");
1703 case kDNSServiceErr_Transient
:
1704 return ("Transient error occurred - please try again.");
1706 case kDNSServiceErr_ServiceNotRunning
:
1707 return ("Server not running.");
1709 case kDNSServiceErr_NATPortMappingUnsupported
:
1710 return ("NAT doesn't support NAT-PMP or UPnP.");
1712 case kDNSServiceErr_NATPortMappingDisabled
:
1713 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1715 case kDNSServiceErr_NoRouter
:
1716 return ("No Internet/default router configured.");
1718 case kDNSServiceErr_PollingMode
:
1719 return ("Service polling mode error.");
1722 case kDNSServiceErr_Timeout
:
1723 return ("Service timeout.");
1727 # elif defined(HAVE_AVAHI)
1728 return (avahi_strerror(error
));
1729 # endif /* HAVE_DNSSD */
1734 * 'eval_expr()' - Evaluate the expressions against the specified service.
1736 * Returns 1 for true and 0 for false.
1739 static int /* O - Result of evaluation */
1740 eval_expr(ippfind_srv_t
*service
, /* I - Service */
1741 ippfind_expr_t
*expressions
) /* I - Expressions */
1743 int logic
, /* Logical operation */
1744 result
; /* Result of current expression */
1745 ippfind_expr_t
*expression
; /* Current expression */
1746 const char *val
; /* TXT value */
1749 * Loop through the expressions...
1752 if (expressions
&& expressions
->parent
)
1753 logic
= expressions
->parent
->op
;
1755 logic
= IPPFIND_OP_AND
;
1757 for (expression
= expressions
; expression
; expression
= expression
->next
)
1759 switch (expression
->op
)
1762 case IPPFIND_OP_AND
:
1763 case IPPFIND_OP_OR
:
1764 if (expression
->child
)
1765 result
= eval_expr(service
, expression
->child
);
1767 result
= expression
->op
== IPPFIND_OP_AND
;
1769 case IPPFIND_OP_TRUE
:
1772 case IPPFIND_OP_FALSE
:
1775 case IPPFIND_OP_IS_LOCAL
:
1776 result
= service
->is_local
;
1778 case IPPFIND_OP_IS_REMOTE
:
1779 result
= !service
->is_local
;
1781 case IPPFIND_OP_DOMAIN_REGEX
:
1782 result
= !regexec(&(expression
->re
), service
->domain
, 0, NULL
, 0);
1784 case IPPFIND_OP_NAME_REGEX
:
1785 result
= !regexec(&(expression
->re
), service
->name
, 0, NULL
, 0);
1787 case IPPFIND_OP_HOST_REGEX
:
1788 result
= !regexec(&(expression
->re
), service
->host
, 0, NULL
, 0);
1790 case IPPFIND_OP_PORT_RANGE
:
1791 result
= service
->port
>= expression
->range
[0] &&
1792 service
->port
<= expression
->range
[1];
1794 case IPPFIND_OP_PATH_REGEX
:
1795 result
= !regexec(&(expression
->re
), service
->resource
, 0, NULL
, 0);
1797 case IPPFIND_OP_TXT_EXISTS
:
1798 result
= cupsGetOption(expression
->key
, service
->num_txt
,
1799 service
->txt
) != NULL
;
1801 case IPPFIND_OP_TXT_REGEX
:
1802 val
= cupsGetOption(expression
->key
, service
->num_txt
,
1805 result
= !regexec(&(expression
->re
), val
, 0, NULL
, 0);
1809 if (getenv("IPPFIND_DEBUG"))
1810 printf("TXT_REGEX of \"%s\": %d\n", val
, result
);
1812 case IPPFIND_OP_URI_REGEX
:
1813 result
= !regexec(&(expression
->re
), service
->uri
, 0, NULL
, 0);
1815 case IPPFIND_OP_EXEC
:
1816 result
= exec_program(service
, expression
->num_args
,
1819 case IPPFIND_OP_LIST
:
1820 result
= list_service(service
);
1822 case IPPFIND_OP_PRINT_NAME
:
1823 _cupsLangPuts(stdout
, service
->name
);
1826 case IPPFIND_OP_PRINT_URI
:
1827 _cupsLangPuts(stdout
, service
->uri
);
1830 case IPPFIND_OP_QUIET
:
1835 if (expression
->invert
)
1838 if (logic
== IPPFIND_OP_AND
&& !result
)
1840 else if (logic
== IPPFIND_OP_OR
&& result
)
1844 return (logic
== IPPFIND_OP_AND
);
1849 * 'exec_program()' - Execute a program for a service.
1852 static int /* O - 1 if program terminated
1853 successfully, 0 otherwise. */
1854 exec_program(ippfind_srv_t
*service
, /* I - Service */
1855 int num_args
, /* I - Number of command-line args */
1856 char **args
) /* I - Command-line arguments */
1858 char **myargv
, /* Command-line arguments */
1859 **myenvp
, /* Environment variables */
1860 *ptr
, /* Pointer into variable */
1861 domain
[1024], /* IPPFIND_SERVICE_DOMAIN */
1862 hostname
[1024], /* IPPFIND_SERVICE_HOSTNAME */
1863 name
[256], /* IPPFIND_SERVICE_NAME */
1864 port
[32], /* IPPFIND_SERVICE_PORT */
1865 regtype
[256], /* IPPFIND_SERVICE_REGTYPE */
1866 scheme
[128], /* IPPFIND_SERVICE_SCHEME */
1867 uri
[1024], /* IPPFIND_SERVICE_URI */
1868 txt
[100][256]; /* IPPFIND_TXT_foo */
1869 int i
, /* Looping var */
1870 myenvc
, /* Number of environment variables */
1871 status
; /* Exit status of program */
1873 char program
[1024]; /* Program to execute */
1874 int pid
; /* Process ID */
1879 * Environment variables...
1882 snprintf(domain
, sizeof(domain
), "IPPFIND_SERVICE_DOMAIN=%s",
1884 snprintf(hostname
, sizeof(hostname
), "IPPFIND_SERVICE_HOSTNAME=%s",
1886 snprintf(name
, sizeof(name
), "IPPFIND_SERVICE_NAME=%s", service
->name
);
1887 snprintf(port
, sizeof(port
), "IPPFIND_SERVICE_PORT=%d", service
->port
);
1888 snprintf(regtype
, sizeof(regtype
), "IPPFIND_SERVICE_REGTYPE=%s",
1890 snprintf(scheme
, sizeof(scheme
), "IPPFIND_SERVICE_SCHEME=%s",
1891 !strncmp(service
->regtype
, "_http._tcp", 10) ? "http" :
1892 !strncmp(service
->regtype
, "_https._tcp", 11) ? "https" :
1893 !strncmp(service
->regtype
, "_ipp._tcp", 9) ? "ipp" :
1894 !strncmp(service
->regtype
, "_ipps._tcp", 10) ? "ipps" : "lpd");
1895 snprintf(uri
, sizeof(uri
), "IPPFIND_SERVICE_URI=%s", service
->uri
);
1896 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1898 snprintf(txt
[i
], sizeof(txt
[i
]), "IPPFIND_TXT_%s=%s", service
->txt
[i
].name
,
1899 service
->txt
[i
].value
);
1900 for (ptr
= txt
[i
] + 12; *ptr
&& *ptr
!= '='; ptr
++)
1901 *ptr
= (char)_cups_toupper(*ptr
);
1904 for (i
= 0, myenvc
= 7 + service
->num_txt
; environ
[i
]; i
++)
1905 if (strncmp(environ
[i
], "IPPFIND_", 8))
1908 if ((myenvp
= calloc(sizeof(char *), (size_t)(myenvc
+ 1))) == NULL
)
1910 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1911 exit(IPPFIND_EXIT_MEMORY
);
1914 for (i
= 0, myenvc
= 0; environ
[i
]; i
++)
1915 if (strncmp(environ
[i
], "IPPFIND_", 8))
1916 myenvp
[myenvc
++] = environ
[i
];
1918 myenvp
[myenvc
++] = domain
;
1919 myenvp
[myenvc
++] = hostname
;
1920 myenvp
[myenvc
++] = name
;
1921 myenvp
[myenvc
++] = port
;
1922 myenvp
[myenvc
++] = regtype
;
1923 myenvp
[myenvc
++] = scheme
;
1924 myenvp
[myenvc
++] = uri
;
1926 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1927 myenvp
[myenvc
++] = txt
[i
];
1930 * Allocate and copy command-line arguments...
1933 if ((myargv
= calloc(sizeof(char *), (size_t)(num_args
+ 1))) == NULL
)
1935 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1936 exit(IPPFIND_EXIT_MEMORY
);
1939 for (i
= 0; i
< num_args
; i
++)
1941 if (strchr(args
[i
], '{'))
1943 char temp
[2048], /* Temporary string */
1944 *tptr
, /* Pointer into temporary string */
1945 keyword
[256], /* {keyword} */
1946 *kptr
; /* Pointer into keyword */
1948 for (ptr
= args
[i
], tptr
= temp
; *ptr
; ptr
++)
1953 * Do a {var} substitution...
1956 for (kptr
= keyword
, ptr
++; *ptr
&& *ptr
!= '}'; ptr
++)
1957 if (kptr
< (keyword
+ sizeof(keyword
) - 1))
1962 _cupsLangPuts(stderr
,
1963 _("ippfind: Missing close brace in substitution."));
1964 exit(IPPFIND_EXIT_SYNTAX
);
1968 if (!keyword
[0] || !strcmp(keyword
, "service_uri"))
1969 strlcpy(tptr
, service
->uri
, sizeof(temp
) - (size_t)(tptr
- temp
));
1970 else if (!strcmp(keyword
, "service_domain"))
1971 strlcpy(tptr
, service
->domain
, sizeof(temp
) - (size_t)(tptr
- temp
));
1972 else if (!strcmp(keyword
, "service_hostname"))
1973 strlcpy(tptr
, service
->host
, sizeof(temp
) - (size_t)(tptr
- temp
));
1974 else if (!strcmp(keyword
, "service_name"))
1975 strlcpy(tptr
, service
->name
, sizeof(temp
) - (size_t)(tptr
- temp
));
1976 else if (!strcmp(keyword
, "service_path"))
1977 strlcpy(tptr
, service
->resource
, sizeof(temp
) - (size_t)(tptr
- temp
));
1978 else if (!strcmp(keyword
, "service_port"))
1979 strlcpy(tptr
, port
+ 21, sizeof(temp
) - (size_t)(tptr
- temp
));
1980 else if (!strcmp(keyword
, "service_scheme"))
1981 strlcpy(tptr
, scheme
+ 22, sizeof(temp
) - (size_t)(tptr
- temp
));
1982 else if (!strncmp(keyword
, "txt_", 4))
1984 const char *val
= cupsGetOption(keyword
+ 4, service
->num_txt
, service
->txt
);
1986 strlcpy(tptr
, val
, sizeof(temp
) - (size_t)(tptr
- temp
));
1992 _cupsLangPrintf(stderr
, _("ippfind: Unknown variable \"{%s}\"."),
1994 exit(IPPFIND_EXIT_SYNTAX
);
1997 tptr
+= strlen(tptr
);
1999 else if (tptr
< (temp
+ sizeof(temp
) - 1))
2004 myargv
[i
] = strdup(temp
);
2007 myargv
[i
] = strdup(args
[i
]);
2011 if (getenv("IPPFIND_DEBUG"))
2013 printf("\nProgram:\n %s\n", args
[0]);
2014 puts("\nArguments:");
2015 for (i
= 0; i
< num_args
; i
++)
2016 printf(" %s\n", myargv
[i
]);
2017 puts("\nEnvironment:");
2018 for (i
= 0; i
< myenvc
; i
++)
2019 printf(" %s\n", myenvp
[i
]);
2022 status
= _spawnvpe(_P_WAIT
, args
[0], myargv
, myenvp
);
2026 * Execute the program...
2029 if (strchr(args
[0], '/') && !access(args
[0], X_OK
))
2030 strlcpy(program
, args
[0], sizeof(program
));
2031 else if (!cupsFileFind(args
[0], getenv("PATH"), 1, program
, sizeof(program
)))
2033 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2034 args
[0], strerror(ENOENT
));
2035 exit(IPPFIND_EXIT_SYNTAX
);
2038 if (getenv("IPPFIND_DEBUG"))
2040 printf("\nProgram:\n %s\n", program
);
2041 puts("\nArguments:");
2042 for (i
= 0; i
< num_args
; i
++)
2043 printf(" %s\n", myargv
[i
]);
2044 puts("\nEnvironment:");
2045 for (i
= 0; i
< myenvc
; i
++)
2046 printf(" %s\n", myenvp
[i
]);
2049 if ((pid
= fork()) == 0)
2052 * Child comes here...
2055 execve(program
, myargv
, myenvp
);
2060 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2061 args
[0], strerror(errno
));
2062 exit(IPPFIND_EXIT_SYNTAX
);
2067 * Wait for it to complete...
2070 while (wait(&status
) != pid
)
2079 for (i
= 0; i
< num_args
; i
++)
2086 * Return whether the program succeeded or crashed...
2089 if (getenv("IPPFIND_DEBUG"))
2092 printf("Exit Status: %d\n", status
);
2094 if (WIFEXITED(status
))
2095 printf("Exit Status: %d\n", WEXITSTATUS(status
));
2097 printf("Terminating Signal: %d\n", WTERMSIG(status
));
2101 return (status
== 0);
2106 * 'get_service()' - Create or update a device.
2109 static ippfind_srv_t
* /* O - Service */
2110 get_service(cups_array_t
*services
, /* I - Service array */
2111 const char *serviceName
, /* I - Name of service/device */
2112 const char *regtype
, /* I - Type of service */
2113 const char *replyDomain
) /* I - Service domain */
2115 ippfind_srv_t key
, /* Search key */
2116 *service
; /* Service */
2117 char fullName
[kDNSServiceMaxDomainName
];
2118 /* Full name for query */
2122 * See if this is a new device...
2125 key
.name
= (char *)serviceName
;
2126 key
.regtype
= (char *)regtype
;
2128 for (service
= cupsArrayFind(services
, &key
);
2130 service
= cupsArrayNext(services
))
2131 if (_cups_strcasecmp(service
->name
, key
.name
))
2133 else if (!strcmp(service
->regtype
, key
.regtype
))
2137 * Yes, add the service...
2140 service
= calloc(sizeof(ippfind_srv_t
), 1);
2141 service
->name
= strdup(serviceName
);
2142 service
->domain
= strdup(replyDomain
);
2143 service
->regtype
= strdup(regtype
);
2145 cupsArrayAdd(services
, service
);
2148 * Set the "full name" of this service, which is used for queries and
2153 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
2154 #else /* HAVE_AVAHI */
2155 avahi_service_name_join(fullName
, kDNSServiceMaxDomainName
, serviceName
,
2156 regtype
, replyDomain
);
2157 #endif /* HAVE_DNSSD */
2159 service
->fullName
= strdup(fullName
);
2166 * 'get_time()' - Get the current time-of-day in seconds.
2173 struct _timeb curtime
; /* Current Windows time */
2177 return (curtime
.time
+ 0.001 * curtime
.millitm
);
2180 struct timeval curtime
; /* Current UNIX time */
2182 if (gettimeofday(&curtime
, NULL
))
2185 return (curtime
.tv_sec
+ 0.000001 * curtime
.tv_usec
);
2191 * 'list_service()' - List the contents of a service.
2194 static int /* O - 1 if successful, 0 otherwise */
2195 list_service(ippfind_srv_t
*service
) /* I - Service */
2197 http_addrlist_t
*addrlist
; /* Address(es) of service */
2198 char port
[10]; /* Port number of service */
2201 snprintf(port
, sizeof(port
), "%d", service
->port
);
2203 if ((addrlist
= httpAddrGetList(service
->host
, address_family
, port
)) == NULL
)
2205 _cupsLangPrintf(stdout
, "%s unreachable", service
->uri
);
2209 if (!strncmp(service
->regtype
, "_ipp._tcp", 9) ||
2210 !strncmp(service
->regtype
, "_ipps._tcp", 10))
2216 http_t
*http
; /* HTTP connection */
2217 ipp_t
*request
, /* IPP request */
2218 *response
; /* IPP response */
2219 ipp_attribute_t
*attr
; /* IPP attribute */
2220 int i
, /* Looping var */
2221 count
, /* Number of values */
2222 version
, /* IPP version */
2223 paccepting
; /* printer-is-accepting-jobs value */
2224 ipp_pstate_t pstate
; /* printer-state value */
2225 char preasons
[1024], /* Comma-delimited printer-state-reasons */
2226 *ptr
, /* Pointer into reasons */
2227 *end
; /* End of reasons buffer */
2228 static const char * const rattrs
[] =/* Requested attributes */
2230 "printer-is-accepting-jobs",
2232 "printer-state-reasons"
2236 * Connect to the printer...
2239 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2240 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2241 HTTP_ENCRYPTION_ALWAYS
:
2242 HTTP_ENCRYPTION_IF_REQUESTED
,
2245 httpAddrFreeList(addrlist
);
2249 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2254 * Get the current printer state...
2258 version
= ipp_version
;
2262 request
= ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES
);
2263 ippSetVersion(request
, version
/ 10, version
% 10);
2264 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri", NULL
,
2266 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
2267 "requesting-user-name", NULL
, cupsUser());
2268 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
2269 "requested-attributes",
2270 (int)(sizeof(rattrs
) / sizeof(rattrs
[0])), NULL
, rattrs
);
2272 response
= cupsDoRequest(http
, request
, service
->resource
);
2274 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST
&& version
> 11)
2277 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
&& version
> 11);
2283 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
)
2285 _cupsLangPrintf(stdout
, "%s: unavailable", service
->uri
);
2289 if ((attr
= ippFindAttribute(response
, "printer-state",
2290 IPP_TAG_ENUM
)) != NULL
)
2291 pstate
= (ipp_pstate_t
)ippGetInteger(attr
, 0);
2293 pstate
= IPP_PSTATE_STOPPED
;
2295 if ((attr
= ippFindAttribute(response
, "printer-is-accepting-jobs",
2296 IPP_TAG_BOOLEAN
)) != NULL
)
2297 paccepting
= ippGetBoolean(attr
, 0);
2301 if ((attr
= ippFindAttribute(response
, "printer-state-reasons",
2302 IPP_TAG_KEYWORD
)) != NULL
)
2304 strlcpy(preasons
, ippGetString(attr
, 0, NULL
), sizeof(preasons
));
2306 for (i
= 1, count
= ippGetCount(attr
), ptr
= preasons
+ strlen(preasons
),
2307 end
= preasons
+ sizeof(preasons
) - 1;
2308 i
< count
&& ptr
< end
;
2309 i
++, ptr
+= strlen(ptr
))
2312 strlcpy(ptr
, ippGetString(attr
, i
, NULL
), (size_t)(end
- ptr
+ 1));
2316 strlcpy(preasons
, "none", sizeof(preasons
));
2318 ippDelete(response
);
2321 _cupsLangPrintf(stdout
, "%s %s %s %s", service
->uri
,
2322 ippEnumString("printer-state", pstate
),
2323 paccepting
? "accepting-jobs" : "not-accepting-jobs",
2326 else if (!strncmp(service
->regtype
, "_http._tcp", 10) ||
2327 !strncmp(service
->regtype
, "_https._tcp", 11))
2330 * HTTP/HTTPS web page
2333 http_t
*http
; /* HTTP connection */
2334 http_status_t status
; /* HEAD status */
2338 * Connect to the web server...
2341 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2342 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2343 HTTP_ENCRYPTION_ALWAYS
:
2344 HTTP_ENCRYPTION_IF_REQUESTED
,
2347 httpAddrFreeList(addrlist
);
2351 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2355 if (httpGet(http
, service
->resource
))
2357 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2363 status
= httpUpdate(http
);
2365 while (status
== HTTP_STATUS_CONTINUE
);
2370 if (status
>= HTTP_STATUS_BAD_REQUEST
)
2372 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2376 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2378 else if (!strncmp(service
->regtype
, "_printer._tcp", 13))
2384 int sock
; /* Socket */
2387 if (!httpAddrConnect(addrlist
, &sock
))
2389 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2390 httpAddrFreeList(addrlist
);
2394 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2395 httpAddrFreeList(addrlist
);
2397 httpAddrClose(NULL
, sock
);
2401 _cupsLangPrintf(stdout
, "%s unsupported", service
->uri
);
2402 httpAddrFreeList(addrlist
);
2411 * 'new_expr()' - Create a new expression.
2414 static ippfind_expr_t
* /* O - New expression */
2415 new_expr(ippfind_op_t op
, /* I - Operation */
2416 int invert
, /* I - Invert result? */
2417 const char *value
, /* I - TXT key or port range */
2418 const char *regex
, /* I - Regular expression */
2419 char **args
) /* I - Pointer to argument strings */
2421 ippfind_expr_t
*temp
; /* New expression */
2424 if ((temp
= calloc(1, sizeof(ippfind_expr_t
))) == NULL
)
2428 temp
->invert
= invert
;
2430 if (op
== IPPFIND_OP_TXT_EXISTS
|| op
== IPPFIND_OP_TXT_REGEX
)
2431 temp
->key
= (char *)value
;
2432 else if (op
== IPPFIND_OP_PORT_RANGE
)
2435 * Pull port number range of the form "number", "-number" (0-number),
2436 * "number-" (number-65535), and "number-number".
2441 temp
->range
[1] = atoi(value
+ 1);
2443 else if (strchr(value
, '-'))
2445 if (sscanf(value
, "%d-%d", temp
->range
, temp
->range
+ 1) == 1)
2446 temp
->range
[1] = 65535;
2450 temp
->range
[0] = temp
->range
[1] = atoi(value
);
2456 int err
= regcomp(&(temp
->re
), regex
, REG_NOSUB
| REG_ICASE
| REG_EXTENDED
);
2460 char message
[256]; /* Error message */
2462 regerror(err
, &(temp
->re
), message
, sizeof(message
));
2463 _cupsLangPrintf(stderr
, _("ippfind: Bad regular expression: %s"),
2465 exit(IPPFIND_EXIT_SYNTAX
);
2471 int num_args
; /* Number of arguments */
2473 for (num_args
= 1; args
[num_args
]; num_args
++)
2474 if (!strcmp(args
[num_args
], ";"))
2477 temp
->num_args
= num_args
;
2478 temp
->args
= malloc((size_t)num_args
* sizeof(char *));
2479 memcpy(temp
->args
, args
, (size_t)num_args
* sizeof(char *));
2488 * 'poll_callback()' - Wait for input on the specified file descriptors.
2490 * Note: This function is needed because avahi_simple_poll_iterate is broken
2491 * and always uses a timeout of 0 (!) milliseconds.
2492 * (Avahi Ticket #364)
2495 static int /* O - Number of file descriptors matching */
2497 struct pollfd
*pollfds
, /* I - File descriptors */
2498 unsigned int num_pollfds
, /* I - Number of file descriptors */
2499 int timeout
, /* I - Timeout in milliseconds (unused) */
2500 void *context
) /* I - User data (unused) */
2502 int val
; /* Return value */
2508 val
= poll(pollfds
, num_pollfds
, 500);
2515 #endif /* HAVE_AVAHI */
2519 * 'resolve_callback()' - Process resolve data.
2523 static void DNSSD_API
2525 DNSServiceRef sdRef
, /* I - Service reference */
2526 DNSServiceFlags flags
, /* I - Data flags */
2527 uint32_t interfaceIndex
, /* I - Interface */
2528 DNSServiceErrorType errorCode
, /* I - Error, if any */
2529 const char *fullName
, /* I - Full service name */
2530 const char *hostTarget
, /* I - Hostname */
2531 uint16_t port
, /* I - Port number (network byte order) */
2532 uint16_t txtLen
, /* I - Length of TXT record data */
2533 const unsigned char *txtRecord
, /* I - TXT record data */
2534 void *context
) /* I - Service */
2536 char key
[256], /* TXT key value */
2537 *value
; /* Value from TXT record */
2538 const unsigned char *txtEnd
; /* End of TXT record */
2539 uint8_t valueLen
; /* Length of value */
2540 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2545 * Only process "add" data...
2550 (void)interfaceIndex
;
2553 if (errorCode
!= kDNSServiceErr_NoError
)
2555 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
2556 dnssd_error_string(errorCode
));
2561 service
->is_resolved
= 1;
2562 service
->host
= strdup(hostTarget
);
2563 service
->port
= ntohs(port
);
2565 value
= service
->host
+ strlen(service
->host
) - 1;
2566 if (value
>= service
->host
&& *value
== '.')
2570 * Loop through the TXT key/value pairs and add them to an array...
2573 for (txtEnd
= txtRecord
+ txtLen
; txtRecord
< txtEnd
; txtRecord
+= valueLen
)
2576 * Ignore bogus strings...
2579 valueLen
= *txtRecord
++;
2581 memcpy(key
, txtRecord
, valueLen
);
2582 key
[valueLen
] = '\0';
2584 if ((value
= strchr(key
, '=')) == NULL
)
2590 * Add to array of TXT values...
2593 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2597 set_service_uri(service
);
2601 #elif defined(HAVE_AVAHI)
2604 AvahiServiceResolver
*resolver
, /* I - Resolver */
2605 AvahiIfIndex interface
, /* I - Interface */
2606 AvahiProtocol protocol
, /* I - Address protocol */
2607 AvahiResolverEvent event
, /* I - Event */
2608 const char *serviceName
,/* I - Service name */
2609 const char *regtype
, /* I - Registration type */
2610 const char *replyDomain
,/* I - Domain name */
2611 const char *hostTarget
, /* I - FQDN */
2612 const AvahiAddress
*address
, /* I - Address */
2613 uint16_t port
, /* I - Port number */
2614 AvahiStringList
*txt
, /* I - TXT records */
2615 AvahiLookupResultFlags flags
, /* I - Lookup flags */
2616 void *context
) /* I - Service */
2618 char key
[256], /* TXT key */
2619 *value
; /* TXT value */
2620 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2622 AvahiStringList
*current
; /* Current TXT key/value pair */
2627 if (event
!= AVAHI_RESOLVER_FOUND
)
2631 avahi_service_resolver_free(resolver
);
2632 avahi_simple_poll_quit(avahi_poll
);
2636 service
->is_resolved
= 1;
2637 service
->host
= strdup(hostTarget
);
2638 service
->port
= port
;
2640 value
= service
->host
+ strlen(service
->host
) - 1;
2641 if (value
>= service
->host
&& *value
== '.')
2645 * Loop through the TXT key/value pairs and add them to an array...
2648 for (current
= txt
; current
; current
= current
->next
)
2651 * Ignore bogus strings...
2654 if (current
->size
> (sizeof(key
) - 1))
2657 memcpy(key
, current
->text
, current
->size
);
2658 key
[current
->size
] = '\0';
2660 if ((value
= strchr(key
, '=')) == NULL
)
2666 * Add to array of TXT values...
2669 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2673 set_service_uri(service
);
2675 #endif /* HAVE_DNSSD */
2679 * 'set_service_uri()' - Set the URI of the service.
2683 set_service_uri(ippfind_srv_t
*service
) /* I - Service */
2685 char uri
[1024]; /* URI */
2686 const char *path
, /* Resource path */
2687 *scheme
; /* URI scheme */
2690 if (!strncmp(service
->regtype
, "_http.", 6))
2693 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2695 else if (!strncmp(service
->regtype
, "_https.", 7))
2698 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2700 else if (!strncmp(service
->regtype
, "_ipp.", 5))
2703 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2705 else if (!strncmp(service
->regtype
, "_ipps.", 6))
2708 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2710 else if (!strncmp(service
->regtype
, "_printer.", 9))
2713 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2718 if (!path
|| !*path
)
2723 service
->resource
= strdup(path
);
2727 snprintf(uri
, sizeof(uri
), "/%s", path
);
2728 service
->resource
= strdup(uri
);
2731 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
,
2732 service
->host
, service
->port
, service
->resource
);
2733 service
->uri
= strdup(uri
);
2738 * 'show_usage()' - Show program usage.
2744 _cupsLangPuts(stderr
, _("Usage: ippfind [options] regtype[,subtype]"
2745 "[.domain.] ... [expression]\n"
2746 " ippfind [options] name[.regtype[.domain.]] "
2747 "... [expression]\n"
2749 " ippfind --version"));
2750 _cupsLangPuts(stderr
, "");
2751 _cupsLangPuts(stderr
, _("Options:"));
2752 _cupsLangPuts(stderr
, _(" -4 Connect using IPv4."));
2753 _cupsLangPuts(stderr
, _(" -6 Connect using IPv6."));
2754 _cupsLangPuts(stderr
, _(" -T seconds Set the browse timeout in "
2756 _cupsLangPuts(stderr
, _(" -V version Set default IPP "
2758 _cupsLangPuts(stderr
, _(" --help Show this help."));
2759 _cupsLangPuts(stderr
, _(" --version Show program version."));
2760 _cupsLangPuts(stderr
, "");
2761 _cupsLangPuts(stderr
, _("Expressions:"));
2762 _cupsLangPuts(stderr
, _(" -P number[-number] Match port to number or range."));
2763 _cupsLangPuts(stderr
, _(" -d regex Match domain to regular expression."));
2764 _cupsLangPuts(stderr
, _(" -h regex Match hostname to regular expression."));
2765 _cupsLangPuts(stderr
, _(" -l List attributes."));
2766 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
2767 _cupsLangPuts(stderr
, _(" -p Print URI if true."));
2768 _cupsLangPuts(stderr
, _(" -q Quietly report match via exit code."));
2769 _cupsLangPuts(stderr
, _(" -r True if service is remote."));
2770 _cupsLangPuts(stderr
, _(" -s Print service name if true."));
2771 _cupsLangPuts(stderr
, _(" -t key True if the TXT record contains the key."));
2772 _cupsLangPuts(stderr
, _(" -u regex Match URI to regular expression."));
2773 _cupsLangPuts(stderr
, _(" -x utility [argument ...] ;\n"
2774 " Execute program if true."));
2775 _cupsLangPuts(stderr
, _(" --domain regex Match domain to regular expression."));
2776 _cupsLangPuts(stderr
, _(" --exec utility [argument ...] ;\n"
2777 " Execute program if true."));
2778 _cupsLangPuts(stderr
, _(" --host regex Match hostname to regular expression."));
2779 _cupsLangPuts(stderr
, _(" --ls List attributes."));
2780 _cupsLangPuts(stderr
, _(" --local True if service is local."));
2781 _cupsLangPuts(stderr
, _(" --name regex Match service name to regular expression."));
2782 _cupsLangPuts(stderr
, _(" --path regex Match resource path to regular expression."));
2783 _cupsLangPuts(stderr
, _(" --port number[-number] Match port to number or range."));
2784 _cupsLangPuts(stderr
, _(" --print Print URI if true."));
2785 _cupsLangPuts(stderr
, _(" --print-name Print service name if true."));
2786 _cupsLangPuts(stderr
, _(" --quiet Quietly report match via exit code."));
2787 _cupsLangPuts(stderr
, _(" --remote True if service is remote."));
2788 _cupsLangPuts(stderr
, _(" --txt key True if the TXT record contains the key."));
2789 _cupsLangPuts(stderr
, _(" --txt-* regex Match TXT record key to regular expression."));
2790 _cupsLangPuts(stderr
, _(" --uri regex Match URI to regular expression."));
2791 _cupsLangPuts(stderr
, "");
2792 _cupsLangPuts(stderr
, _("Modifiers:"));
2793 _cupsLangPuts(stderr
, _(" ( expressions ) Group expressions."));
2794 _cupsLangPuts(stderr
, _(" ! expression Unary NOT of expression."));
2795 _cupsLangPuts(stderr
, _(" --not expression Unary NOT of expression."));
2796 _cupsLangPuts(stderr
, _(" --false Always false."));
2797 _cupsLangPuts(stderr
, _(" --true Always true."));
2798 _cupsLangPuts(stderr
, _(" expression expression Logical AND."));
2799 _cupsLangPuts(stderr
, _(" expression --and expression\n"
2801 _cupsLangPuts(stderr
, _(" expression --or expression\n"
2803 _cupsLangPuts(stderr
, "");
2804 _cupsLangPuts(stderr
, _("Substitutions:"));
2805 _cupsLangPuts(stderr
, _(" {} URI"));
2806 _cupsLangPuts(stderr
, _(" {service_domain} Domain name"));
2807 _cupsLangPuts(stderr
, _(" {service_hostname} Fully-qualified domain name"));
2808 _cupsLangPuts(stderr
, _(" {service_name} Service instance name"));
2809 _cupsLangPuts(stderr
, _(" {service_port} Port number"));
2810 _cupsLangPuts(stderr
, _(" {service_regtype} DNS-SD registration type"));
2811 _cupsLangPuts(stderr
, _(" {service_scheme} URI scheme"));
2812 _cupsLangPuts(stderr
, _(" {service_uri} URI"));
2813 _cupsLangPuts(stderr
, _(" {txt_*} Value of TXT record key"));
2814 _cupsLangPuts(stderr
, "");
2815 _cupsLangPuts(stderr
, _("Environment Variables:"));
2816 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_DOMAIN Domain name"));
2817 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_HOSTNAME\n"
2818 " Fully-qualified domain name"));
2819 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_NAME Service instance name"));
2820 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_PORT Port number"));
2821 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
2822 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_SCHEME URI scheme"));
2823 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_URI URI"));
2824 _cupsLangPuts(stderr
, _(" IPPFIND_TXT_* Value of TXT record key"));
2826 exit(IPPFIND_EXIT_TRUE
);
2831 * 'show_version()' - Show program version.
2837 _cupsLangPuts(stderr
, CUPS_SVERSION
);
2839 exit(IPPFIND_EXIT_TRUE
);