]> git.ipfire.org Git - thirdparty/cups.git/blame - test/ippfind.c
Save work on new ippfind tool.
[thirdparty/cups.git] / test / ippfind.c
CommitLineData
bac07992
MS
1/*
2 * "$Id$"
3 *
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.
7 *
8 * Copyright 2008-2013 by Apple Inc.
9 *
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/".
15 *
16 * This file is subject to the Apple OS-Developed Software exception.
17 *
18 * Usage:
19 *
20 * ./ippfind [options] regtype[,subtype][.domain.] ... [expression]
21 * ./ippfind [options] name[.regtype[.domain.]] ... [expression]
22 * ./ippfind --help
23 * ./ippfind --version
24 *
25 * Supported regtypes are:
26 *
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)
32 *
33 * Exit Codes:
34 *
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
39 *
40 * Options:
41 *
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
47 * seconds)
48 * -V version - Specify IPP version (1.1, 2.0, 2.1, 2.2)
49 *
50 * "expression" is any of the following:
51 *
52 * -d regex
53 * --domain regex - True if the domain matches the given
54 * regular expression.
55 *
56 * -e utility [argument ...] ;
57 * --exec utility [argument ...] ; - Executes the specified program; "{}"
58 * does a substitution (see below)
59 *
60 * -l
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
65 * otherwise.
66 *
67 * --local - True if the service is local to this
68 * computer.
69 *
70 * -n regex
71 * --name regex - True if the name matches the given
72 * regular expression.
73 *
74 * --path regex - True if the URI resource path matches
75 * the given regular expression.
76 *
77 * -p
78 * --print - Prints the URI of found printers (always
79 * true, default if -e, -l, -p, -q, or -s
80 * is not specified.
81 *
82 * -q
83 * --quiet - Quiet mode (just return exit code)
84 *
85 * -r
86 * --remote - True if the service is not local to this
87 * computer.
88 *
89 * -s
90 * --print-name - Prints the service name of found
91 * printers.
92 *
93 * -t key
94 * --txt key - True if the TXT record contains the
95 * named key
96 *
97 * --txt-* regex - True if the TXT record contains the
98 * named key and matches the given regular
99 * expression.
100 *
101 * -u regex
102 * --uri regex - True if the URI matches the given
103 * regular expression.
104 *
105 * Expressions may also contain modifiers:
106 *
107 * ( expression ) - Group the result of expressions.
108 *
109 * ! expression
110 * --not expression - Unary NOT
111 *
112 * --false - Always false
113 * --true - Always true
114 *
115 * expression expression
116 * expression --and expression
117 * expression -a expression - Logical AND.
118 *
119 * expression -o expression
120 * expression --or expression - Logical OR.
121 *
122 * The substitutions for {} are:
123 *
124 * {} - URI
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
133 *
134 * These variables are also set in the environment for executed programs:
135 *
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)
144 *
145 * Contents:
146 *
147 */
148
149/*
150 * Include necessary headers.
151 */
152
153#include <cups/cups-private.h>
154#include <regex.h>
155#ifdef HAVE_DNSSD
156# include <dns_sd.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 */
166
167
168/*
169 * Structures...
170 */
171
172typedef enum ippfind_exit_e /* Exit codes */
173{
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 */
178} ippfind_exit_t;
179
180typedef enum ippfind_op_e /* Operations for expressions */
181{
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 */
193
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 */
200} ippfind_op_t;
201
202typedef struct ippfind_expr_s /* Expression */
203{
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 */
213} ippfind_expr_t;
214
215typedef struct ippfind_srv_s /* Service information */
216{
217#ifdef HAVE_DNSSD
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 */
227 *uri; /* URI */
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 */
235} ippfind_srv_t;
236
237
238/*
239 * Local globals...
240 */
241
242#ifdef HAVE_DNSSD
243static DNSServiceRef dnssd_ref; /* Master service reference */
244#elif defined(HAVE_AVAHI)
245static AvahiClient *avahi_client = NULL;/* Client information */
246static int avahi_got_data = 0; /* Got data from poll? */
247static AvahiSimplePoll *avahi_poll = NULL;
248 /* Poll information */
249#endif /* HAVE_DNSSD */
250
251static int address_family = AF_UNSPEC;
252 /* Address family for LIST */
253static int bonjour_error = 0; /* Error browsing/resolving? */
254static int ipp_version = 20; /* IPP version for LIST */
255static double timeout = 10; /* Timeout in seconds */
256
257
258/*
259 * Local functions...
260 */
261
262#ifdef HAVE_DNSSD
263static void browse_callback(DNSServiceRef sdRef,
264 DNSServiceFlags flags,
265 uint32_t interfaceIndex,
266 DNSServiceErrorType errorCode,
267 const char *serviceName,
268 const char *regtype,
269 const char *replyDomain, void *context)
270 __attribute__((nonnull(1,5,6,7,8)));
271static void browse_local_callback(DNSServiceRef sdRef,
272 DNSServiceFlags flags,
273 uint32_t interfaceIndex,
274 DNSServiceErrorType errorCode,
275 const char *serviceName,
276 const char *regtype,
277 const char *replyDomain,
278 void *context)
279 __attribute__((nonnull(1,5,6,7,8)));
280#elif defined(HAVE_AVAHI)
281static void browse_callback(AvahiServiceBrowser *browser,
282 AvahiIfIndex interface,
283 AvahiProtocol protocol,
284 AvahiBrowserEvent event,
285 const char *serviceName,
286 const char *regtype,
287 const char *replyDomain,
288 AvahiLookupResultFlags flags,
289 void *context);
290static void client_callback(AvahiClient *client,
291 AvahiClientState state,
292 void *context);
293#endif /* HAVE_AVAHI */
294
295static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b);
296static ippfind_srv_t *get_service(cups_array_t *services,
297 const char *serviceName,
298 const char *regtype,
299 const char *replyDomain)
300 __attribute__((nonnull(1,2,3,4)));
301#ifdef HAVE_DNSSD
302static 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,
308 uint16_t txtLen,
309 const unsigned char *txtRecord,
310 void *context);
311 __attribute__((nonnull(1,5,6,9, 10)));
312#elif defined(HAVE_AVAHI)
313static int poll_callback(struct pollfd *pollfds,
314 unsigned int num_pollfds, int timeout,
315 void *context);
316static void resolve_callback(AvahiServiceResolver *res,
317 AvahiIfIndex interface,
318 AvahiProtocol protocol,
319 AvahiBrowserEvent event,
320 const char *serviceName,
321 const char *regtype,
322 const char *replyDomain,
323 const char *host_name,
324 uint16_t port,
325 AvahiStringList *txt,
326 AvahiLookupResultFlags flags,
327 void *context);
328#endif /* HAVE_DNSSD */
329static void set_service_uri(ippfind_srv_t *service);
330static void show_usage(void) __attribute__((noreturn));
331static void show_version(void) __attribute__((noreturn));
332static void unquote(char *dst, const char *src, size_t dstsize)
333 __attribute__((nonnull(1,2)));
334
335
336/*
337 * 'main()' - Browse for printers.
338 */
339
340int /* O - Exit status */
341main(int argc, /* I - Number of command-line args */
342 char *argv[]) /* I - Command-line arguments */
343{
344 int fd; /* File descriptor for Bonjour */
345 cups_array_t *services; /* Service array */
346 ippfind_srv_t *service; /* Current service */
347
348
349 /*
350 * Initialize the locale...
351 */
352
353 _cupsSetLocale(argv);
354
355 /*
356 * Create an array to track services...
357 */
358
359 services = cupsArrayNew((cups_array_func_t)compare_services, NULL);
360
361 /*
362 * Start up masters for browsing/resolving...
363 */
364
365#ifdef HAVE_DNSSD
366 if (DNSServiceCreateConnection(&dnssd_ref) != kDNSServiceErr_NoError)
367 {
368 perror("ERROR: Unable to create service connection");
369 return (IPPFIND_EXIT_BONJOUR);
370 }
371
372 fd = DNSServiceRefSockFD(dnssd_ref);
373
374#elif defined(HAVE_AVAHI)
375#endif /* HAVE_DNSSD */
376
377#if 0
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? */
394
395
396 for (i = 1; i < argc; i ++)
397 if (!strcmp(argv[i], "snmp"))
398 snmponly = 1;
399 else if (!strcmp(argv[i], "ipp"))
400 ipponly = 1;
401 else
402 {
403 puts("Usage: ./ipp-printers [{ipp | snmp}]");
404 return (1);
405 }
406
407 /*
408 * Create an array to track devices...
409 */
410
411 devices = cupsArrayNew((cups_array_func_t)compare_services, NULL);
412
413 /*
414 * Browse for different kinds of printers...
415 */
416
417 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
418 {
419 perror("ERROR: Unable to create service connection");
420 return (1);
421 }
422
423 fd = DNSServiceRefSockFD(main_ref);
424
425 ipp_ref = main_ref;
426 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
427 "_ipp._tcp", NULL, browse_callback, devices);
428
429 /*
430 * Loop until we are killed...
431 */
432
433 progress();
434
435 for (;;)
436 {
437 FD_ZERO(&input);
438 FD_SET(fd, &input);
439
440 timeout.tv_sec = 2;
441 timeout.tv_usec = 500000;
442
443 if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0)
444 {
445 time_t curtime = time(NULL);
446
447 for (device = (ippfind_srv_t *)cupsArrayFirst(devices);
448 device;
449 device = (ippfind_srv_t *)cupsArrayNext(devices))
450 if (!device->got_resolve)
451 {
452 if (!device->ref)
453 break;
454
455 if ((curtime - device->resolve_time) > 10)
456 {
457 device->got_resolve = -1;
458 fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n",
459 device->name);
460 progress();
461 }
462 else
463 break;
464 }
465
466 if (!device)
467 break;
468 }
469
470 if (FD_ISSET(fd, &input))
471 {
472 /*
473 * Process results of our browsing...
474 */
475
476 progress();
477 DNSServiceProcessResult(main_ref);
478 }
479 else
480 {
481 /*
482 * Query any devices we've found...
483 */
484
485 DNSServiceErrorType status; /* DNS query status */
486 int count; /* Number of queries */
487
488
489 for (device = (ippfind_srv_t *)cupsArrayFirst(devices), count = 0;
490 device;
491 device = (ippfind_srv_t *)cupsArrayNext(devices))
492 {
493 if (!device->ref && !device->sent)
494 {
495 /*
496 * Found the device, now get the TXT record(s) for it...
497 */
498
499 if (count < 50)
500 {
501 device->resolve_time = time(NULL);
502 device->ref = main_ref;
503
504 status = DNSServiceResolve(&(device->ref),
505 kDNSServiceFlagsShareConnection,
506 0, device->name, device->regtype,
507 device->domain, resolve_callback,
508 device);
509 if (status != kDNSServiceErr_NoError)
510 {
511 fprintf(stderr, "\rUnable to resolve \"%s\": %d\n",
512 device->name, status);
513 progress();
514 }
515 else
516 count ++;
517 }
518 }
519 else if (!device->sent && device->got_resolve)
520 {
521 /*
522 * Got the TXT records, now report the device...
523 */
524
525 DNSServiceRefDeallocate(device->ref);
526 device->ref = 0;
527 device->sent = 1;
528 }
529 }
530 }
531 }
532
533#ifndef DEBUG
534 fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n",
535 cupsArrayCount(devices));
536#endif /* !DEBUG */
537
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");
544
545 for (device = (ippfind_srv_t *)cupsArrayFirst(devices);
546 device;
547 device = (ippfind_srv_t *)cupsArrayNext(devices))
548 {
549 if (device->got_resolve <= 0 || device->cups_shared)
550 continue;
551
552#ifdef DEBUG
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);
555#else
556 fprintf(stderr, "Checking \"%s\"...\n", device->name);
557#endif /* DEBUG */
558
559 if ((http = httpConnect(device->host, device->port)) == NULL)
560 {
561 fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name,
562 cupsLastErrorString());
563 continue;
564 }
565
566 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
567 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
568 device->uri);
569
570 response = cupsDoRequest(http, request, device->rp);
571
572 if (cupsLastError() > IPP_OK_SUBST)
573 fprintf(stderr, "Failed to query \"%s\": %s\n", device->name,
574 cupsLastErrorString());
575 else
576 {
577 if ((attr = ippFindAttribute(response, "ipp-versions-supported",
578 IPP_TAG_KEYWORD)) != NULL)
579 {
580 version = attr->values[0].string.text;
581
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;
585 }
586 else
587 version = "1.0";
588
589 testfile = NULL;
590
591 if ((attr = ippFindAttribute(response, "document-format-supported",
592 IPP_TAG_MIMETYPE)) != NULL)
593 {
594 /*
595 * Figure out the test file for printing, preferring PDF and PostScript
596 * over JPEG and plain text...
597 */
598
599 for (i = 0; i < attr->num_values; i ++)
600 {
601 if (!strcasecmp(attr->values[i].string.text, "application/pdf"))
602 {
603 testfile = "testfile.pdf";
604 break;
605 }
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") &&
610 !testfile)
611 testfile = "testfile.jpg";
612 else if (!strcasecmp(attr->values[i].string.text, "text/plain") &&
613 !testfile)
614 testfile = "testfile.txt";
615 else if (!strcasecmp(attr->values[i].string.text,
616 "application/vnd.hp-PCL") && !testfile)
617 testfile = "testfile.pcl";
618 }
619
620 if (!testfile)
621 {
622 fprintf(stderr,
623 "Printer \"%s\" reports the following IPP file formats:\n",
624 device->name);
625 for (i = 0; i < attr->num_values; i ++)
626 fprintf(stderr, " \"%s\"\n", attr->values[i].string.text);
627 }
628 }
629
630 if (!testfile && device->pdl)
631 {
632 char *pdl, /* Copy of pdl string */
633 *start, *end; /* Pointers into pdl string */
634
635
636 pdl = strdup(device->pdl);
637 for (start = device->pdl; start && *start; start = end)
638 {
639 if ((end = strchr(start, ',')) != NULL)
640 *end++ = '\0';
641
642 if (!strcasecmp(start, "application/pdf"))
643 {
644 testfile = "testfile.pdf";
645 break;
646 }
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";
655 }
656 free(pdl);
657
658 if (testfile)
659 {
660 fprintf(stderr,
661 "Using \"%s\" for printer \"%s\" based on TXT record pdl "
662 "info.\n", testfile, device->name);
663 }
664 else
665 {
666 fprintf(stderr,
667 "Printer \"%s\" reports the following TXT file formats:\n",
668 device->name);
669 fprintf(stderr, " \"%s\"\n", device->pdl);
670 }
671 }
672
673 if (!device->ty &&
674 (attr = ippFindAttribute(response, "printer-make-and-model",
675 IPP_TAG_TEXT)) != NULL)
676 device->ty = strdup(attr->values[0].string.text);
677
678 if (strcmp(version, "1.0") && testfile && device->ty)
679 {
680 char filename[1024], /* Filename */
681 *fileptr; /* Pointer into filename */
682 const char *typtr; /* Pointer into ty */
683
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));
689 else
690 filename[0] = '\0';
691
692 fileptr = filename + strlen(filename);
693
694 if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29))
695 typtr = device->ty + 22;
696 else
697 typtr = device->ty;
698
699 while (*typtr && fileptr < (filename + sizeof(filename) - 1))
700 {
701 if (isalnum(*typtr & 255) || *typtr == '-')
702 *fileptr++ = *typtr++;
703 else
704 {
705 *fileptr++ = '_';
706 typtr++;
707 }
708 }
709
710 *fileptr = '\0';
711
712 printf("# %s\n", device->name);
713 printf("echo \"Testing %s...\"\n", device->name);
714
715 if (!ipponly)
716 {
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);
724 }
725
726 if (!snmponly)
727 {
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,
735 version, filename);
736 }
737
738 puts("");
739 }
740 else if (!device->ty)
741 fprintf(stderr,
742 "Ignoring \"%s\" since it doesn't provide a make and model.\n",
743 device->name);
744 else if (!testfile)
745 fprintf(stderr,
746 "Ignoring \"%s\" since it does not support a common format.\n",
747 device->name);
748 else
749 fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n",
750 device->name);
751 }
752
753 ippDelete(response);
754 httpClose(http);
755 }
756
757 return (0);
758#endif /* 0 */
759}
760
761
762#ifdef HAVE_DNSSD
763/*
764 * 'browse_callback()' - Browse devices.
765 */
766
767static void
768browse_callback(
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 */
777{
778 /*
779 * Only process "add" data...
780 */
781
782 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
783 return;
784
785 /*
786 * Get the device...
787 */
788
789 get_service((cups_array_t *)context, serviceName, regtype, replyDomain);
790}
791
792
793/*
794 * 'browse_local_callback()' - Browse local devices.
795 */
796
797static void
798browse_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 */
807{
808 ippfind_srv_t *service; /* Service */
809
810
811 /*
812 * Only process "add" data...
813 */
814
815 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
816 return;
817
818 /*
819 * Get the device...
820 */
821
822 service = get_service((cups_array_t *)context, serviceName, regtype,
823 replyDomain);
824 service->is_local = 1;
825}
826#endif /* HAVE_DNSSD */
827
828
829#ifdef HAVE_AVAHI
830/*
831 * 'browse_callback()' - Browse devices.
832 */
833
834static void
835browse_callback(
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 */
845{
846 AvahiClient *client = avahi_service_browser_get_client(browser);
847 /* Client information */
848 ippfind_srv_t *service; /* Service information */
849
850
851 (void)interface;
852 (void)protocol;
853 (void)context;
854
855 switch (event)
856 {
857 case AVAHI_BROWSER_FAILURE:
858 fprintf(stderr, "DEBUG: browse_callback: %s\n",
859 avahi_strerror(avahi_client_errno(client)));
860 bonjour_error = 1;
861 avahi_simple_poll_quit(simple_poll);
862 break;
863
864 case AVAHI_BROWSER_NEW:
865 /*
866 * This object is new on the network. Create a device entry for it if
867 * it doesn't yet exist.
868 */
869
870 service = get_service((cups_array_t *)context, name, type, domain);
871
872 if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
873 service->is_local = 1;
874 break;
875
876 case AVAHI_BROWSER_REMOVE:
877 case AVAHI_BROWSER_ALL_FOR_NOW:
878 case AVAHI_BROWSER_CACHE_EXHAUSTED:
879 break;
880 }
881}
882
883
884/*
885 * 'client_callback()' - Avahi client callback function.
886 */
887
888static void
889client_callback(
890 AvahiClient *client, /* I - Client information (unused) */
891 AvahiClientState state, /* I - Current state */
892 void *context) /* I - User data (unused) */
893{
894 (void)client;
895 (void)context;
896
897 /*
898 * If the connection drops, quit.
899 */
900
901 if (state == AVAHI_CLIENT_FAILURE)
902 {
903 fputs("DEBUG: Avahi connection failed.\n", stderr);
904 bonjour_error = 1;
905 avahi_simple_poll_quit(avahi_poll);
906 }
907}
908#endif /* HAVE_AVAHI */
909
910
911/*
912 * 'compare_services()' - Compare two devices.
913 */
914
915static int /* O - Result of comparison */
916compare_services(ippfind_srv_t *a, /* I - First device */
917 ippfind_srv_t *b) /* I - Second device */
918{
919 return (strcmp(a->name, b->name));
920}
921
922
923/*
924 * 'get_service()' - Create or update a device.
925 */
926
927static ippfind_srv_t * /* O - Service */
928get_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 */
932{
933 ippfind_srv_t key, /* Search key */
934 *service; /* Service */
935 char fullName[kDNSServiceMaxDomainName];
936 /* Full name for query */
937
938
939 /*
940 * See if this is a new device...
941 */
942
943 key.name = (char *)serviceName;
944 key.regtype = (char *)regtype;
945
946 for (service = cupsArrayFind(services, &key);
947 service;
948 service = cupsArrayNext(services))
949 if (_cups_strcasecmp(service->name, key.name))
950 break;
951 else if (!strcmp(service->regtype, key.regtype))
952 return (service);
953
954 /*
955 * Yes, add the service...
956 */
957
958 service = calloc(sizeof(ippfind_srv_t), 1);
959 service->name = strdup(serviceName);
960 service->domain = strdup(replyDomain);
961 service->regtype = strdup(regtype);
962
963 cupsArrayAdd(services, service);
964
965 /*
966 * Set the "full name" of this service, which is used for queries and
967 * resolves...
968 */
969
970#ifdef HAVE_DNSSD
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 */
976
977 service->fullName = strdup(fullName);
978
979 return (service);
980}
981
982
983#ifdef HAVE_AVAHI
984/*
985 * 'poll_callback()' - Wait for input on the specified file descriptors.
986 *
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)
990 */
991
992static int /* O - Number of file descriptors matching */
993poll_callback(
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) */
998{
999 int val; /* Return value */
1000
1001
1002 (void)timeout;
1003 (void)context;
1004
1005 val = poll(pollfds, num_pollfds, 500);
1006
1007 if (val < 0)
1008 fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno));
1009 else if (val > 0)
1010 got_data = 1;
1011
1012 return (val);
1013}
1014#endif /* HAVE_AVAHI */
1015
1016
1017/*
1018 * 'resolve_callback()' - Process resolve data.
1019 */
1020
1021#ifdef HAVE_DNSSD
1022static void
1023resolve_callback(
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 */
1034{
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;
1040 /* Service */
1041
1042
1043 /*
1044 * Only process "add" data...
1045 */
1046
1047 if (errorCode != kDNSServiceErr_NoError)
1048 return;
1049
1050 service->is_resolved = 1;
1051 service->host = strdup(hostTarget);
1052 service->port = ntohs(port);
1053
1054 /*
1055 * Loop through the TXT key/value pairs and add them to an array...
1056 */
1057
1058 for (txtEnd = txtRecord + txtLen; txtRecord < txtEnd; txtRecord += valueLen)
1059 {
1060 /*
1061 * Ignore bogus strings...
1062 */
1063
1064 valueLen = *txtRecord++;
1065
1066 memcpy(key, txtRecord, valueLen);
1067 key[valueLen] = '\0';
1068
1069 if ((value = strchr(key, '=')) == NULL)
1070 continue;
1071
1072 *value++ = '\0';
1073
1074 /*
1075 * Add to array of TXT values...
1076 */
1077
1078 service->num_txt = cupsAddOption(key, value, service->num_txt,
1079 &(service->txt));
1080 }
1081
1082 set_service_uri(service);
1083}
1084
1085
1086#elif defined(HAVE_AVAHI)
1087static void
1088resolve_callback(
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 */
1101{
1102 char uri[1024]; /* URI */
1103 key[256], /* TXT key */
1104 *value; /* TXT value */
1105 ippfind_srv_t *service = (ippfind_srv_t *)context;
1106 /* Service */
1107 AvahiStringList *current; /* Current TXT key/value pair */
1108
1109
1110 if (event != AVAHI_RESOLVER_FOUND)
1111 {
1112 bonjour_error = 1;
1113
1114 avahi_service_resolver_free(resolver);
1115 avahi_simple_poll_quit(uribuf->poll);
1116 return;
1117 }
1118
1119 service->is_resolved = 1;
1120 service->host = strdup(hostTarget);
1121 service->port = ntohs(port);
1122
1123 /*
1124 * Loop through the TXT key/value pairs and add them to an array...
1125 */
1126
1127 for (current = txt; current; current = current->next)
1128 {
1129 /*
1130 * Ignore bogus strings...
1131 */
1132
1133 if (current->size > (sizeof(key) - 1))
1134 continue;
1135
1136 memcpy(key, current->text, current->size);
1137 key[current->size] = '\0';
1138
1139 if ((value = strchr(key, '=')) == NULL)
1140 continue;
1141
1142 *value++ = '\0';
1143
1144 /*
1145 * Add to array of TXT values...
1146 */
1147
1148 service->num_txt = cupsAddOption(key, value, service->num_txt,
1149 &(service->txt));
1150 }
1151
1152 set_service_uri(service);
1153}
1154#endif /* HAVE_DNSSD */
1155
1156
1157/*
1158 * 'set_service_uri()' - Set the URI of the service.
1159 */
1160
1161static void
1162set_service_uri(ippfind_srv_t *service) /* I - Service */
1163{
1164 char uri[1024]; /* URI */
1165 const char *path, /* Resource path */
1166 *scheme; /* URI scheme */
1167
1168
1169 if (!strncmp(service->regtype, "_http.", 6))
1170 {
1171 scheme = "http";
1172 path = cupsGetOption("path", service->num_txt, service->txt);
1173 }
1174 else if (!strncmp(service->regtype, "_https.", 7))
1175 {
1176 scheme = "https";
1177 path = cupsGetOption("path", service->num_txt, service->txt);
1178 }
1179 else if (!strncmp(service->regtype, "_ipp.", 5))
1180 {
1181 scheme = "ipp";
1182 path = cupsGetOption("rp", service->num_txt, service->txt);
1183 }
1184 else if (!strncmp(service->regtype, "_ipps.", 6))
1185 {
1186 scheme = "ipps";
1187 path = cupsGetOption("rp", service->num_txt, service->txt);
1188 }
1189 else if (!strncmp(service->regtype, "_printer.", 9))
1190 {
1191 scheme = "lpd";
1192 path = cupsGetOption("rp", service->num_txt, service->txt);
1193 }
1194 else
1195 return;
1196
1197 if (!path || !*path)
1198 path = "/";
1199
1200 if (*path == '/')
1201 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL,
1202 service->host, service->port, path);
1203 else
1204 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL,
1205 service->host, service->port, "/%s", path);
1206
1207 service->uri = strdup(uri);
1208}
1209
1210
1211/*
1212 * 'usage()' - Show program usage.
1213 */
1214
1215static void
1216usage(void)
1217{
1218 _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]"
1219 "[.domain.] ... [expression]\n"
1220 " ippfind [options] name[.regtype[.domain.]] "
1221 "... [expression]\n"
1222 " ippfind --help\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 "
1229 "seconds."));
1230 _cupsLangPuts(stderr, _(" -V version Set default IPP "
1231 "version."));
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."));
1246
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 "
1255 "value."));
1256 _cupsLangPuts(stderr, _(" -f filename Set default request "
1257 "filename."));
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."));
1265
1266 exit(IPPFIND_EXIT_OK);
1267}
1268
1269
1270/*
1271 * 'show_version()' - Show program version.
1272 */
1273
1274static void
1275show_version(void)
1276{
1277 _cupsLangPuts(stderr, CUPS_SVERSION);
1278
1279 exit(IPPFIND_EXIT_OK);
1280}
1281
1282
1283/*
1284 * 'unquote()' - Unquote a name string.
1285 */
1286
1287static void
1288unquote(char *dst, /* I - Destination buffer */
1289 const char *src, /* I - Source string */
1290 size_t dstsize) /* I - Size of destination buffer */
1291{
1292 char *dstend = dst + dstsize - 1; /* End of destination buffer */
1293
1294
1295 while (*src && dst < dstend)
1296 {
1297 if (*src == '\\')
1298 {
1299 src ++;
1300 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
1301 isdigit(src[2] & 255))
1302 {
1303 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
1304 src += 3;
1305 }
1306 else
1307 *dst++ = *src++;
1308 }
1309 else
1310 *dst++ = *src ++;
1311 }
1312
1313 *dst = '\0';
1314}
1315
1316
1317/*
1318 * End of "$Id$".
1319 */