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 * ./ippfind [options] regtype[,subtype][.domain.] ... [expression]
21 * ./ippfind [options] name[.regtype[.domain.]] ... [expression]
25 * Supported regtypes are:
27 * _http._tcp - HTTP (RFC 2616)
28 * _https._tcp - HTTPS (RFC 2818)
29 * _ipp._tcp - IPP (RFC 2911)
30 * _ipps._tcp - IPPS (pending)
31 * _printer._tcp - LPD (RFC 1179)
35 * 0 if result for all processed expressions is true
36 * 1 if result of any processed expression is false
37 * 2 if browsing or any query or resolution failed
38 * 3 if an undefined option or invalid expression was specified
42 * --help - Show program help
43 * --version - Show program version
44 * -4 - Use IPv4 when listing
45 * -6 - Use IPv6 when listing
46 * -T seconds - Specify browse timeout (default 10
48 * -V version - Specify IPP version (1.1, 2.0, 2.1, 2.2)
50 * "expression" is any of the following:
53 * --domain regex - True if the domain matches the given
56 * -e utility [argument ...] ;
57 * --exec utility [argument ...] ; - Executes the specified program; "{}"
58 * does a substitution (see below)
61 * --ls - Lists attributes returned by
62 * Get-Printer-Attributes for IPP printers,
63 * ???? of HEAD request for HTTP URLs)
64 * True if resource is accessible, false
67 * --local - True if the service is local to this
71 * --name regex - True if the name matches the given
74 * --path regex - True if the URI resource path matches
75 * the given regular expression.
78 * --print - Prints the URI of found printers (always
79 * true, default if -e, -l, -p, -q, or -s
83 * --quiet - Quiet mode (just return exit code)
86 * --remote - True if the service is not local to this
90 * --print-name - Prints the service name of found
94 * --txt key - True if the TXT record contains the
97 * --txt-* regex - True if the TXT record contains the
98 * named key and matches the given regular
102 * --uri regex - True if the URI matches the given
103 * regular expression.
105 * Expressions may also contain modifiers:
107 * ( expression ) - Group the result of expressions.
110 * --not expression - Unary NOT
112 * --false - Always false
113 * --true - Always true
115 * expression expression
116 * expression --and expression
117 * expression -a expression - Logical AND.
119 * expression -o expression
120 * expression --or expression - Logical OR.
122 * The substitutions for {} are:
125 * {service_domain} - Domain name
126 * {service_hostname} - FQDN
127 * {service_name} - Service name
128 * {service_port} - Port number
129 * {service_regtype} - DNS-SD registration type
130 * {service_scheme} - URI scheme for DNS-SD registration type
131 * {service_uri} - URI
132 * {txt_*} - Value of TXT record key
134 * These variables are also set in the environment for executed programs:
136 * IPPFIND_SERVICE_DOMAIN - Domain name
137 * IPPFIND_SERVICE_HOSTNAME - FQDN
138 * IPPFIND_SERVICE_NAME - Service name
139 * IPPFIND_SERVICE_PORT - Port number
140 * IPPFIND_SERVICE_REGTYPE - DNS-SD registration type
141 * IPPFIND_SERVICE_SCHEME - URI scheme for DNS-SD registration type
142 * IPPFIND_SERVICE_URI - URI
143 * IPPFIND_TXT_* - Values of TXT record key (uppercase)
150 * Include necessary headers.
153 #include <cups/cups-private.h>
157 #elif defined(HAVE_AVAHI)
158 # include <avahi-client/client.h>
159 # include <avahi-client/lookup.h>
160 # include <avahi-common/simple-watch.h>
161 # include <avahi-common/domain.h>
162 # include <avahi-common/error.h>
163 # include <avahi-common/malloc.h>
164 # define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
165 #endif /* HAVE_DNSSD */
172 typedef enum ippfind_exit_e
/* Exit codes */
174 IPPFIND_EXIT_OK
= 0, /* OK and result is true */
175 IPPFIND_EXIT_FALSE
, /* OK but result is false*/
176 IPPFIND_EXIT_BONJOUR
, /* Browse/resolve failure */
177 IPPFIND_EXIT_SYNTAX
/* Bad option or syntax error */
180 typedef enum ippfind_op_e
/* Operations for expressions */
182 IPPFIND_OP_NONE
, /* No operation */
183 IPPFIND_OP_AND
, /* Logical AND of all children */
184 IPPFIND_OP_OR
, /* Logical OR of all children */
185 IPPFIND_OP_TRUE
, /* Always true */
186 IPPFIND_OP_FALSE
, /* Always false */
187 IPPFIND_OP_DOMAIN_REGEX
, /* Domain matches regular expression */
188 IPPFIND_OP_NAME_REGEX
, /* Name matches regular expression */
189 IPPFIND_OP_PATH_REGEX
, /* Path matches regular expression */
190 IPPFIND_OP_TXT_EXISTS
, /* TXT record key exists */
191 IPPFIND_OP_TXT_REGEX
, /* TXT record key matches regular expression */
192 IPPFIND_OP_URI_REGEX
, /* URI matches regular expression */
194 IPPFIND_OP_OUTPUT
= 100, /* Output operations */
195 IPPFIND_OP_EXEC
, /* Execute when true */
196 IPPFIND_OP_LIST
, /* List when true */
197 IPPFIND_OP_PRINT_NAME
, /* Print URI when true */
198 IPPFIND_OP_PRINT_URI
, /* Print name when true */
199 IPPFIND_OP_QUIET
, /* No output when true */
202 typedef struct ippfind_expr_s
/* Expression */
204 struct ippfind_expr_s
205 *prev
, /* Previous expression */
206 *next
, /* Next expression */
207 *parent
, /* Parent expressions */
208 *child
; /* Child expressions */
209 ippfind_op_t op
; /* Operation code (see above) */
210 int invert
; /* Invert the result */
211 char *key
; /* TXT record key */
212 regex_t re
; /* Regular expression for matching */
215 typedef struct ippfind_srv_s
/* Service information */
218 DNSServiceRef ref
; /* Service reference for query */
219 #elif defined(HAVE_AVAHI)
220 AvahiServiceResolver
*ref
; /* Resolver */
221 #endif /* HAVE_DNSSD */
222 char *name
, /* Service name */
223 *domain
, /* Domain name */
224 *regtype
, /* Registration type */
225 *fullName
, /* Full name */
226 *host
, /* Hostname */
228 int num_txt
; /* Number of TXT record keys */
229 cups_option_t
*txt
; /* TXT record keys */
230 int port
, /* Port number */
231 is_local
, /* Is a local service? */
232 is_processed
, /* Did we process the service? */
233 is_resolved
; /* Got the resolve data? */
234 time_t resolve_time
; /* Time we started the resolve */
243 static DNSServiceRef dnssd_ref
; /* Master service reference */
244 #elif defined(HAVE_AVAHI)
245 static AvahiClient
*avahi_client
= NULL
;/* Client information */
246 static int avahi_got_data
= 0; /* Got data from poll? */
247 static AvahiSimplePoll
*avahi_poll
= NULL
;
248 /* Poll information */
249 #endif /* HAVE_DNSSD */
251 static int address_family
= AF_UNSPEC
;
252 /* Address family for LIST */
253 static int bonjour_error
= 0; /* Error browsing/resolving? */
254 static int ipp_version
= 20; /* IPP version for LIST */
255 static double timeout
= 10; /* Timeout in seconds */
263 static void browse_callback(DNSServiceRef sdRef
,
264 DNSServiceFlags flags
,
265 uint32_t interfaceIndex
,
266 DNSServiceErrorType errorCode
,
267 const char *serviceName
,
269 const char *replyDomain
, void *context
)
270 __attribute__((nonnull(1,5,6,7,8)));
271 static void browse_local_callback(DNSServiceRef sdRef
,
272 DNSServiceFlags flags
,
273 uint32_t interfaceIndex
,
274 DNSServiceErrorType errorCode
,
275 const char *serviceName
,
277 const char *replyDomain
,
279 __attribute__((nonnull(1,5,6,7,8)));
280 #elif defined(HAVE_AVAHI)
281 static void browse_callback(AvahiServiceBrowser
*browser
,
282 AvahiIfIndex interface
,
283 AvahiProtocol protocol
,
284 AvahiBrowserEvent event
,
285 const char *serviceName
,
287 const char *replyDomain
,
288 AvahiLookupResultFlags flags
,
290 static void client_callback(AvahiClient
*client
,
291 AvahiClientState state
,
293 #endif /* HAVE_AVAHI */
295 static int compare_services(ippfind_srv_t
*a
, ippfind_srv_t
*b
);
296 static ippfind_srv_t
*get_service(cups_array_t
*services
,
297 const char *serviceName
,
299 const char *replyDomain
)
300 __attribute__((nonnull(1,2,3,4)));
302 static void resolve_callback(DNSServiceRef sdRef
,
303 DNSServiceFlags flags
,
304 uint32_t interfaceIndex
,
305 DNSServiceErrorType errorCode
,
306 const char *fullName
,
307 const char *hostTarget
, uint16_t port
,
309 const unsigned char *txtRecord
,
311 __attribute__((nonnull(1,5,6,9, 10)));
312 #elif defined(HAVE_AVAHI)
313 static int poll_callback(struct pollfd
*pollfds
,
314 unsigned int num_pollfds
, int timeout
,
316 static void resolve_callback(AvahiServiceResolver
*res
,
317 AvahiIfIndex interface
,
318 AvahiProtocol protocol
,
319 AvahiBrowserEvent event
,
320 const char *serviceName
,
322 const char *replyDomain
,
323 const char *host_name
,
325 AvahiStringList
*txt
,
326 AvahiLookupResultFlags flags
,
328 #endif /* HAVE_DNSSD */
329 static void set_service_uri(ippfind_srv_t
*service
);
330 static void show_usage(void) __attribute__((noreturn
));
331 static void show_version(void) __attribute__((noreturn
));
332 static void unquote(char *dst
, const char *src
, size_t dstsize
)
333 __attribute__((nonnull(1,2)));
337 * 'main()' - Browse for printers.
340 int /* O - Exit status */
341 main(int argc
, /* I - Number of command-line args */
342 char *argv
[]) /* I - Command-line arguments */
344 int fd
; /* File descriptor for Bonjour */
345 cups_array_t
*services
; /* Service array */
346 ippfind_srv_t
*service
; /* Current service */
350 * Initialize the locale...
353 _cupsSetLocale(argv
);
356 * Create an array to track services...
359 services
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
362 * Start up masters for browsing/resolving...
366 if (DNSServiceCreateConnection(&dnssd_ref
) != kDNSServiceErr_NoError
)
368 perror("ERROR: Unable to create service connection");
369 return (IPPFIND_EXIT_BONJOUR
);
372 fd
= DNSServiceRefSockFD(dnssd_ref
);
374 #elif defined(HAVE_AVAHI)
375 #endif /* HAVE_DNSSD */
378 int i
; /* Looping var */
379 DNSServiceRef main_ref
, /* Main service reference */
380 ipp_ref
; /* IPP service reference */
381 int fd
; /* Main file descriptor */
382 fd_set input
; /* Input set for select() */
383 struct timeval timeout
; /* Timeout for select() */
384 cups_array_t
*devices
; /* Device array */
385 ippfind_srv_t
*device
; /* Current device */
386 http_t
*http
; /* Connection to printer */
387 ipp_t
*request
, /* Get-Printer-Attributes request */
388 *response
; /* Get-Printer-Attributes response */
389 ipp_attribute_t
*attr
; /* IPP attribute in response */
390 const char *version
, /* Version supported */
391 *testfile
; /* Test file to use */
392 int ipponly
= 0, /* Do IPP tests only? */
393 snmponly
= 0; /* Do SNMP walk only? */
396 for (i
= 1; i
< argc
; i
++)
397 if (!strcmp(argv
[i
], "snmp"))
399 else if (!strcmp(argv
[i
], "ipp"))
403 puts("Usage: ./ipp-printers [{ipp | snmp}]");
408 * Create an array to track devices...
411 devices
= cupsArrayNew((cups_array_func_t
)compare_services
, NULL
);
414 * Browse for different kinds of printers...
417 if (DNSServiceCreateConnection(&main_ref
) != kDNSServiceErr_NoError
)
419 perror("ERROR: Unable to create service connection");
423 fd
= DNSServiceRefSockFD(main_ref
);
426 DNSServiceBrowse(&ipp_ref
, kDNSServiceFlagsShareConnection
, 0,
427 "_ipp._tcp", NULL
, browse_callback
, devices
);
430 * Loop until we are killed...
441 timeout
.tv_usec
= 500000;
443 if (select(fd
+ 1, &input
, NULL
, NULL
, &timeout
) <= 0)
445 time_t curtime
= time(NULL
);
447 for (device
= (ippfind_srv_t
*)cupsArrayFirst(devices
);
449 device
= (ippfind_srv_t
*)cupsArrayNext(devices
))
450 if (!device
->got_resolve
)
455 if ((curtime
- device
->resolve_time
) > 10)
457 device
->got_resolve
= -1;
458 fprintf(stderr
, "\rUnable to resolve \"%s\": timeout\n",
470 if (FD_ISSET(fd
, &input
))
473 * Process results of our browsing...
477 DNSServiceProcessResult(main_ref
);
482 * Query any devices we've found...
485 DNSServiceErrorType status
; /* DNS query status */
486 int count
; /* Number of queries */
489 for (device
= (ippfind_srv_t
*)cupsArrayFirst(devices
), count
= 0;
491 device
= (ippfind_srv_t
*)cupsArrayNext(devices
))
493 if (!device
->ref
&& !device
->sent
)
496 * Found the device, now get the TXT record(s) for it...
501 device
->resolve_time
= time(NULL
);
502 device
->ref
= main_ref
;
504 status
= DNSServiceResolve(&(device
->ref
),
505 kDNSServiceFlagsShareConnection
,
506 0, device
->name
, device
->regtype
,
507 device
->domain
, resolve_callback
,
509 if (status
!= kDNSServiceErr_NoError
)
511 fprintf(stderr
, "\rUnable to resolve \"%s\": %d\n",
512 device
->name
, status
);
519 else if (!device
->sent
&& device
->got_resolve
)
522 * Got the TXT records, now report the device...
525 DNSServiceRefDeallocate(device
->ref
);
534 fprintf(stderr
, "\rFound %d printers. Now querying for capabilities...\n",
535 cupsArrayCount(devices
));
538 puts("#!/bin/sh -x");
539 puts("test -d results && rm -rf results");
540 puts("mkdir results");
541 puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL");
542 puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|"
543 "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER");
545 for (device
= (ippfind_srv_t
*)cupsArrayFirst(devices
);
547 device
= (ippfind_srv_t
*)cupsArrayNext(devices
))
549 if (device
->got_resolve
<= 0 || device
->cups_shared
)
553 fprintf(stderr
, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n",
554 device
->name
, device
->got_resolve
, device
->cups_shared
, device
->uri
);
556 fprintf(stderr
, "Checking \"%s\"...\n", device
->name
);
559 if ((http
= httpConnect(device
->host
, device
->port
)) == NULL
)
561 fprintf(stderr
, "Failed to connect to \"%s\": %s\n", device
->name
,
562 cupsLastErrorString());
566 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
567 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri", NULL
,
570 response
= cupsDoRequest(http
, request
, device
->rp
);
572 if (cupsLastError() > IPP_OK_SUBST
)
573 fprintf(stderr
, "Failed to query \"%s\": %s\n", device
->name
,
574 cupsLastErrorString());
577 if ((attr
= ippFindAttribute(response
, "ipp-versions-supported",
578 IPP_TAG_KEYWORD
)) != NULL
)
580 version
= attr
->values
[0].string
.text
;
582 for (i
= 1; i
< attr
->num_values
; i
++)
583 if (strcmp(attr
->values
[i
].string
.text
, version
) > 0)
584 version
= attr
->values
[i
].string
.text
;
591 if ((attr
= ippFindAttribute(response
, "document-format-supported",
592 IPP_TAG_MIMETYPE
)) != NULL
)
595 * Figure out the test file for printing, preferring PDF and PostScript
596 * over JPEG and plain text...
599 for (i
= 0; i
< attr
->num_values
; i
++)
601 if (!strcasecmp(attr
->values
[i
].string
.text
, "application/pdf"))
603 testfile
= "testfile.pdf";
606 else if (!strcasecmp(attr
->values
[i
].string
.text
,
607 "application/postscript"))
608 testfile
= "testfile.ps";
609 else if (!strcasecmp(attr
->values
[i
].string
.text
, "image/jpeg") &&
611 testfile
= "testfile.jpg";
612 else if (!strcasecmp(attr
->values
[i
].string
.text
, "text/plain") &&
614 testfile
= "testfile.txt";
615 else if (!strcasecmp(attr
->values
[i
].string
.text
,
616 "application/vnd.hp-PCL") && !testfile
)
617 testfile
= "testfile.pcl";
623 "Printer \"%s\" reports the following IPP file formats:\n",
625 for (i
= 0; i
< attr
->num_values
; i
++)
626 fprintf(stderr
, " \"%s\"\n", attr
->values
[i
].string
.text
);
630 if (!testfile
&& device
->pdl
)
632 char *pdl
, /* Copy of pdl string */
633 *start
, *end
; /* Pointers into pdl string */
636 pdl
= strdup(device
->pdl
);
637 for (start
= device
->pdl
; start
&& *start
; start
= end
)
639 if ((end
= strchr(start
, ',')) != NULL
)
642 if (!strcasecmp(start
, "application/pdf"))
644 testfile
= "testfile.pdf";
647 else if (!strcasecmp(start
, "application/postscript"))
648 testfile
= "testfile.ps";
649 else if (!strcasecmp(start
, "image/jpeg") && !testfile
)
650 testfile
= "testfile.jpg";
651 else if (!strcasecmp(start
, "text/plain") && !testfile
)
652 testfile
= "testfile.txt";
653 else if (!strcasecmp(start
, "application/vnd.hp-PCL") && !testfile
)
654 testfile
= "testfile.pcl";
661 "Using \"%s\" for printer \"%s\" based on TXT record pdl "
662 "info.\n", testfile
, device
->name
);
667 "Printer \"%s\" reports the following TXT file formats:\n",
669 fprintf(stderr
, " \"%s\"\n", device
->pdl
);
674 (attr
= ippFindAttribute(response
, "printer-make-and-model",
675 IPP_TAG_TEXT
)) != NULL
)
676 device
->ty
= strdup(attr
->values
[0].string
.text
);
678 if (strcmp(version
, "1.0") && testfile
&& device
->ty
)
680 char filename
[1024], /* Filename */
681 *fileptr
; /* Pointer into filename */
682 const char *typtr
; /* Pointer into ty */
684 if (!strncasecmp(device
->ty
, "DeskJet", 7) ||
685 !strncasecmp(device
->ty
, "DesignJet", 9) ||
686 !strncasecmp(device
->ty
, "OfficeJet", 9) ||
687 !strncasecmp(device
->ty
, "Photosmart", 10))
688 strlcpy(filename
, "HP_", sizeof(filename
));
692 fileptr
= filename
+ strlen(filename
);
694 if (!strncasecmp(device
->ty
, "Lexmark International Lexmark", 29))
695 typtr
= device
->ty
+ 22;
699 while (*typtr
&& fileptr
< (filename
+ sizeof(filename
) - 1))
701 if (isalnum(*typtr
& 255) || *typtr
== '-')
702 *fileptr
++ = *typtr
++;
712 printf("# %s\n", device
->name
);
713 printf("echo \"Testing %s...\"\n", device
->name
);
717 printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 "
718 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n",
719 device
->host
, filename
);
720 printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 "
721 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | "
722 "tee -a results/%s.snmpwalk\n",
723 device
->host
, filename
);
728 printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s "
729 "ipp-%s.test\" > results/%s.log\n", testfile
, version
,
730 device
->uri
, version
, filename
);
731 printf("CUPS_DEBUG_LOG=results/%s.debug_log "
732 "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s "
733 "ipp-%s.test | tee -a results/%s.log\n", filename
,
734 testfile
, version
, device
->uri
,
740 else if (!device
->ty
)
742 "Ignoring \"%s\" since it doesn't provide a make and model.\n",
746 "Ignoring \"%s\" since it does not support a common format.\n",
749 fprintf(stderr
, "Ignoring \"%s\" since it only supports IPP/1.0.\n",
764 * 'browse_callback()' - Browse devices.
769 DNSServiceRef sdRef
, /* I - Service reference */
770 DNSServiceFlags flags
, /* I - Option flags */
771 uint32_t interfaceIndex
, /* I - Interface number */
772 DNSServiceErrorType errorCode
, /* I - Error, if any */
773 const char *serviceName
, /* I - Name of service/device */
774 const char *regtype
, /* I - Type of service */
775 const char *replyDomain
, /* I - Service domain */
776 void *context
) /* I - Services array */
779 * Only process "add" data...
782 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
789 get_service((cups_array_t
*)context
, serviceName
, regtype
, replyDomain
);
794 * 'browse_local_callback()' - Browse local devices.
798 browse_local_callback(
799 DNSServiceRef sdRef
, /* I - Service reference */
800 DNSServiceFlags flags
, /* I - Option flags */
801 uint32_t interfaceIndex
, /* I - Interface number */
802 DNSServiceErrorType errorCode
, /* I - Error, if any */
803 const char *serviceName
, /* I - Name of service/device */
804 const char *regtype
, /* I - Type of service */
805 const char *replyDomain
, /* I - Service domain */
806 void *context
) /* I - Services array */
808 ippfind_srv_t
*service
; /* Service */
812 * Only process "add" data...
815 if (errorCode
!= kDNSServiceErr_NoError
|| !(flags
& kDNSServiceFlagsAdd
))
822 service
= get_service((cups_array_t
*)context
, serviceName
, regtype
,
824 service
->is_local
= 1;
826 #endif /* HAVE_DNSSD */
831 * 'browse_callback()' - Browse devices.
836 AvahiServiceBrowser
*browser
, /* I - Browser */
837 AvahiIfIndex interface
, /* I - Interface index (unused) */
838 AvahiProtocol protocol
, /* I - Network protocol (unused) */
839 AvahiBrowserEvent event
, /* I - What happened */
840 const char *name
, /* I - Service name */
841 const char *type
, /* I - Registration type */
842 const char *domain
, /* I - Domain */
843 AvahiLookupResultFlags flags
, /* I - Flags */
844 void *context
) /* I - Services array */
846 AvahiClient
*client
= avahi_service_browser_get_client(browser
);
847 /* Client information */
848 ippfind_srv_t
*service
; /* Service information */
857 case AVAHI_BROWSER_FAILURE
:
858 fprintf(stderr
, "DEBUG: browse_callback: %s\n",
859 avahi_strerror(avahi_client_errno(client
)));
861 avahi_simple_poll_quit(simple_poll
);
864 case AVAHI_BROWSER_NEW
:
866 * This object is new on the network. Create a device entry for it if
867 * it doesn't yet exist.
870 service
= get_service((cups_array_t
*)context
, name
, type
, domain
);
872 if (flags
& AVAHI_LOOKUP_RESULT_LOCAL
)
873 service
->is_local
= 1;
876 case AVAHI_BROWSER_REMOVE
:
877 case AVAHI_BROWSER_ALL_FOR_NOW
:
878 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
885 * 'client_callback()' - Avahi client callback function.
890 AvahiClient
*client
, /* I - Client information (unused) */
891 AvahiClientState state
, /* I - Current state */
892 void *context
) /* I - User data (unused) */
898 * If the connection drops, quit.
901 if (state
== AVAHI_CLIENT_FAILURE
)
903 fputs("DEBUG: Avahi connection failed.\n", stderr
);
905 avahi_simple_poll_quit(avahi_poll
);
908 #endif /* HAVE_AVAHI */
912 * 'compare_services()' - Compare two devices.
915 static int /* O - Result of comparison */
916 compare_services(ippfind_srv_t
*a
, /* I - First device */
917 ippfind_srv_t
*b
) /* I - Second device */
919 return (strcmp(a
->name
, b
->name
));
924 * 'get_service()' - Create or update a device.
927 static ippfind_srv_t
* /* O - Service */
928 get_service(cups_array_t
*services
, /* I - Service array */
929 const char *serviceName
, /* I - Name of service/device */
930 const char *regtype
, /* I - Type of service */
931 const char *replyDomain
) /* I - Service domain */
933 ippfind_srv_t key
, /* Search key */
934 *service
; /* Service */
935 char fullName
[kDNSServiceMaxDomainName
];
936 /* Full name for query */
940 * See if this is a new device...
943 key
.name
= (char *)serviceName
;
944 key
.regtype
= (char *)regtype
;
946 for (service
= cupsArrayFind(services
, &key
);
948 service
= cupsArrayNext(services
))
949 if (_cups_strcasecmp(service
->name
, key
.name
))
951 else if (!strcmp(service
->regtype
, key
.regtype
))
955 * Yes, add the service...
958 service
= calloc(sizeof(ippfind_srv_t
), 1);
959 service
->name
= strdup(serviceName
);
960 service
->domain
= strdup(replyDomain
);
961 service
->regtype
= strdup(regtype
);
963 cupsArrayAdd(services
, service
);
966 * Set the "full name" of this service, which is used for queries and
971 DNSServiceConstructFullName(fullName
, serviceName
, regtype
, replyDomain
);
972 #else /* HAVE_AVAHI */
973 avahi_service_name_join(fullName
, kDNSServiceMaxDomainName
, serviceName
,
974 regtype
, replyDomain
);
975 #endif /* HAVE_DNSSD */
977 service
->fullName
= strdup(fullName
);
985 * 'poll_callback()' - Wait for input on the specified file descriptors.
987 * Note: This function is needed because avahi_simple_poll_iterate is broken
988 * and always uses a timeout of 0 (!) milliseconds.
989 * (Avahi Ticket #364)
992 static int /* O - Number of file descriptors matching */
994 struct pollfd
*pollfds
, /* I - File descriptors */
995 unsigned int num_pollfds
, /* I - Number of file descriptors */
996 int timeout
, /* I - Timeout in milliseconds (unused) */
997 void *context
) /* I - User data (unused) */
999 int val
; /* Return value */
1005 val
= poll(pollfds
, num_pollfds
, 500);
1008 fprintf(stderr
, "DEBUG: poll_callback: %s\n", strerror(errno
));
1014 #endif /* HAVE_AVAHI */
1018 * 'resolve_callback()' - Process resolve data.
1024 DNSServiceRef sdRef
, /* I - Service reference */
1025 DNSServiceFlags flags
, /* I - Data flags */
1026 uint32_t interfaceIndex
, /* I - Interface */
1027 DNSServiceErrorType errorCode
, /* I - Error, if any */
1028 const char *fullName
, /* I - Full service name */
1029 const char *hostTarget
, /* I - Hostname */
1030 uint16_t port
, /* I - Port number (network byte order) */
1031 uint16_t txtLen
, /* I - Length of TXT record data */
1032 const unsigned char *txtRecord
, /* I - TXT record data */
1033 void *context
) /* I - Service */
1035 char key
[256], /* TXT key value */
1036 *value
; /* Value from TXT record */
1037 const unsigned char *txtEnd
; /* End of TXT record */
1038 uint8_t valueLen
; /* Length of value */
1039 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
1044 * Only process "add" data...
1047 if (errorCode
!= kDNSServiceErr_NoError
)
1050 service
->is_resolved
= 1;
1051 service
->host
= strdup(hostTarget
);
1052 service
->port
= ntohs(port
);
1055 * Loop through the TXT key/value pairs and add them to an array...
1058 for (txtEnd
= txtRecord
+ txtLen
; txtRecord
< txtEnd
; txtRecord
+= valueLen
)
1061 * Ignore bogus strings...
1064 valueLen
= *txtRecord
++;
1066 memcpy(key
, txtRecord
, valueLen
);
1067 key
[valueLen
] = '\0';
1069 if ((value
= strchr(key
, '=')) == NULL
)
1075 * Add to array of TXT values...
1078 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
1082 set_service_uri(service
);
1086 #elif defined(HAVE_AVAHI)
1089 AvahiServiceResolver
*resolver
, /* I - Resolver */
1090 AvahiIfIndex interface
, /* I - Interface */
1091 AvahiProtocol protocol
, /* I - Address protocol */
1092 AvahiBrowserEvent event
, /* I - Event */
1093 const char *serviceName
,/* I - Service name */
1094 const char *regtype
, /* I - Registration type */
1095 const char *replyDomain
,/* I - Domain name */
1096 const char *hostTarget
, /* I - FQDN */
1097 uint16_t port
, /* I - Port number */
1098 AvahiStringList
*txt
, /* I - TXT records */
1099 AvahiLookupResultFlags flags
, /* I - Lookup flags */
1100 void *context
) /* I - Service */
1102 char uri
[1024]; /* URI */
1103 key
[256], /* TXT key */
1104 *value
; /* TXT value */
1105 ippfind_srv_t
*service
= (ippfind_srv_t
*)context
;
1107 AvahiStringList
*current
; /* Current TXT key/value pair */
1110 if (event
!= AVAHI_RESOLVER_FOUND
)
1114 avahi_service_resolver_free(resolver
);
1115 avahi_simple_poll_quit(uribuf
->poll
);
1119 service
->is_resolved
= 1;
1120 service
->host
= strdup(hostTarget
);
1121 service
->port
= ntohs(port
);
1124 * Loop through the TXT key/value pairs and add them to an array...
1127 for (current
= txt
; current
; current
= current
->next
)
1130 * Ignore bogus strings...
1133 if (current
->size
> (sizeof(key
) - 1))
1136 memcpy(key
, current
->text
, current
->size
);
1137 key
[current
->size
] = '\0';
1139 if ((value
= strchr(key
, '=')) == NULL
)
1145 * Add to array of TXT values...
1148 service
->num_txt
= cupsAddOption(key
, value
, service
->num_txt
,
1152 set_service_uri(service
);
1154 #endif /* HAVE_DNSSD */
1158 * 'set_service_uri()' - Set the URI of the service.
1162 set_service_uri(ippfind_srv_t
*service
) /* I - Service */
1164 char uri
[1024]; /* URI */
1165 const char *path
, /* Resource path */
1166 *scheme
; /* URI scheme */
1169 if (!strncmp(service
->regtype
, "_http.", 6))
1172 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
1174 else if (!strncmp(service
->regtype
, "_https.", 7))
1177 path
= cupsGetOption("path", service
->num_txt
, service
->txt
);
1179 else if (!strncmp(service
->regtype
, "_ipp.", 5))
1182 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
1184 else if (!strncmp(service
->regtype
, "_ipps.", 6))
1187 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
1189 else if (!strncmp(service
->regtype
, "_printer.", 9))
1192 path
= cupsGetOption("rp", service
->num_txt
, service
->txt
);
1197 if (!path
|| !*path
)
1201 httpAssembleURI(HTTP_URI_CODING_ALL
, uri
, sizeof(URI
), scheme
, NULL
,
1202 service
->host
, service
->port
, path
);
1204 httpAssembleURIf(HTTP_URI_CODING_ALL
, uri
, sizeof(URI
), scheme
, NULL
,
1205 service
->host
, service
->port
, "/%s", path
);
1207 service
->uri
= strdup(uri
);
1212 * 'usage()' - Show program usage.
1218 _cupsLangPuts(stderr
, _("Usage: ippfind [options] regtype[,subtype]"
1219 "[.domain.] ... [expression]\n"
1220 " ippfind [options] name[.regtype[.domain.]] "
1221 "... [expression]\n"
1223 " ippfind --version"));
1224 _cupsLangPuts(stderr
, "");
1225 _cupsLangPuts(stderr
, _("Options:"));
1226 _cupsLangPuts(stderr
, _(" -4 Connect using IPv4."));
1227 _cupsLangPuts(stderr
, _(" -6 Connect using IPv6."));
1228 _cupsLangPuts(stderr
, _(" -T seconds Set the browse timeout in "
1230 _cupsLangPuts(stderr
, _(" -V version Set default IPP "
1232 _cupsLangPuts(stderr
, _(" --help Show this help."));
1233 _cupsLangPuts(stderr
, _(" --version Show program version."));
1234 _cupsLangPuts(stderr
, "");
1235 _cupsLangPuts(stderr
, _("Expressions:"));
1236 _cupsLangPuts(stderr
, _(" -d regex Match domain to regular expression."));
1237 _cupsLangPuts(stderr
, _(" -e utility [argument ...] ;\n"
1238 " Execute program if true."));
1239 _cupsLangPuts(stderr
, _(" -l List attributes."));
1240 _cupsLangPuts(stderr
, _(" --local True if service is local."));
1241 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1242 _cupsLangPuts(stderr
, _(" --path regex Match resource path to regular expression."));
1243 _cupsLangPuts(stderr
, _(" -p Print URI if true."));
1244 _cupsLangPuts(stderr
, _(" -q Quietly report match via exit code."));
1245 _cupsLangPuts(stderr
, _(" -r True if service is remote."));
1247 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1248 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1249 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1250 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1251 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1252 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1253 _cupsLangPuts(stderr
, _(" -n regex Match service name to regular expression."));
1254 _cupsLangPuts(stderr
, _(" -d name=value Set named variable to "
1256 _cupsLangPuts(stderr
, _(" -f filename Set default request "
1258 _cupsLangPuts(stderr
, _(" -i seconds Repeat the last file with "
1259 "the given time interval."));
1260 _cupsLangPuts(stderr
, _(" -n count Repeat the last file the "
1261 "given number of times."));
1262 _cupsLangPuts(stderr
, _(" -q Run silently."));
1263 _cupsLangPuts(stderr
, _(" -t Produce a test report."));
1264 _cupsLangPuts(stderr
, _(" -v Be verbose."));
1266 exit(IPPFIND_EXIT_OK
);
1271 * 'show_version()' - Show program version.
1277 _cupsLangPuts(stderr
, CUPS_SVERSION
);
1279 exit(IPPFIND_EXIT_OK
);
1284 * 'unquote()' - Unquote a name string.
1288 unquote(char *dst
, /* I - Destination buffer */
1289 const char *src
, /* I - Source string */
1290 size_t dstsize
) /* I - Size of destination buffer */
1292 char *dstend
= dst
+ dstsize
- 1; /* End of destination buffer */
1295 while (*src
&& dst
< dstend
)
1300 if (isdigit(src
[0] & 255) && isdigit(src
[1] & 255) &&
1301 isdigit(src
[2] & 255))
1303 *dst
++ = ((((src
[0] - '0') * 10) + src
[1] - '0') * 10) + src
[2] - '0';