4 * Utility to find IPP printers via Bonjour/DNS-SD and optionally run
5 * commands such as IPP and Bonjour conformance tests. This tool is
6 * inspired by the UNIX "find" command, thus its name.
8 * Copyright 2008-2013 by Apple Inc.
10 * These coded instructions, statements, and computer programs are the
11 * property of Apple Inc. and are protected by Federal copyright
12 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
13 * which should have been included with this file. If this file is
14 * file is missing or damaged, see the license at "http://www.cups.org/".
16 * This file is subject to the Apple OS-Developed Software exception.
20 * main() - Browse for printers.
21 * browse_callback() - Browse devices.
22 * browse_local_callback() - Browse local devices.
23 * browse_callback() - Browse devices.
24 * client_callback() - Avahi client callback function.
25 * compare_services() - Compare two devices.
26 * dnssd_error_string() - Return an error string for an error code.
27 * eval_expr() - Evaluate the expressions against the specified
29 * exec_program() - Execute a program for a service.
30 * get_service() - Create or update a device.
31 * get_time() - Get the current time-of-day in seconds.
32 * list_service() - List the contents of a service.
33 * new_expr() - Create a new expression.
34 * poll_callback() - Wait for input on the specified file
36 * resolve_callback() - Process resolve data.
37 * set_service_uri() - Set the URI of the service.
38 * show_usage() - Show program usage.
39 * show_version() - Show program version.
43 * Include necessary headers.
46 #define _CUPS_NO_DEPRECATED
47 #include <cups/cups-private.h>
52 #elif defined(HAVE_AVAHI)
53 # include <avahi-client/client.h>
54 # include <avahi-client/lookup.h>
55 # include <avahi-common/simple-watch.h>
56 # include <avahi-common/domain.h>
57 # include <avahi-common/error.h>
58 # include <avahi-common/malloc.h>
59 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
60 #endif /* HAVE_DNSSD */
62 extern char **environ
; /* Process environment variables */
69 typedef enum ippfind_exit_e
/* Exit codes */
71 IPPFIND_EXIT_TRUE
= 0, /* OK and result is true */
72 IPPFIND_EXIT_FALSE
, /* OK but result is false*/
73 IPPFIND_EXIT_BONJOUR
, /* Browse/resolve failure */
74 IPPFIND_EXIT_SYNTAX
, /* Bad option or syntax error */
75 IPPFIND_EXIT_MEMORY
/* Out of memory */
78 typedef enum ippfind_op_e
/* Operations for expressions */
80 /* "Evaluation" operations */
81 IPPFIND_OP_NONE
, /* No operation */
82 IPPFIND_OP_AND
, /* Logical AND of all children */
83 IPPFIND_OP_OR
, /* Logical OR of all children */
84 IPPFIND_OP_TRUE
, /* Always true */
85 IPPFIND_OP_FALSE
, /* Always false */
86 IPPFIND_OP_IS_LOCAL
, /* Is a local service */
87 IPPFIND_OP_IS_REMOTE
, /* Is a remote service */
88 IPPFIND_OP_DOMAIN_REGEX
, /* Domain matches regular expression */
89 IPPFIND_OP_NAME_REGEX
, /* Name matches regular expression */
90 IPPFIND_OP_HOST_REGEX
, /* Hostname matches regular expression */
91 IPPFIND_OP_PORT_RANGE
, /* Port matches range */
92 IPPFIND_OP_PATH_REGEX
, /* Path matches regular expression */
93 IPPFIND_OP_TXT_EXISTS
, /* TXT record key exists */
94 IPPFIND_OP_TXT_REGEX
, /* TXT record key matches regular expression */
95 IPPFIND_OP_URI_REGEX
, /* URI matches regular expression */
97 /* "Output" operations */
98 IPPFIND_OP_EXEC
, /* Execute when true */
99 IPPFIND_OP_LIST
, /* List when true */
100 IPPFIND_OP_PRINT_NAME
, /* Print URI when true */
101 IPPFIND_OP_PRINT_URI
, /* Print name when true */
102 IPPFIND_OP_QUIET
/* No output when true */
105 typedef struct ippfind_expr_s
/* Expression */
107 struct ippfind_expr_s
108 *prev
, /* Previous expression */
109 *next
, /* Next expression */
110 *parent
, /* Parent expressions */
111 *child
; /* Child expressions */
112 ippfind_op_t op
; /* Operation code (see above) */
113 int invert
; /* Invert the result */
114 char *key
; /* TXT record key */
115 regex_t re
; /* Regular expression for matching */
116 int range
[2]; /* Port number range */
117 int num_args
; /* Number of arguments for exec */
118 char **args
; /* Arguments for exec */
121 typedef struct ippfind_srv_s
/* Service information */
124 DNSServiceRef ref
; /* Service reference for query */
125 #elif defined(HAVE_AVAHI)
126 AvahiServiceResolver
*ref
; /* Resolver */
127 #endif /* HAVE_DNSSD */
128 char *name
, /* Service name */
129 *domain
, /* Domain name */
130 *regtype
, /* Registration type */
131 *fullName
, /* Full name */
132 *host
, /* Hostname */
133 *resource
, /* Resource path */
135 int num_txt
; /* Number of TXT record keys */
136 cups_option_t
*txt
; /* TXT record keys */
137 int port
, /* Port number */
138 is_local
, /* Is a local service? */
139 is_processed
, /* Did we process the service? */
140 is_resolved
; /* Got the resolve data? */
149 static DNSServiceRef dnssd_ref
; /* Master service reference */
150 #elif defined(HAVE_AVAHI)
151 static AvahiClient
*avahi_client
= NULL
;/* Client information */
152 static int avahi_got_data
= 0; /* Got data from poll? */
153 static AvahiSimplePoll
*avahi_poll
= NULL
;
154 /* Poll information */
155 #endif /* HAVE_DNSSD */
157 static int address_family
= AF_UNSPEC
;
158 /* Address family for LIST */
159 static int bonjour_error
= 0; /* Error browsing/resolving? */
160 static double bonjour_timeout
= 1.0; /* Timeout in seconds */
161 static int ipp_version
= 20; /* IPP version for LIST */
169 static void browse_callback(DNSServiceRef sdRef
,
170 DNSServiceFlags flags
,
171 uint32_t interfaceIndex
,
172 DNSServiceErrorType errorCode
,
173 const char *serviceName
,
175 const char *replyDomain
, void *context
)
176 __attribute__((nonnull(1,5,6,7,8)));
177 static void browse_local_callback(DNSServiceRef sdRef
,
178 DNSServiceFlags flags
,
179 uint32_t interfaceIndex
,
180 DNSServiceErrorType errorCode
,
181 const char *serviceName
,
183 const char *replyDomain
,
185 __attribute__((nonnull(1,5,6,7,8)));
186 #elif defined(HAVE_AVAHI)
187 static void browse_callback(AvahiServiceBrowser
*browser
,
188 AvahiIfIndex interface
,
189 AvahiProtocol protocol
,
190 AvahiBrowserEvent event
,
191 const char *serviceName
,
193 const char *replyDomain
,
194 AvahiLookupResultFlags flags
,
196 static void client_callback(AvahiClient
*client
,
197 AvahiClientState state
,
199 #endif /* HAVE_AVAHI */
201 static int compare_services(ippfind_srv_t
*a
, ippfind_srv_t
*b
);
202 static const char *dnssd_error_string(int error
);
203 static int eval_expr(ippfind_srv_t
*service
,
204 ippfind_expr_t
*expressions
);
205 static int exec_program(ippfind_srv_t
*service
, int num_args
,
207 static ippfind_srv_t
*get_service(cups_array_t
*services
,
208 const char *serviceName
,
210 const char *replyDomain
)
211 __attribute__((nonnull(1,2,3,4)));
212 static double get_time(void);
213 static int list_service(ippfind_srv_t
*service
);
214 static ippfind_expr_t
*new_expr(ippfind_op_t op
, int invert
,
215 const char *value
, const char *regex
,
218 static void resolve_callback(DNSServiceRef sdRef
,
219 DNSServiceFlags flags
,
220 uint32_t interfaceIndex
,
221 DNSServiceErrorType errorCode
,
222 const char *fullName
,
223 const char *hostTarget
, uint16_t port
,
225 const unsigned char *txtRecord
,
227 __attribute__((nonnull(1,5,6,9, 10)));
228 #elif defined(HAVE_AVAHI)
229 static int poll_callback(struct pollfd
*pollfds
,
230 unsigned int num_pollfds
, int timeout
,
232 static void resolve_callback(AvahiServiceResolver
*res
,
233 AvahiIfIndex interface
,
234 AvahiProtocol protocol
,
235 AvahiResolverEvent event
,
236 const char *serviceName
,
238 const char *replyDomain
,
239 const char *host_name
,
240 const AvahiAddress
*address
,
242 AvahiStringList
*txt
,
243 AvahiLookupResultFlags flags
,
245 #endif /* HAVE_DNSSD */
246 static void set_service_uri(ippfind_srv_t
*service
);
247 static void show_usage(void) __attribute__((noreturn
));
248 static void show_version(void) __attribute__((noreturn
));
252 * 'main()' - Browse for printers.
255 int /* O - Exit status */
256 main(int argc
, /* I - Number of command-line args */
257 char *argv
[]) /* I - Command-line arguments */
259 int i
, /* Looping var */
260 have_output
= 0,/* Have output expression */
261 status
= IPPFIND_EXIT_TRUE
;
263 const char *opt
, /* Option character */
264 *search
; /* Current browse/resolve string */
265 cups_array_t
*searches
; /* Things to browse/resolve */
266 cups_array_t
*services
; /* Service array */
267 ippfind_srv_t
*service
; /* Current service */
268 ippfind_expr_t
*expressions
= NULL
,
269 /* Expression tree */
270 *temp
= NULL
, /* New expression */
271 *parent
= NULL
, /* Parent expression */
272 *current
= NULL
,/* Current expression */
273 *parens
[100]; /* Markers for parenthesis */
274 int num_parens
= 0; /* Number of parenthesis */
275 ippfind_op_t logic
= IPPFIND_OP_AND
;
276 /* Logic for next expression */
277 int invert
= 0; /* Invert expression? */
278 int err
; /* DNS-SD error */
280 fd_set sinput
; /* Input set for select() */
281 struct timeval stimeout
; /* Timeout for select() */
282 #endif /* HAVE_DNSSD */
283 double endtime
; /* End time */
284 static const char * const ops
[] = /* Node operation names */
310 * Initialize the locale...
313 _cupsSetLocale(argv
);
316 * Create arrays to track services and things we want to browse/resolve...
319 searches
= cupsArrayNew(NULL
, NULL
);
320 services
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
323 * Parse command-line...
326 for (i
= 1; i
< argc
; i
++)
328 if (argv
[i
][0] == '-')
330 if (argv
[i
][1] == '-')
333 * Parse --option options...
336 if (!strcmp(argv
[i
], "--and"))
338 if (logic
== IPPFIND_OP_OR
)
340 _cupsLangPuts(stderr
, _("ippfind: Cannot use --and after --or."));
346 _cupsLangPuts(stderr
,
347 _("ippfind: Missing expression before \"--and\"."));
353 else if (!strcmp(argv
[i
], "--domain"))
358 _cupsLangPrintf(stderr
,
359 _("ippfind: Missing regular expression after %s."),
364 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
, argv
[i
],
366 return (IPPFIND_EXIT_MEMORY
);
368 else if (!strcmp(argv
[i
], "--exec"))
373 _cupsLangPrintf(stderr
, _("ippfind: Expected program after %s."),
378 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
380 return (IPPFIND_EXIT_MEMORY
);
383 if (!strcmp(argv
[i
], ";"))
390 _cupsLangPrintf(stderr
, _("ippfind: Expected semi-colon after %s."),
397 else if (!strcmp(argv
[i
], "--false"))
399 if ((temp
= new_expr(IPPFIND_OP_FALSE
, invert
, NULL
, NULL
,
401 return (IPPFIND_EXIT_MEMORY
);
403 else if (!strcmp(argv
[i
], "--help"))
407 else if (!strcmp(argv
[i
], "--host"))
412 _cupsLangPrintf(stderr
,
413 _("ippfind: Missing regular expression after %s."),
418 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
, argv
[i
],
420 return (IPPFIND_EXIT_MEMORY
);
422 else if (!strcmp(argv
[i
], "--ls"))
424 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
426 return (IPPFIND_EXIT_MEMORY
);
430 else if (!strcmp(argv
[i
], "--local"))
432 if ((temp
= new_expr(IPPFIND_OP_IS_LOCAL
, invert
, NULL
, NULL
,
434 return (IPPFIND_EXIT_MEMORY
);
436 else if (!strcmp(argv
[i
], "--name"))
441 _cupsLangPrintf(stderr
,
442 _("ippfind: Missing regular expression after %s."),
447 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
, argv
[i
],
449 return (IPPFIND_EXIT_MEMORY
);
451 else if (!strcmp(argv
[i
], "--not"))
455 else if (!strcmp(argv
[i
], "--or"))
459 _cupsLangPuts(stderr
,
460 _("ippfind: Missing expression before \"--or\"."));
464 logic
= IPPFIND_OP_OR
;
466 if (parent
&& parent
->op
== IPPFIND_OP_OR
)
469 * Already setup to do "foo --or bar --or baz"...
474 else if (!current
->prev
&& parent
)
477 * Change parent node into an OR node...
480 parent
->op
= IPPFIND_OP_OR
;
483 else if (!current
->prev
)
486 * Need to group "current" in a new OR node...
489 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
491 return (IPPFIND_EXIT_MEMORY
);
493 temp
->parent
= parent
;
494 temp
->child
= current
;
495 current
->parent
= temp
;
498 parent
->child
= temp
;
508 * Need to group previous expressions in an AND node, and then
509 * put that in an OR node...
512 if ((temp
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
514 return (IPPFIND_EXIT_MEMORY
);
516 while (current
->prev
)
518 current
->parent
= temp
;
519 current
= current
->prev
;
522 current
->parent
= temp
;
523 temp
->child
= current
;
526 if ((temp
= new_expr(IPPFIND_OP_OR
, 0, NULL
, NULL
,
528 return (IPPFIND_EXIT_MEMORY
);
530 temp
->parent
= parent
;
531 current
->parent
= temp
;
534 parent
->child
= temp
;
542 else if (!strcmp(argv
[i
], "--path"))
547 _cupsLangPrintf(stderr
,
548 _("ippfind: Missing regular expression after %s."),
553 if ((temp
= new_expr(IPPFIND_OP_PATH_REGEX
, invert
, NULL
, argv
[i
],
555 return (IPPFIND_EXIT_MEMORY
);
557 else if (!strcmp(argv
[i
], "--port"))
562 _cupsLangPrintf(stderr
,
563 _("ippfind: Expected port range after %s."),
568 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
], NULL
,
570 return (IPPFIND_EXIT_MEMORY
);
572 else if (!strcmp(argv
[i
], "--print"))
574 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
576 return (IPPFIND_EXIT_MEMORY
);
580 else if (!strcmp(argv
[i
], "--print-name"))
582 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
584 return (IPPFIND_EXIT_MEMORY
);
588 else if (!strcmp(argv
[i
], "--quiet"))
590 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
592 return (IPPFIND_EXIT_MEMORY
);
596 else if (!strcmp(argv
[i
], "--remote"))
598 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
600 return (IPPFIND_EXIT_MEMORY
);
602 else if (!strcmp(argv
[i
], "--true"))
604 if ((temp
= new_expr(IPPFIND_OP_TRUE
, invert
, NULL
, argv
[i
],
606 return (IPPFIND_EXIT_MEMORY
);
608 else if (!strcmp(argv
[i
], "--txt"))
613 _cupsLangPrintf(stderr
, _("ippfind: Expected key name after %s."),
618 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
], NULL
,
620 return (IPPFIND_EXIT_MEMORY
);
622 else if (!strncmp(argv
[i
], "--txt-", 5))
624 const char *key
= argv
[i
] + 5;/* TXT key */
629 _cupsLangPrintf(stderr
,
630 _("ippfind: Missing regular expression after %s."),
635 if ((temp
= new_expr(IPPFIND_OP_TXT_REGEX
, invert
, key
, argv
[i
],
637 return (IPPFIND_EXIT_MEMORY
);
639 else if (!strcmp(argv
[i
], "--uri"))
644 _cupsLangPrintf(stderr
,
645 _("ippfind: Missing regular expression after %s."),
650 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
, argv
[i
],
652 return (IPPFIND_EXIT_MEMORY
);
654 else if (!strcmp(argv
[i
], "--version"))
660 _cupsLangPrintf(stderr
, _("%s: Unknown option \"%s\"."),
668 * Add new expression...
671 if (logic
== IPPFIND_OP_AND
&&
672 current
&& current
->prev
&&
673 parent
&& parent
->op
!= IPPFIND_OP_AND
)
676 * Need to re-group "current" in a new AND node...
679 ippfind_expr_t
*tempand
; /* Temporary AND node */
681 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
683 return (IPPFIND_EXIT_MEMORY
);
686 * Replace "current" with new AND node at the end of this list...
689 current
->prev
->next
= tempand
;
690 tempand
->prev
= current
->prev
;
691 tempand
->parent
= parent
;
694 * Add "current to the new AND node...
697 tempand
->child
= current
;
698 current
->parent
= tempand
;
699 current
->prev
= NULL
;
704 * Add the new node at current level...
707 temp
->parent
= parent
;
708 temp
->prev
= current
;
711 current
->next
= temp
;
713 parent
->child
= temp
;
719 logic
= IPPFIND_OP_AND
;
729 for (opt
= argv
[i
] + 1; *opt
; opt
++)
734 address_family
= AF_INET
;
738 address_family
= AF_INET6
;
745 _cupsLangPrintf(stderr
,
746 _("ippfind: Expected port range after %s."),
751 if ((temp
= new_expr(IPPFIND_OP_PORT_RANGE
, invert
, argv
[i
],
752 NULL
, NULL
)) == NULL
)
753 return (IPPFIND_EXIT_MEMORY
);
760 _cupsLangPrintf(stderr
,
761 _("%s: Missing timeout for \"-T\"."),
766 bonjour_timeout
= atof(argv
[i
]);
773 _cupsLangPrintf(stderr
,
774 _("%s: Missing version for \"-V\"."),
780 if (!strcmp(argv
[i
], "1.1"))
782 else if (!strcmp(argv
[i
], "2.0"))
784 else if (!strcmp(argv
[i
], "2.1"))
786 else if (!strcmp(argv
[i
], "2.2"))
790 _cupsLangPrintf(stderr
, _("%s: Bad version %s for \"-V\"."),
800 _cupsLangPrintf(stderr
,
801 _("ippfind: Missing regular expression after "
806 if ((temp
= new_expr(IPPFIND_OP_DOMAIN_REGEX
, invert
, NULL
,
807 argv
[i
], NULL
)) == NULL
)
808 return (IPPFIND_EXIT_MEMORY
);
815 _cupsLangPrintf(stderr
,
816 _("ippfind: Missing regular expression after "
821 if ((temp
= new_expr(IPPFIND_OP_HOST_REGEX
, invert
, NULL
,
822 argv
[i
], NULL
)) == NULL
)
823 return (IPPFIND_EXIT_MEMORY
);
827 if ((temp
= new_expr(IPPFIND_OP_LIST
, invert
, NULL
, NULL
,
829 return (IPPFIND_EXIT_MEMORY
);
838 _cupsLangPrintf(stderr
,
839 _("ippfind: Missing regular expression after "
844 if ((temp
= new_expr(IPPFIND_OP_NAME_REGEX
, invert
, NULL
,
845 argv
[i
], NULL
)) == NULL
)
846 return (IPPFIND_EXIT_MEMORY
);
850 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, invert
, NULL
, NULL
,
852 return (IPPFIND_EXIT_MEMORY
);
858 if ((temp
= new_expr(IPPFIND_OP_QUIET
, invert
, NULL
, NULL
,
860 return (IPPFIND_EXIT_MEMORY
);
866 if ((temp
= new_expr(IPPFIND_OP_IS_REMOTE
, invert
, NULL
, NULL
,
868 return (IPPFIND_EXIT_MEMORY
);
872 if ((temp
= new_expr(IPPFIND_OP_PRINT_NAME
, invert
, NULL
, NULL
,
874 return (IPPFIND_EXIT_MEMORY
);
883 _cupsLangPrintf(stderr
,
884 _("ippfind: Missing key name after %s."),
889 if ((temp
= new_expr(IPPFIND_OP_TXT_EXISTS
, invert
, argv
[i
],
890 NULL
, NULL
)) == NULL
)
891 return (IPPFIND_EXIT_MEMORY
);
898 _cupsLangPrintf(stderr
,
899 _("ippfind: Missing regular expression after "
904 if ((temp
= new_expr(IPPFIND_OP_URI_REGEX
, invert
, NULL
,
905 argv
[i
], NULL
)) == NULL
)
906 return (IPPFIND_EXIT_MEMORY
);
913 _cupsLangPrintf(stderr
,
914 _("ippfind: Missing program after %s."),
919 if ((temp
= new_expr(IPPFIND_OP_EXEC
, invert
, NULL
, NULL
,
921 return (IPPFIND_EXIT_MEMORY
);
924 if (!strcmp(argv
[i
], ";"))
931 _cupsLangPrintf(stderr
,
932 _("ippfind: Missing semi-colon after %s."),
941 _cupsLangPrintf(stderr
, _("%s: Unknown option \"-%c\"."),
950 * Add new expression...
953 if (logic
== IPPFIND_OP_AND
&&
954 current
&& current
->prev
&&
955 parent
&& parent
->op
!= IPPFIND_OP_AND
)
958 * Need to re-group "current" in a new AND node...
961 ippfind_expr_t
*tempand
; /* Temporary AND node */
963 if ((tempand
= new_expr(IPPFIND_OP_AND
, 0, NULL
, NULL
,
965 return (IPPFIND_EXIT_MEMORY
);
968 * Replace "current" with new AND node at the end of this list...
971 current
->prev
->next
= tempand
;
972 tempand
->prev
= current
->prev
;
973 tempand
->parent
= parent
;
976 * Add "current to the new AND node...
979 tempand
->child
= current
;
980 current
->parent
= tempand
;
981 current
->prev
= NULL
;
986 * Add the new node at current level...
989 temp
->parent
= parent
;
990 temp
->prev
= current
;
993 current
->next
= temp
;
995 parent
->child
= temp
;
1001 logic
= IPPFIND_OP_AND
;
1007 else if (!strcmp(argv
[i
], "("))
1009 if (num_parens
>= 100)
1011 _cupsLangPuts(stderr
, _("ippfind: Too many parenthesis."));
1015 if ((temp
= new_expr(IPPFIND_OP_AND
, invert
, NULL
, NULL
, NULL
)) == NULL
)
1016 return (IPPFIND_EXIT_MEMORY
);
1018 parens
[num_parens
++] = temp
;
1022 temp
->parent
= current
->parent
;
1023 current
->next
= temp
;
1024 temp
->prev
= current
;
1032 logic
= IPPFIND_OP_AND
;
1034 else if (!strcmp(argv
[i
], ")"))
1036 if (num_parens
<= 0)
1038 _cupsLangPuts(stderr
, _("ippfind: Missing open parenthesis."));
1042 current
= parens
[--num_parens
];
1043 parent
= current
->parent
;
1045 logic
= IPPFIND_OP_AND
;
1047 else if (!strcmp(argv
[i
], "!"))
1054 * _regtype._tcp[,subtype][.domain]
1058 * service-name[._regtype._tcp[.domain]]
1061 cupsArrayAdd(searches
, argv
[i
]);
1067 _cupsLangPuts(stderr
, _("ippfind: Missing close parenthesis."));
1074 * Add an implicit --print-uri to the end...
1077 if ((temp
= new_expr(IPPFIND_OP_PRINT_URI
, 0, NULL
, NULL
, NULL
)) == NULL
)
1078 return (IPPFIND_EXIT_MEMORY
);
1082 while (current
->parent
)
1083 current
= current
->parent
;
1085 current
->next
= temp
;
1086 temp
->prev
= current
;
1092 if (cupsArrayCount(searches
) == 0)
1095 * Add an implicit browse for IPP printers ("_ipp._tcp")...
1098 cupsArrayAdd(searches
, "_ipp._tcp");
1101 if (getenv("IPPFIND_DEBUG"))
1103 int indent
= 4; /* Indentation */
1105 puts("Expression tree:");
1106 current
= expressions
;
1110 * Print the current node...
1113 printf("%*s%s%s\n", indent
, "", current
->invert
? "!" : "",
1117 * Advance to the next node...
1122 current
= current
->child
;
1125 else if (current
->next
)
1126 current
= current
->next
;
1127 else if (current
->parent
)
1129 while (current
->parent
)
1132 current
= current
->parent
;
1137 current
= current
->next
;
1143 puts("\nSearch items:");
1144 for (search
= (const char *)cupsArrayFirst(searches
);
1146 search
= (const char *)cupsArrayNext(searches
))
1147 printf(" %s\n", search
);
1151 * Start up browsing/resolving...
1155 if ((err
= DNSServiceCreateConnection(&dnssd_ref
)) != kDNSServiceErr_NoError
)
1157 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1158 dnssd_error_string(err
));
1159 return (IPPFIND_EXIT_BONJOUR
);
1162 #elif defined(HAVE_AVAHI)
1163 if ((avahi_poll
= avahi_simple_poll_new()) == NULL
)
1165 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1167 return (IPPFIND_EXIT_BONJOUR
);
1170 avahi_simple_poll_set_func(avahi_poll
, poll_callback
, NULL
);
1172 avahi_client
= avahi_client_new(avahi_simple_poll_get(avahi_poll
),
1173 0, client_callback
, avahi_poll
, &err
);
1176 _cupsLangPrintf(stderr
, _("ippfind: Unable to use Bonjour: %s"),
1177 dnssd_error_string(err
));
1178 return (IPPFIND_EXIT_BONJOUR
);
1180 #endif /* HAVE_DNSSD */
1182 for (search
= (const char *)cupsArrayFirst(searches
);
1184 search
= (const char *)cupsArrayNext(searches
))
1186 char buf
[1024], /* Full name string */
1187 *name
= NULL
, /* Service instance name */
1188 *regtype
, /* Registration type */
1189 *domain
; /* Domain, if any */
1191 strlcpy(buf
, search
, sizeof(buf
));
1196 else if ((regtype
= strstr(buf
, "._")) != NULL
)
1204 regtype
= "_ipp._tcp";
1207 for (domain
= regtype
; *domain
; domain
++)
1208 if (*domain
== '.' && domain
[1] != '_')
1220 * Resolve the given service instance name, regtype, and domain...
1226 service
= get_service(services
, name
, regtype
, domain
);
1229 service
->ref
= dnssd_ref
;
1230 err
= DNSServiceResolve(&(service
->ref
),
1231 kDNSServiceFlagsShareConnection
, 0, name
,
1232 regtype
, domain
, resolve_callback
,
1235 #elif defined(HAVE_AVAHI)
1236 service
->ref
= avahi_service_resolver_new(avahi_client
, AVAHI_IF_UNSPEC
,
1237 AVAHI_PROTO_UNSPEC
, name
,
1239 AVAHI_PROTO_UNSPEC
, 0,
1240 resolve_callback
, service
);
1244 err
= avahi_client_errno(avahi_client
);
1245 #endif /* HAVE_DNSSD */
1250 * Browse for services of the given type...
1254 DNSServiceRef ref
; /* Browse reference */
1257 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
, 0, regtype
,
1258 domain
, browse_callback
, services
);
1263 err
= DNSServiceBrowse(&ref
, kDNSServiceFlagsShareConnection
,
1264 kDNSServiceInterfaceIndexLocalOnly
, regtype
,
1265 domain
, browse_local_callback
, services
);
1268 #elif defined(HAVE_AVAHI)
1269 if (avahi_service_browser_new(avahi_client
, AVAHI_IF_UNSPEC
,
1270 AVAHI_PROTO_UNSPEC
, regtype
, domain
, 0,
1271 browse_callback
, services
))
1274 err
= avahi_client_errno(avahi_client
);
1275 #endif /* HAVE_DNSSD */
1280 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
1281 dnssd_error_string(err
));
1284 printf("name=\"%s\"\n", name
);
1286 printf("regtype=\"%s\"\n", regtype
);
1289 printf("domain=\"%s\"\n", domain
);
1291 return (IPPFIND_EXIT_BONJOUR
);
1296 * Process browse/resolve requests...
1299 if (bonjour_timeout
> 1.0)
1300 endtime
= get_time() + bonjour_timeout
;
1302 endtime
= get_time() + 300.0;
1304 while (get_time() < endtime
)
1306 int process
= 0; /* Process services? */
1309 int fd
= DNSServiceRefSockFD(dnssd_ref
);
1310 /* File descriptor for DNS-SD */
1313 FD_SET(fd
, &sinput
);
1315 stimeout
.tv_sec
= 0;
1316 stimeout
.tv_usec
= 500000;
1318 if (select(fd
+ 1, &sinput
, NULL
, NULL
, &stimeout
) < 0)
1321 if (FD_ISSET(fd
, &sinput
))
1324 * Process responses...
1327 DNSServiceProcessResult(dnssd_ref
);
1332 * Time to process services...
1338 #elif defined(HAVE_AVAHI)
1341 if (avahi_simple_poll_iterate(avahi_poll
, 500) > 0)
1344 * We've been told to exit the loop. Perhaps the connection to
1348 return (IPPFIND_EXIT_BONJOUR
);
1351 if (!avahi_got_data
)
1354 * Time to process services...
1359 #endif /* HAVE_DNSSD */
1364 * Process any services that we have found...
1367 int active
= 0, /* Number of active resolves */
1368 resolved
= 0, /* Number of resolved services */
1369 processed
= 0; /* Number of processed services */
1371 for (service
= (ippfind_srv_t
*)cupsArrayFirst(services
);
1373 service
= (ippfind_srv_t
*)cupsArrayNext(services
))
1375 if (service
->is_processed
)
1378 if (service
->is_resolved
)
1381 if (!service
->ref
&& !service
->is_resolved
)
1384 * Found a service, now resolve it (but limit to 50 active resolves...)
1390 service
->ref
= dnssd_ref
;
1391 err
= DNSServiceResolve(&(service
->ref
),
1392 kDNSServiceFlagsShareConnection
, 0,
1393 service
->name
, service
->regtype
,
1394 service
->domain
, resolve_callback
,
1397 #elif defined(HAVE_AVAHI)
1398 service
->ref
= avahi_service_resolver_new(avahi_client
,
1404 AVAHI_PROTO_UNSPEC
, 0,
1410 err
= avahi_client_errno(avahi_client
);
1411 #endif /* HAVE_DNSSD */
1415 _cupsLangPrintf(stderr
,
1416 _("ippfind: Unable to browse or resolve: %s"),
1417 dnssd_error_string(err
));
1418 return (IPPFIND_EXIT_BONJOUR
);
1424 else if (service
->is_resolved
&& !service
->is_processed
)
1427 * Resolved, not process this service against the expressions...
1433 DNSServiceRefDeallocate(service
->ref
);
1435 avahi_service_resolver_free(service
->ref
);
1436 #endif /* HAVE_DNSSD */
1438 service
->ref
= NULL
;
1441 if (!eval_expr(service
, expressions
))
1442 status
= IPPFIND_EXIT_FALSE
;
1444 service
->is_processed
= 1;
1446 else if (service
->ref
)
1451 * If we have processed all services we have discovered, then we are done.
1454 if (processed
== cupsArrayCount(services
) && bonjour_timeout
<= 1.0)
1460 return (IPPFIND_EXIT_BONJOUR
);
1468 * 'browse_callback()' - Browse devices.
1473 DNSServiceRef sdRef
, /* I - Service reference */
1474 DNSServiceFlags flags
, /* I - Option flags */
1475 uint32_t interfaceIndex
, /* I - Interface number */
1476 DNSServiceErrorType errorCode
, /* I - Error, if any */
1477 const char *serviceName
, /* I - Name of service/device */
1478 const char *regtype
, /* I - Type of service */
1479 const char *replyDomain
, /* I - Service domain */
1480 void *context
) /* I - Services array */
1483 * Only process "add" data...
1486 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1493 get_service((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
1498 * 'browse_local_callback()' - Browse local devices.
1502 browse_local_callback(
1503 DNSServiceRef sdRef
, /* I - Service reference */
1504 DNSServiceFlags flags
, /* I - Option flags */
1505 uint32_t interfaceIndex
, /* I - Interface number */
1506 DNSServiceErrorType errorCode
, /* I - Error, if any */
1507 const char *serviceName
, /* I - Name of service/device */
1508 const char *regtype
, /* I - Type of service */
1509 const char *replyDomain
, /* I - Service domain */
1510 void *context
) /* I - Services array */
1512 ippfind_srv_t
*service
; /* Service */
1516 * Only process "add" data...
1519 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
1526 service
= get_service((cups_array_t
*)context
, serviceName
, regtype
,
1528 service
->is_local
= 1;
1530 #endif /* HAVE_DNSSD */
1535 * 'browse_callback()' - Browse devices.
1540 AvahiServiceBrowser
*browser
, /* I - Browser */
1541 AvahiIfIndex interface
, /* I - Interface index (unused) */
1542 AvahiProtocol protocol
, /* I - Network protocol (unused) */
1543 AvahiBrowserEvent event
, /* I - What happened */
1544 const char *name
, /* I - Service name */
1545 const char *type
, /* I - Registration type */
1546 const char *domain
, /* I - Domain */
1547 AvahiLookupResultFlags flags
, /* I - Flags */
1548 void *context
) /* I - Services array */
1550 AvahiClient
*client
= avahi_service_browser_get_client(browser
);
1551 /* Client information */
1552 ippfind_srv_t
*service
; /* Service information */
1561 case AVAHI_BROWSER_FAILURE
:
1562 fprintf(stderr
, "DEBUG: browse_callback: %s\n",
1563 avahi_strerror(avahi_client_errno(client
)));
1565 avahi_simple_poll_quit(avahi_poll
);
1568 case AVAHI_BROWSER_NEW
:
1570 * This object is new on the network. Create a device entry for it if
1571 * it doesn't yet exist.
1574 service
= get_service((cups_array_t
*)context
, name
, type
, domain
);
1576 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
1577 service
->is_local
= 1;
1580 case AVAHI_BROWSER_REMOVE
:
1581 case AVAHI_BROWSER_ALL_FOR_NOW
:
1582 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
1589 * 'client_callback()' - Avahi client callback function.
1594 AvahiClient
*client
, /* I - Client information (unused) */
1595 AvahiClientState state
, /* I - Current state */
1596 void *context
) /* I - User data (unused) */
1602 * If the connection drops, quit.
1605 if (state
== AVAHI_CLIENT_FAILURE
)
1607 fputs("DEBUG: Avahi connection failed.\n", stderr
);
1609 avahi_simple_poll_quit(avahi_poll
);
1612 #endif /* HAVE_AVAHI */
1616 * 'compare_services()' - Compare two devices.
1619 static int /* O - Result of comparison */
1620 compare_services(ippfind_srv_t
*a
, /* I - First device */
1621 ippfind_srv_t
*b
) /* I - Second device */
1623 return (strcmp(a
->name
, b
->name
));
1628 * 'dnssd_error_string()' - Return an error string for an error code.
1631 static const char * /* O - Error message */
1632 dnssd_error_string(int error
) /* I - Error number */
1637 case kDNSServiceErr_NoError
:
1641 case kDNSServiceErr_Unknown
:
1642 return ("Unknown error.");
1644 case kDNSServiceErr_NoSuchName
:
1645 return ("Service not found.");
1647 case kDNSServiceErr_NoMemory
:
1648 return ("Out of memory.");
1650 case kDNSServiceErr_BadParam
:
1651 return ("Bad parameter.");
1653 case kDNSServiceErr_BadReference
:
1654 return ("Bad service reference.");
1656 case kDNSServiceErr_BadState
:
1657 return ("Bad state.");
1659 case kDNSServiceErr_BadFlags
:
1660 return ("Bad flags.");
1662 case kDNSServiceErr_Unsupported
:
1663 return ("Unsupported.");
1665 case kDNSServiceErr_NotInitialized
:
1666 return ("Not initialized.");
1668 case kDNSServiceErr_AlreadyRegistered
:
1669 return ("Already registered.");
1671 case kDNSServiceErr_NameConflict
:
1672 return ("Name conflict.");
1674 case kDNSServiceErr_Invalid
:
1675 return ("Invalid name.");
1677 case kDNSServiceErr_Firewall
:
1678 return ("Firewall prevents registration.");
1680 case kDNSServiceErr_Incompatible
:
1681 return ("Client library incompatible.");
1683 case kDNSServiceErr_BadInterfaceIndex
:
1684 return ("Bad interface index.");
1686 case kDNSServiceErr_Refused
:
1687 return ("Server prevents registration.");
1689 case kDNSServiceErr_NoSuchRecord
:
1690 return ("Record not found.");
1692 case kDNSServiceErr_NoAuth
:
1693 return ("Authentication required.");
1695 case kDNSServiceErr_NoSuchKey
:
1696 return ("Encryption key not found.");
1698 case kDNSServiceErr_NATTraversal
:
1699 return ("Unable to traverse NAT boundary.");
1701 case kDNSServiceErr_DoubleNAT
:
1702 return ("Unable to traverse double-NAT boundary.");
1704 case kDNSServiceErr_BadTime
:
1705 return ("Bad system time.");
1707 case kDNSServiceErr_BadSig
:
1708 return ("Bad signature.");
1710 case kDNSServiceErr_BadKey
:
1711 return ("Bad encryption key.");
1713 case kDNSServiceErr_Transient
:
1714 return ("Transient error occurred - please try again.");
1716 case kDNSServiceErr_ServiceNotRunning
:
1717 return ("Server not running.");
1719 case kDNSServiceErr_NATPortMappingUnsupported
:
1720 return ("NAT doesn't support NAT-PMP or UPnP.");
1722 case kDNSServiceErr_NATPortMappingDisabled
:
1723 return ("NAT supports NAT-PNP or UPnP but it is disabled.");
1725 case kDNSServiceErr_NoRouter
:
1726 return ("No Internet/default router configured.");
1728 case kDNSServiceErr_PollingMode
:
1729 return ("Service polling mode error.");
1731 case kDNSServiceErr_Timeout
:
1732 return ("Service timeout.");
1735 # elif defined(HAVE_AVAHI)
1736 return (avahi_strerror(error
));
1737 # endif /* HAVE_DNSSD */
1742 * 'eval_expr()' - Evaluate the expressions against the specified service.
1744 * Returns 1 for true and 0 for false.
1747 static int /* O - Result of evaluation */
1748 eval_expr(ippfind_srv_t
*service
, /* I - Service */
1749 ippfind_expr_t
*expressions
) /* I - Expressions */
1751 int logic
, /* Logical operation */
1752 result
; /* Result of current expression */
1753 ippfind_expr_t
*expression
; /* Current expression */
1754 const char *val
; /* TXT value */
1757 * Loop through the expressions...
1760 if (expressions
&& expressions
->parent
)
1761 logic
= expressions
->parent
->op
;
1763 logic
= IPPFIND_OP_AND
;
1765 for (expression
= expressions
; expression
; expression
= expression
->next
)
1767 switch (expression
->op
)
1770 case IPPFIND_OP_AND
:
1771 case IPPFIND_OP_OR
:
1772 if (expression
->child
)
1773 result
= eval_expr(service
, expression
->child
);
1775 result
= expression
->op
== IPPFIND_OP_AND
;
1777 case IPPFIND_OP_TRUE
:
1780 case IPPFIND_OP_FALSE
:
1783 case IPPFIND_OP_IS_LOCAL
:
1784 result
= service
->is_local
;
1786 case IPPFIND_OP_IS_REMOTE
:
1787 result
= !service
->is_local
;
1789 case IPPFIND_OP_DOMAIN_REGEX
:
1790 result
= !regexec(&(expression
->re
), service
->domain
, 0, NULL
, 0);
1792 case IPPFIND_OP_NAME_REGEX
:
1793 result
= !regexec(&(expression
->re
), service
->name
, 0, NULL
, 0);
1795 case IPPFIND_OP_HOST_REGEX
:
1796 result
= !regexec(&(expression
->re
), service
->host
, 0, NULL
, 0);
1798 case IPPFIND_OP_PORT_RANGE
:
1799 result
= service
->port
>= expression
->range
[0] &&
1800 service
->port
<= expression
->range
[1];
1802 case IPPFIND_OP_PATH_REGEX
:
1803 result
= !regexec(&(expression
->re
), service
->resource
, 0, NULL
, 0);
1805 case IPPFIND_OP_TXT_EXISTS
:
1806 result
= cupsGetOption(expression
->key
, service
->num_txt
,
1807 service
->txt
) != NULL
;
1809 case IPPFIND_OP_TXT_REGEX
:
1810 val
= cupsGetOption(expression
->key
, service
->num_txt
,
1813 result
= !regexec(&(expression
->re
), val
, 0, NULL
, 0);
1817 case IPPFIND_OP_URI_REGEX
:
1818 result
= !regexec(&(expression
->re
), service
->uri
, 0, NULL
, 0);
1820 case IPPFIND_OP_EXEC
:
1821 result
= exec_program(service
, expression
->num_args
,
1824 case IPPFIND_OP_LIST
:
1825 result
= list_service(service
);
1827 case IPPFIND_OP_PRINT_NAME
:
1828 _cupsLangPuts(stdout
, service
->name
);
1831 case IPPFIND_OP_PRINT_URI
:
1832 _cupsLangPuts(stdout
, service
->uri
);
1835 case IPPFIND_OP_QUIET
:
1840 if (expression
->invert
)
1843 if (logic
== IPPFIND_OP_AND
&& !result
)
1845 else if (logic
== IPPFIND_OP_OR
&& result
)
1849 return (logic
== IPPFIND_OP_AND
);
1854 * 'exec_program()' - Execute a program for a service.
1857 static int /* O - 1 if program terminated
1858 successfully, 0 otherwise. */
1859 exec_program(ippfind_srv_t
*service
, /* I - Service */
1860 int num_args
, /* I - Number of command-line args */
1861 char **args
) /* I - Command-line arguments */
1863 char **myargv
, /* Command-line arguments */
1864 **myenvp
, /* Environment variables */
1865 *ptr
, /* Pointer into variable */
1866 domain
[1024], /* IPPFIND_SERVICE_DOMAIN */
1867 hostname
[1024], /* IPPFIND_SERVICE_HOSTNAME */
1868 name
[256], /* IPPFIND_SERVICE_NAME */
1869 port
[32], /* IPPFIND_SERVICE_PORT */
1870 regtype
[256], /* IPPFIND_SERVICE_REGTYPE */
1871 scheme
[128], /* IPPFIND_SERVICE_SCHEME */
1872 uri
[1024], /* IPPFIND_SERVICE_URI */
1873 txt
[100][256]; /* IPPFIND_TXT_foo */
1874 int i
, /* Looping var */
1875 myenvc
, /* Number of environment variables */
1876 status
; /* Exit status of program */
1878 char program
[1024]; /* Program to execute */
1879 int pid
; /* Process ID */
1884 * Environment variables...
1887 snprintf(domain
, sizeof(domain
), "IPPFIND_SERVICE_DOMAIN=%s",
1889 snprintf(hostname
, sizeof(hostname
), "IPPFIND_SERVICE_HOSTNAME=%s",
1891 snprintf(name
, sizeof(name
), "IPPFIND_SERVICE_NAME=%s", service
->name
);
1892 snprintf(port
, sizeof(port
), "IPPFIND_SERVICE_PORT=%d", service
->port
);
1893 snprintf(regtype
, sizeof(regtype
), "IPPFIND_SERVICE_REGTYPE=%s",
1895 snprintf(scheme
, sizeof(scheme
), "IPPFIND_SERVICE_SCHEME=%s",
1896 !strncmp(service
->regtype
, "_http._tcp", 10) ? "http" :
1897 !strncmp(service
->regtype
, "_https._tcp", 11) ? "https" :
1898 !strncmp(service
->regtype
, "_ipp._tcp", 9) ? "ipp" :
1899 !strncmp(service
->regtype
, "_ipps._tcp", 10) ? "ipps" : "lpd");
1900 snprintf(uri
, sizeof(uri
), "IPPFIND_SERVICE_URI=%s", service
->uri
);
1901 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1903 snprintf(txt
[i
], sizeof(txt
[i
]), "IPPFIND_TXT_%s=%s", service
->txt
[i
].name
,
1904 service
->txt
[i
].value
);
1905 for (ptr
= txt
[i
] + 12; *ptr
&& *ptr
!= '='; ptr
++)
1906 *ptr
= _cups_toupper(*ptr
);
1909 for (i
= 0, myenvc
= 7 + service
->num_txt
; environ
[i
]; i
++)
1910 if (strncmp(environ
[i
], "IPPFIND_", 8))
1913 if ((myenvp
= calloc(sizeof(char *), myenvc
+ 1)) == NULL
)
1915 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1916 exit(IPPFIND_EXIT_MEMORY
);
1919 for (i
= 0, myenvc
= 0; environ
[i
]; i
++)
1920 if (strncmp(environ
[i
], "IPPFIND_", 8))
1921 myenvp
[myenvc
++] = environ
[i
];
1923 myenvp
[myenvc
++] = domain
;
1924 myenvp
[myenvc
++] = hostname
;
1925 myenvp
[myenvc
++] = name
;
1926 myenvp
[myenvc
++] = port
;
1927 myenvp
[myenvc
++] = regtype
;
1928 myenvp
[myenvc
++] = scheme
;
1929 myenvp
[myenvc
++] = uri
;
1931 for (i
= 0; i
< service
->num_txt
&& i
< 100; i
++)
1932 myenvp
[myenvc
++] = txt
[i
];
1935 * Allocate and copy command-line arguments...
1938 if ((myargv
= calloc(sizeof(char *), num_args
+ 1)) == NULL
)
1940 _cupsLangPuts(stderr
, _("ippfind: Out of memory."));
1941 exit(IPPFIND_EXIT_MEMORY
);
1944 for (i
= 0; i
< num_args
; i
++)
1946 if (strchr(args
[i
], '{'))
1948 char temp
[2048], /* Temporary string */
1949 *tptr
, /* Pointer into temporary string */
1950 keyword
[256], /* {keyword} */
1951 *kptr
; /* Pointer into keyword */
1953 for (ptr
= args
[i
], tptr
= temp
; *ptr
; ptr
++)
1958 * Do a {var} substitution...
1961 for (kptr
= keyword
, ptr
++; *ptr
&& *ptr
!= '}'; ptr
++)
1962 if (kptr
< (keyword
+ sizeof(keyword
) - 1))
1967 _cupsLangPuts(stderr
,
1968 _("ippfind: Missing close brace in substitution."));
1969 exit(IPPFIND_EXIT_SYNTAX
);
1973 if (!keyword
[0] || !strcmp(keyword
, "service_uri"))
1974 strlcpy(tptr
, service
->uri
, sizeof(temp
) - (tptr
- temp
));
1975 else if (!strcmp(keyword
, "service_domain"))
1976 strlcpy(tptr
, service
->domain
, sizeof(temp
) - (tptr
- temp
));
1977 else if (!strcmp(keyword
, "service_hostname"))
1978 strlcpy(tptr
, service
->host
, sizeof(temp
) - (tptr
- temp
));
1979 else if (!strcmp(keyword
, "service_name"))
1980 strlcpy(tptr
, service
->name
, sizeof(temp
) - (tptr
- temp
));
1981 else if (!strcmp(keyword
, "service_path"))
1982 strlcpy(tptr
, service
->resource
, sizeof(temp
) - (tptr
- temp
));
1983 else if (!strcmp(keyword
, "service_port"))
1984 strlcpy(tptr
, port
+ 20, sizeof(temp
) - (tptr
- temp
));
1985 else if (!strcmp(keyword
, "service_scheme"))
1986 strlcpy(tptr
, scheme
+ 22, sizeof(temp
) - (tptr
- temp
));
1987 else if (!strncmp(keyword
, "txt_", 4))
1989 if ((ptr
= (char *)cupsGetOption(keyword
+ 4, service
->num_txt
,
1990 service
->txt
)) != NULL
)
1991 strlcpy(tptr
, strdup(ptr
), sizeof(temp
) - (tptr
- temp
));
1997 _cupsLangPrintf(stderr
, _("ippfind: Unknown variable \"{%s}\"."),
1999 exit(IPPFIND_EXIT_SYNTAX
);
2002 tptr
+= strlen(tptr
);
2004 else if (tptr
< (temp
+ sizeof(temp
) - 1))
2009 myargv
[i
] = strdup(temp
);
2012 myargv
[i
] = strdup(args
[i
]);
2016 status
= _spawnvpe(_P_WAIT
, args
[0], myargv
, myenvp
);
2020 * Execute the program...
2023 if (strchr(args
[0], '/') && !access(args
[0], X_OK
))
2024 strlcpy(program
, args
[0], sizeof(program
));
2025 else if (!cupsFileFind(args
[0], getenv("PATH"), 1, program
, sizeof(program
)))
2027 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2028 args
[0], strerror(ENOENT
));
2029 exit(IPPFIND_EXIT_SYNTAX
);
2032 if (getenv("IPPFIND_DEBUG"))
2034 printf("\nProgram:\n %s\n", program
);
2035 puts("\nArguments:");
2036 for (i
= 0; i
< num_args
; i
++)
2037 printf(" %s\n", myargv
[i
]);
2038 puts("\nEnvironment:");
2039 for (i
= 0; i
< myenvc
; i
++)
2040 printf(" %s\n", myenvp
[i
]);
2043 if ((pid
= fork()) == 0)
2046 * Child comes here...
2049 execve(program
, myargv
, myenvp
);
2054 _cupsLangPrintf(stderr
, _("ippfind: Unable to execute \"%s\": %s"),
2055 args
[0], strerror(errno
));
2056 exit(IPPFIND_EXIT_SYNTAX
);
2061 * Wait for it to complete...
2064 while (wait(&status
) != pid
)
2073 for (i
= 0; i
< num_args
; i
++)
2077 * Return whether the program succeeded or crashed...
2080 return (status
== 0);
2085 * 'get_service()' - Create or update a device.
2088 static ippfind_srv_t
* /* O - Service */
2089 get_service(cups_array_t
*services
, /* I - Service array */
2090 const char *serviceName
, /* I - Name of service/device */
2091 const char *regtype
, /* I - Type of service */
2092 const char *replyDomain
) /* I - Service domain */
2094 ippfind_srv_t key
, /* Search key */
2095 *service
; /* Service */
2096 char fullName
[kDNSServiceMaxDomainName
];
2097 /* Full name for query */
2101 * See if this is a new device...
2104 key
.name
= (char *)serviceName
;
2105 key
.regtype
= (char *)regtype
;
2107 for (service
= cupsArrayFind(services
, &key
);
2109 service
= cupsArrayNext(services
))
2110 if (_cups_strcasecmp(service
->name
, key
.name
))
2112 else if (!strcmp(service
->regtype
, key
.regtype
))
2116 * Yes, add the service...
2119 service
= calloc(sizeof(ippfind_srv_t
), 1);
2120 service
->name
= strdup(serviceName
);
2121 service
->domain
= strdup(replyDomain
);
2122 service
->regtype
= strdup(regtype
);
2124 cupsArrayAdd(services
, service
);
2127 * Set the "full name" of this service, which is used for queries and
2132 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
2133 #else /* HAVE_AVAHI */
2134 avahi_service_name_join(fullName
, kDNSServiceMaxDomainName
, serviceName
,
2135 regtype
, replyDomain
);
2136 #endif /* HAVE_DNSSD */
2138 service
->fullName
= strdup(fullName
);
2145 * 'get_time()' - Get the current time-of-day in seconds.
2152 struct _timeb curtime
; /* Current Windows time */
2156 return (curtime
.time
+ 0.001 * curtime
.millitm
);
2159 struct timeval curtime
; /* Current UNIX time */
2161 if (gettimeofday(&curtime
, NULL
))
2164 return (curtime
.tv_sec
+ 0.000001 * curtime
.tv_usec
);
2170 * 'list_service()' - List the contents of a service.
2173 static int /* O - 1 if successful, 0 otherwise */
2174 list_service(ippfind_srv_t
*service
) /* I - Service */
2176 http_addrlist_t
*addrlist
; /* Address(es) of service */
2177 char port
[10]; /* Port number of service */
2180 snprintf(port
, sizeof(port
), "%d", service
->port
);
2182 if ((addrlist
= httpAddrGetList(service
->host
, address_family
, port
)) == NULL
)
2184 _cupsLangPrintf(stdout
, "%s unreachable", service
->uri
);
2188 if (!strncmp(service
->regtype
, "_ipp._tcp", 9) ||
2189 !strncmp(service
->regtype
, "_ipps._tcp", 10))
2195 http_t
*http
; /* HTTP connection */
2196 ipp_t
*request
, /* IPP request */
2197 *response
; /* IPP response */
2198 ipp_attribute_t
*attr
; /* IPP attribute */
2199 int i
, /* Looping var */
2200 count
, /* Number of values */
2201 version
, /* IPP version */
2202 paccepting
; /* printer-is-accepting-jobs value */
2203 ipp_pstate_t pstate
; /* printer-state value */
2204 char preasons
[1024], /* Comma-delimited printer-state-reasons */
2205 *ptr
, /* Pointer into reasons */
2206 *end
; /* End of reasons buffer */
2207 static const char * const rattrs
[] =/* Requested attributes */
2209 "printer-is-accepting-jobs",
2211 "printer-state-reasons"
2215 * Connect to the printer...
2218 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2219 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2220 HTTP_ENCRYPTION_ALWAYS
:
2221 HTTP_ENCRYPTION_IF_REQUESTED
,
2224 httpAddrFreeList(addrlist
);
2228 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2233 * Get the current printer state...
2237 version
= ipp_version
;
2241 request
= ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES
);
2242 ippSetVersion(request
, version
/ 10, version
% 10);
2243 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri", NULL
,
2245 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
2246 "requesting-user-name", NULL
, cupsUser());
2247 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
2248 "requested-attributes",
2249 (int)(sizeof(rattrs
) / sizeof(rattrs
[0])), NULL
, rattrs
);
2251 response
= cupsDoRequest(http
, request
, service
->resource
);
2253 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST
&& version
> 11)
2256 while (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
&& version
> 11);
2262 if (cupsLastError() > IPP_STATUS_OK_EVENTS_COMPLETE
)
2264 _cupsLangPrintf(stdout
, "%s: unavailable", service
->uri
);
2268 if ((attr
= ippFindAttribute(response
, "printer-state",
2269 IPP_TAG_ENUM
)) != NULL
)
2270 pstate
= ippGetInteger(attr
, 0);
2272 pstate
= IPP_PSTATE_STOPPED
;
2274 if ((attr
= ippFindAttribute(response
, "printer-is-accepting-jobs",
2275 IPP_TAG_BOOLEAN
)) != NULL
)
2276 paccepting
= ippGetBoolean(attr
, 0);
2280 if ((attr
= ippFindAttribute(response
, "printer-state-reasons",
2281 IPP_TAG_KEYWORD
)) != NULL
)
2283 strlcpy(preasons
, ippGetString(attr
, 0, NULL
), sizeof(preasons
));
2285 for (i
= 1, count
= ippGetCount(attr
), ptr
= preasons
+ strlen(preasons
),
2286 end
= preasons
+ sizeof(preasons
) - 1;
2287 i
< count
&& ptr
< end
;
2288 i
++, ptr
+= strlen(ptr
))
2291 strlcpy(ptr
, ippGetString(attr
, i
, NULL
), end
- ptr
+ 1);
2295 strlcpy(preasons
, "none", sizeof(preasons
));
2297 ippDelete(response
);
2300 _cupsLangPrintf(stdout
, "%s %s %s %s", service
->uri
,
2301 ippEnumString("printer-state", pstate
),
2302 paccepting
? "accepting-jobs" : "not-accepting-jobs",
2305 else if (!strncmp(service
->regtype
, "_http._tcp", 10) ||
2306 !strncmp(service
->regtype
, "_https._tcp", 11))
2309 * HTTP/HTTPS web page
2312 http_t
*http
; /* HTTP connection */
2313 http_status_t status
; /* HEAD status */
2317 * Connect to the web server...
2320 http
= httpConnect2(service
->host
, service
->port
, addrlist
, address_family
,
2321 !strncmp(service
->regtype
, "_ipps._tcp", 10) ?
2322 HTTP_ENCRYPTION_ALWAYS
:
2323 HTTP_ENCRYPTION_IF_REQUESTED
,
2326 httpAddrFreeList(addrlist
);
2330 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2334 if (httpGet(http
, service
->resource
))
2336 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2342 status
= httpUpdate(http
);
2344 while (status
== HTTP_STATUS_CONTINUE
);
2349 if (status
>= HTTP_STATUS_BAD_REQUEST
)
2351 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2355 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2357 else if (!strncmp(service
->regtype
, "_printer._tcp", 13))
2363 int sock
; /* Socket */
2366 if (!httpAddrConnect(addrlist
, &sock
))
2368 _cupsLangPrintf(stdout
, "%s unavailable", service
->uri
);
2369 httpAddrFreeList(addrlist
);
2373 _cupsLangPrintf(stdout
, "%s available", service
->uri
);
2374 httpAddrFreeList(addrlist
);
2384 _cupsLangPrintf(stdout
, "%s unsupported", service
->uri
);
2385 httpAddrFreeList(addrlist
);
2394 * 'new_expr()' - Create a new expression.
2397 static ippfind_expr_t
* /* O - New expression */
2398 new_expr(ippfind_op_t op
, /* I - Operation */
2399 int invert
, /* I - Invert result? */
2400 const char *value
, /* I - TXT key or port range */
2401 const char *regex
, /* I - Regular expression */
2402 char **args
) /* I - Pointer to argument strings */
2404 ippfind_expr_t
*temp
; /* New expression */
2407 if ((temp
= calloc(1, sizeof(ippfind_expr_t
))) == NULL
)
2411 temp
->invert
= invert
;
2413 if (op
== IPPFIND_OP_TXT_EXISTS
|| op
== IPPFIND_OP_TXT_REGEX
)
2414 temp
->key
= (char *)value
;
2415 else if (op
== IPPFIND_OP_PORT_RANGE
)
2418 * Pull port number range of the form "number", "-number" (0-number),
2419 * "number-" (number-65535), and "number-number".
2424 temp
->range
[1] = atoi(value
+ 1);
2426 else if (strchr(value
, '-'))
2428 if (sscanf(value
, "%d-%d", temp
->range
, temp
->range
+ 1) == 1)
2429 temp
->range
[1] = 65535;
2433 temp
->range
[0] = temp
->range
[1] = atoi(value
);
2439 int err
= regcomp(&(temp
->re
), regex
, REG_NOSUB
| REG_ICASE
| REG_EXTENDED
);
2443 char message
[256]; /* Error message */
2445 regerror(err
, &(temp
->re
), message
, sizeof(message
));
2446 _cupsLangPrintf(stderr
, _("ippfind: Bad regular expression: %s"),
2448 exit(IPPFIND_EXIT_SYNTAX
);
2454 int num_args
; /* Number of arguments */
2456 for (num_args
= 1; args
[num_args
]; num_args
++)
2457 if (!strcmp(args
[num_args
], ";"))
2460 temp
->num_args
= num_args
;
2461 temp
->args
= malloc(num_args
* sizeof(char *));
2462 memcpy(temp
->args
, args
, num_args
* sizeof(char *));
2471 * 'poll_callback()' - Wait for input on the specified file descriptors.
2473 * Note: This function is needed because avahi_simple_poll_iterate is broken
2474 * and always uses a timeout of 0 (!) milliseconds.
2475 * (Avahi Ticket #364)
2478 static int /* O - Number of file descriptors matching */
2480 struct pollfd
*pollfds
, /* I - File descriptors */
2481 unsigned int num_pollfds
, /* I - Number of file descriptors */
2482 int timeout
, /* I - Timeout in milliseconds (unused) */
2483 void *context
) /* I - User data (unused) */
2485 int val
; /* Return value */
2491 val
= poll(pollfds
, num_pollfds
, 500);
2498 #endif /* HAVE_AVAHI */
2502 * 'resolve_callback()' - Process resolve data.
2508 DNSServiceRef sdRef
, /* I - Service reference */
2509 DNSServiceFlags flags
, /* I - Data flags */
2510 uint32_t interfaceIndex
, /* I - Interface */
2511 DNSServiceErrorType errorCode
, /* I - Error, if any */
2512 const char *fullName
, /* I - Full service name */
2513 const char *hostTarget
, /* I - Hostname */
2514 uint16_t port
, /* I - Port number (network byte order) */
2515 uint16_t txtLen
, /* I - Length of TXT record data */
2516 const unsigned char *txtRecord
, /* I - TXT record data */
2517 void *context
) /* I - Service */
2519 char key
[256], /* TXT key value */
2520 *value
; /* Value from TXT record */
2521 const unsigned char *txtEnd
; /* End of TXT record */
2522 uint8_t valueLen
; /* Length of value */
2523 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2528 * Only process "add" data...
2531 if (errorCode
!= kDNSServiceErr_NoError
)
2533 _cupsLangPrintf(stderr
, _("ippfind: Unable to browse or resolve: %s"),
2534 dnssd_error_string(errorCode
));
2539 service
->is_resolved
= 1;
2540 service
->host
= strdup(hostTarget
);
2541 service
->port
= ntohs(port
);
2544 * Loop through the TXT key/value pairs and add them to an array...
2547 for (txtEnd
= txtRecord
+ txtLen
; txtRecord
< txtEnd
; txtRecord
+= valueLen
)
2550 * Ignore bogus strings...
2553 valueLen
= *txtRecord
++;
2555 memcpy(key
, txtRecord
, valueLen
);
2556 key
[valueLen
] = '\0';
2558 if ((value
= strchr(key
, '=')) == NULL
)
2564 * Add to array of TXT values...
2567 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2571 set_service_uri(service
);
2575 #elif defined(HAVE_AVAHI)
2578 AvahiServiceResolver
*resolver
, /* I - Resolver */
2579 AvahiIfIndex interface
, /* I - Interface */
2580 AvahiProtocol protocol
, /* I - Address protocol */
2581 AvahiResolverEvent event
, /* I - Event */
2582 const char *serviceName
,/* I - Service name */
2583 const char *regtype
, /* I - Registration type */
2584 const char *replyDomain
,/* I - Domain name */
2585 const char *hostTarget
, /* I - FQDN */
2586 const AvahiAddress
*address
, /* I - Address */
2587 uint16_t port
, /* I - Port number */
2588 AvahiStringList
*txt
, /* I - TXT records */
2589 AvahiLookupResultFlags flags
, /* I - Lookup flags */
2590 void *context
) /* I - Service */
2592 char key
[256], /* TXT key */
2593 *value
; /* TXT value */
2594 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
2596 AvahiStringList
*current
; /* Current TXT key/value pair */
2601 if (event
!= AVAHI_RESOLVER_FOUND
)
2605 avahi_service_resolver_free(resolver
);
2606 avahi_simple_poll_quit(avahi_poll
);
2610 service
->is_resolved
= 1;
2611 service
->host
= strdup(hostTarget
);
2612 service
->port
= ntohs(port
);
2615 * Loop through the TXT key/value pairs and add them to an array...
2618 for (current
= txt
; current
; current
= current
->next
)
2621 * Ignore bogus strings...
2624 if (current
->size
> (sizeof(key
) - 1))
2627 memcpy(key
, current
->text
, current
->size
);
2628 key
[current
->size
] = '\0';
2630 if ((value
= strchr(key
, '=')) == NULL
)
2636 * Add to array of TXT values...
2639 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
2643 set_service_uri(service
);
2645 #endif /* HAVE_DNSSD */
2649 * 'set_service_uri()' - Set the URI of the service.
2653 set_service_uri(ippfind_srv_t
*service
) /* I - Service */
2655 char uri
[1024]; /* URI */
2656 const char *path
, /* Resource path */
2657 *scheme
; /* URI scheme */
2660 if (!strncmp(service
->regtype
, "_http.", 6))
2663 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2665 else if (!strncmp(service
->regtype
, "_https.", 7))
2668 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
2670 else if (!strncmp(service
->regtype
, "_ipp.", 5))
2673 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2675 else if (!strncmp(service
->regtype
, "_ipps.", 6))
2678 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2680 else if (!strncmp(service
->regtype
, "_printer.", 9))
2683 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
2688 if (!path
|| !*path
)
2693 service
->resource
= strdup(path
);
2697 snprintf(uri
, sizeof(uri
), "/%s", path
);
2698 service
->resource
= strdup(uri
);
2701 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), scheme
, NULL
,
2702 service
->host
, service
->port
, service
->resource
);
2703 service
->uri
= strdup(uri
);
2708 * 'show_usage()' - Show program usage.
2714 _cupsLangPuts(stderr
, _("Usage: ippfind [options] regtype[,subtype]"
2715 "[.domain.] ... [expression]\n"
2716 " ippfind [options] name[.regtype[.domain.]] "
2717 "... [expression]\n"
2719 " ippfind --version"));
2720 _cupsLangPuts(stderr
, "");
2721 _cupsLangPuts(stderr
, _("Options:"));
2722 _cupsLangPuts(stderr
, _(" -4 Connect using IPv4."));
2723 _cupsLangPuts(stderr
, _(" -6 Connect using IPv6."));
2724 _cupsLangPuts(stderr
, _(" -T seconds Set the browse timeout in "
2726 _cupsLangPuts(stderr
, _(" -V version Set default IPP "
2728 _cupsLangPuts(stderr
, _(" --help Show this help."));
2729 _cupsLangPuts(stderr
, _(" --version Show program version."));
2730 _cupsLangPuts(stderr
, "");
2731 _cupsLangPuts(stderr
, _("Expressions:"));
2732 _cupsLangPuts(stderr
, _(" -P number[-number] Match port to number or range."));
2733 _cupsLangPuts(stderr
, _(" -d regex Match domain to regular expression."));
2734 _cupsLangPuts(stderr
, _(" -h regex Match hostname to regular expression."));
2735 _cupsLangPuts(stderr
, _(" -l List attributes."));
2736 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
2737 _cupsLangPuts(stderr
, _(" -p Print URI if true."));
2738 _cupsLangPuts(stderr
, _(" -q Quietly report match via exit code."));
2739 _cupsLangPuts(stderr
, _(" -r True if service is remote."));
2740 _cupsLangPuts(stderr
, _(" -s Print service name if true."));
2741 _cupsLangPuts(stderr
, _(" -t key True if the TXT record contains the key."));
2742 _cupsLangPuts(stderr
, _(" -u regex Match URI to regular expression."));
2743 _cupsLangPuts(stderr
, _(" -x utility [argument ...] ;\n"
2744 " Execute program if true."));
2745 _cupsLangPuts(stderr
, _(" --domain regex Match domain to regular expression."));
2746 _cupsLangPuts(stderr
, _(" --exec utility [argument ...] ;\n"
2747 " Execute program if true."));
2748 _cupsLangPuts(stderr
, _(" --host regex Match hostname to regular expression."));
2749 _cupsLangPuts(stderr
, _(" --ls List attributes."));
2750 _cupsLangPuts(stderr
, _(" --local True if service is local."));
2751 _cupsLangPuts(stderr
, _(" --name regex Match service name to regular expression."));
2752 _cupsLangPuts(stderr
, _(" --path regex Match resource path to regular expression."));
2753 _cupsLangPuts(stderr
, _(" --port number[-number] Match port to number or range."));
2754 _cupsLangPuts(stderr
, _(" --print Print URI if true."));
2755 _cupsLangPuts(stderr
, _(" --print-name Print service name if true."));
2756 _cupsLangPuts(stderr
, _(" --quiet Quietly report match via exit code."));
2757 _cupsLangPuts(stderr
, _(" --remote True if service is remote."));
2758 _cupsLangPuts(stderr
, _(" --txt key True if the TXT record contains the key."));
2759 _cupsLangPuts(stderr
, _(" --txt-* regex Match TXT record key to regular expression."));
2760 _cupsLangPuts(stderr
, _(" --uri regex Match URI to regular expression."));
2761 _cupsLangPuts(stderr
, "");
2762 _cupsLangPuts(stderr
, _("Modifiers:"));
2763 _cupsLangPuts(stderr
, _(" ( expressions ) Group expressions."));
2764 _cupsLangPuts(stderr
, _(" ! expression Unary NOT of expression."));
2765 _cupsLangPuts(stderr
, _(" --not expression Unary NOT of expression."));
2766 _cupsLangPuts(stderr
, _(" --false Always false."));
2767 _cupsLangPuts(stderr
, _(" --true Always true."));
2768 _cupsLangPuts(stderr
, _(" expression expression Logical AND."));
2769 _cupsLangPuts(stderr
, _(" expression --and expression\n"
2771 _cupsLangPuts(stderr
, _(" expression --or expression\n"
2773 _cupsLangPuts(stderr
, "");
2774 _cupsLangPuts(stderr
, _("Substitutions:"));
2775 _cupsLangPuts(stderr
, _(" {} URI"));
2776 _cupsLangPuts(stderr
, _(" {service_domain} Domain name"));
2777 _cupsLangPuts(stderr
, _(" {service_hostname} Fully-qualified domain name"));
2778 _cupsLangPuts(stderr
, _(" {service_name} Service instance name"));
2779 _cupsLangPuts(stderr
, _(" {service_port} Port number"));
2780 _cupsLangPuts(stderr
, _(" {service_regtype} DNS-SD registration type"));
2781 _cupsLangPuts(stderr
, _(" {service_scheme} URI scheme"));
2782 _cupsLangPuts(stderr
, _(" {service_uri} URI"));
2783 _cupsLangPuts(stderr
, _(" {txt_*} Value of TXT record key"));
2784 _cupsLangPuts(stderr
, "");
2785 _cupsLangPuts(stderr
, _("Environment Variables:"));
2786 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_DOMAIN Domain name"));
2787 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_HOSTNAME\n"
2788 " Fully-qualified domain name"));
2789 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_NAME Service instance name"));
2790 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_PORT Port number"));
2791 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type"));
2792 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_SCHEME URI scheme"));
2793 _cupsLangPuts(stderr
, _(" IPPFIND_SERVICE_URI URI"));
2794 _cupsLangPuts(stderr
, _(" IPPFIND_TXT_* Value of TXT record key"));
2796 exit(IPPFIND_EXIT_TRUE
);
2801 * 'show_version()' - Show program version.
2807 _cupsLangPuts(stderr
, CUPS_SVERSION
);
2809 exit(IPPFIND_EXIT_TRUE
);