]>
Commit | Line | Data |
---|---|---|
06399b6e MS |
1 | /* |
2 | * "$Id$" | |
3 | * | |
4 | * ippdiscover command for CUPS. | |
5 | * | |
6 | * Copyright 2007-2013 by Apple Inc. | |
7 | * Copyright 1997-2007 by Easy Software Products. | |
8 | * | |
9 | * These coded instructions, statements, and computer programs are the | |
10 | * property of Apple Inc. and are protected by Federal copyright | |
11 | * law. Distribution and use rights are outlined in the file "LICENSE.txt" | |
12 | * which should have been included with this file. If this file is | |
13 | * file is missing or damaged, see the license at "http://www.cups.org/". | |
14 | * | |
15 | * This file is subject to the Apple OS-Developed Software exception. | |
16 | * | |
17 | * Contents: | |
18 | * | |
19 | */ | |
20 | ||
21 | ||
22 | /* | |
23 | * Include necessary headers. | |
24 | */ | |
25 | ||
26 | #include <cups/cups-private.h> | |
27 | #ifdef HAVE_DNSSD | |
28 | # include <dns_sd.h> | |
29 | # ifdef WIN32 | |
30 | # pragma comment(lib, "dnssd.lib") | |
31 | # endif /* WIN32 */ | |
32 | #endif /* HAVE_DNSSD */ | |
33 | #ifdef HAVE_AVAHI | |
34 | # include <avahi-client/client.h> | |
35 | # include <avahi-client/lookup.h> | |
36 | # include <avahi-common/simple-watch.h> | |
37 | # include <avahi-common/domain.h> | |
38 | # include <avahi-common/error.h> | |
39 | # include <avahi-common/malloc.h> | |
40 | #define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX | |
41 | #endif /* HAVE_AVAHI */ | |
42 | ||
43 | ||
44 | /* | |
45 | * Local globals... | |
46 | */ | |
47 | ||
48 | #ifdef HAVE_AVAHI | |
49 | static int got_data = 0; /* Got data from poll? */ | |
50 | static AvahiSimplePoll *simple_poll = NULL; | |
51 | /* Poll information */ | |
52 | #endif /* HAVE_AVAHI */ | |
53 | static const char *program = NULL;/* Program to run */ | |
54 | ||
55 | ||
56 | /* | |
57 | * Local functions... | |
58 | */ | |
59 | ||
60 | #ifdef HAVE_DNSSD | |
61 | static void DNSSD_API browse_callback(DNSServiceRef sdRef, | |
62 | DNSServiceFlags flags, | |
63 | uint32_t interfaceIndex, | |
64 | DNSServiceErrorType errorCode, | |
65 | const char *serviceName, | |
66 | const char *regtype, | |
67 | const char *replyDomain, void *context) | |
68 | __attribute__((nonnull(1,5,6,7,8))); | |
69 | static void DNSSD_API resolve_cb(DNSServiceRef sdRef, | |
70 | DNSServiceFlags flags, | |
71 | uint32_t interfaceIndex, | |
72 | DNSServiceErrorType errorCode, | |
73 | const char *fullName, | |
74 | const char *hostTarget, | |
75 | uint16_t port, uint16_t txtLen, | |
76 | const unsigned char *txtRecord, | |
77 | void *context); | |
78 | #endif /* HAVE_DNSSD */ | |
79 | ||
80 | #ifdef HAVE_AVAHI | |
81 | static void browse_callback(AvahiServiceBrowser *browser, | |
82 | AvahiIfIndex interface, | |
83 | AvahiProtocol protocol, | |
84 | AvahiBrowserEvent event, | |
85 | const char *serviceName, | |
86 | const char *regtype, | |
87 | const char *replyDomain, | |
88 | AvahiLookupResultFlags flags, | |
89 | void *context); | |
90 | static void client_cb(AvahiClient *client, AvahiClientState state, | |
91 | void *simple_poll); | |
92 | static int poll_cb(struct pollfd *pollfds, unsigned int num_pollfds, | |
93 | int timeout, void *context); | |
94 | static void resolve_cb(AvahiServiceResolver *resolver, | |
95 | AvahiIfIndex interface, | |
96 | AvahiProtocol protocol, | |
97 | AvahiResolverEvent event, | |
98 | const char *name, const char *type, | |
99 | const char *domain, const char *host_name, | |
100 | const AvahiAddress *address, uint16_t port, | |
101 | AvahiStringList *txt, | |
102 | AvahiLookupResultFlags flags, void *context); | |
103 | #endif /* HAVE_AVAHI */ | |
104 | ||
105 | static void resolve_and_run(const char *name, const char *type, | |
106 | const char *domain); | |
107 | static void unquote(char *dst, const char *src, size_t dstsize); | |
108 | static void usage(void) __attribute__((noreturn)); | |
109 | ||
110 | ||
111 | /* | |
112 | * 'main()' - Browse for printers and run the specified command. | |
113 | */ | |
114 | ||
115 | int /* O - Exit status */ | |
116 | main(int argc, /* I - Number of command-line args */ | |
117 | char *argv[]) /* I - Command-line arguments */ | |
118 | { | |
119 | int i; /* Looping var */ | |
120 | const char *opt, /* Current option character */ | |
121 | *name = NULL, /* Service name */ | |
122 | *type = "_ipp._tcp", /* Service type */ | |
123 | *domain = "local."; /* Service domain */ | |
124 | #ifdef HAVE_DNSSD | |
125 | DNSServiceRef ref; /* Browsing service reference */ | |
126 | #endif /* HAVE_DNSSD */ | |
127 | #ifdef HAVE_AVAHI | |
128 | AvahiClient *client; /* Client information */ | |
129 | int error; /* Error code, if any */ | |
130 | #endif /* HAVE_AVAHI */ | |
131 | ||
132 | ||
133 | for (i = 1; i < argc; i ++) | |
134 | if (!strcmp(argv[i], "snmp")) | |
135 | snmponly = 1; | |
136 | else if (!strcmp(argv[i], "ipp")) | |
137 | ipponly = 1; | |
138 | else | |
139 | { | |
140 | puts("Usage: ./ipp-printers [{ipp | snmp}]"); | |
141 | return (1); | |
142 | } | |
143 | ||
144 | /* | |
145 | * Create an array to track devices... | |
146 | */ | |
147 | ||
148 | devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL); | |
149 | ||
150 | /* | |
151 | * Browse for different kinds of printers... | |
152 | */ | |
153 | ||
154 | if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError) | |
155 | { | |
156 | perror("ERROR: Unable to create service connection"); | |
157 | return (1); | |
158 | } | |
159 | ||
160 | fd = DNSServiceRefSockFD(main_ref); | |
161 | ||
162 | ipp_ref = main_ref; | |
163 | DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, | |
164 | "_ipp._tcp", NULL, browse_callback, devices); | |
165 | ||
166 | /* | |
167 | * Loop until we are killed... | |
168 | */ | |
169 | ||
170 | progress(); | |
171 | ||
172 | for (;;) | |
173 | { | |
174 | FD_ZERO(&input); | |
175 | FD_SET(fd, &input); | |
176 | ||
177 | timeout.tv_sec = 2; | |
178 | timeout.tv_usec = 500000; | |
179 | ||
180 | if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0) | |
181 | { | |
182 | time_t curtime = time(NULL); | |
183 | ||
184 | for (device = (cups_device_t *)cupsArrayFirst(devices); | |
185 | device; | |
186 | device = (cups_device_t *)cupsArrayNext(devices)) | |
187 | if (!device->got_resolve) | |
188 | { | |
189 | if (!device->ref) | |
190 | break; | |
191 | ||
192 | if ((curtime - device->resolve_time) > 10) | |
193 | { | |
194 | device->got_resolve = -1; | |
195 | fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n", | |
196 | device->name); | |
197 | progress(); | |
198 | } | |
199 | else | |
200 | break; | |
201 | } | |
202 | ||
203 | if (!device) | |
204 | break; | |
205 | } | |
206 | ||
207 | if (FD_ISSET(fd, &input)) | |
208 | { | |
209 | /* | |
210 | * Process results of our browsing... | |
211 | */ | |
212 | ||
213 | progress(); | |
214 | DNSServiceProcessResult(main_ref); | |
215 | } | |
216 | else | |
217 | { | |
218 | /* | |
219 | * Query any devices we've found... | |
220 | */ | |
221 | ||
222 | DNSServiceErrorType status; /* DNS query status */ | |
223 | int count; /* Number of queries */ | |
224 | ||
225 | ||
226 | for (device = (cups_device_t *)cupsArrayFirst(devices), count = 0; | |
227 | device; | |
228 | device = (cups_device_t *)cupsArrayNext(devices)) | |
229 | { | |
230 | if (!device->ref && !device->sent) | |
231 | { | |
232 | /* | |
233 | * Found the device, now get the TXT record(s) for it... | |
234 | */ | |
235 | ||
236 | if (count < 50) | |
237 | { | |
238 | device->resolve_time = time(NULL); | |
239 | device->ref = main_ref; | |
240 | ||
241 | status = DNSServiceResolve(&(device->ref), | |
242 | kDNSServiceFlagsShareConnection, | |
243 | 0, device->name, device->regtype, | |
244 | device->domain, resolve_callback, | |
245 | device); | |
246 | if (status != kDNSServiceErr_NoError) | |
247 | { | |
248 | fprintf(stderr, "\rUnable to resolve \"%s\": %d\n", | |
249 | device->name, status); | |
250 | progress(); | |
251 | } | |
252 | else | |
253 | count ++; | |
254 | } | |
255 | } | |
256 | else if (!device->sent && device->got_resolve) | |
257 | { | |
258 | /* | |
259 | * Got the TXT records, now report the device... | |
260 | */ | |
261 | ||
262 | DNSServiceRefDeallocate(device->ref); | |
263 | device->ref = 0; | |
264 | device->sent = 1; | |
265 | } | |
266 | } | |
267 | } | |
268 | } | |
269 | ||
270 | #ifndef DEBUG | |
271 | fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n", | |
272 | cupsArrayCount(devices)); | |
273 | #endif /* !DEBUG */ | |
274 | ||
275 | puts("#!/bin/sh -x"); | |
276 | puts("test -d results && rm -rf results"); | |
277 | puts("mkdir results"); | |
278 | puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL"); | |
279 | puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|" | |
280 | "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER"); | |
281 | ||
282 | for (device = (cups_device_t *)cupsArrayFirst(devices); | |
283 | device; | |
284 | device = (cups_device_t *)cupsArrayNext(devices)) | |
285 | { | |
286 | if (device->got_resolve <= 0 || device->cups_shared) | |
287 | continue; | |
288 | ||
289 | #ifdef DEBUG | |
290 | fprintf(stderr, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n", | |
291 | device->name, device->got_resolve, device->cups_shared, device->uri); | |
292 | #else | |
293 | fprintf(stderr, "Checking \"%s\"...\n", device->name); | |
294 | #endif /* DEBUG */ | |
295 | ||
296 | if ((http = httpConnect(device->host, device->port)) == NULL) | |
297 | { | |
298 | fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name, | |
299 | cupsLastErrorString()); | |
300 | continue; | |
301 | } | |
302 | ||
303 | request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); | |
304 | ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, | |
305 | device->uri); | |
306 | ||
307 | response = cupsDoRequest(http, request, device->rp); | |
308 | ||
309 | if (cupsLastError() > IPP_OK_SUBST) | |
310 | fprintf(stderr, "Failed to query \"%s\": %s\n", device->name, | |
311 | cupsLastErrorString()); | |
312 | else | |
313 | { | |
314 | if ((attr = ippFindAttribute(response, "ipp-versions-supported", | |
315 | IPP_TAG_KEYWORD)) != NULL) | |
316 | { | |
317 | version = attr->values[0].string.text; | |
318 | ||
319 | for (i = 1; i < attr->num_values; i ++) | |
320 | if (strcmp(attr->values[i].string.text, version) > 0) | |
321 | version = attr->values[i].string.text; | |
322 | } | |
323 | else | |
324 | version = "1.0"; | |
325 | ||
326 | testfile = NULL; | |
327 | ||
328 | if ((attr = ippFindAttribute(response, "document-format-supported", | |
329 | IPP_TAG_MIMETYPE)) != NULL) | |
330 | { | |
331 | /* | |
332 | * Figure out the test file for printing, preferring PDF and PostScript | |
333 | * over JPEG and plain text... | |
334 | */ | |
335 | ||
336 | for (i = 0; i < attr->num_values; i ++) | |
337 | { | |
338 | if (!strcasecmp(attr->values[i].string.text, "application/pdf")) | |
339 | { | |
340 | testfile = "testfile.pdf"; | |
341 | break; | |
342 | } | |
343 | else if (!strcasecmp(attr->values[i].string.text, | |
344 | "application/postscript")) | |
345 | testfile = "testfile.ps"; | |
346 | else if (!strcasecmp(attr->values[i].string.text, "image/jpeg") && | |
347 | !testfile) | |
348 | testfile = "testfile.jpg"; | |
349 | else if (!strcasecmp(attr->values[i].string.text, "text/plain") && | |
350 | !testfile) | |
351 | testfile = "testfile.txt"; | |
352 | else if (!strcasecmp(attr->values[i].string.text, | |
353 | "application/vnd.hp-PCL") && !testfile) | |
354 | testfile = "testfile.pcl"; | |
355 | } | |
356 | ||
357 | if (!testfile) | |
358 | { | |
359 | fprintf(stderr, | |
360 | "Printer \"%s\" reports the following IPP file formats:\n", | |
361 | device->name); | |
362 | for (i = 0; i < attr->num_values; i ++) | |
363 | fprintf(stderr, " \"%s\"\n", attr->values[i].string.text); | |
364 | } | |
365 | } | |
366 | ||
367 | if (!testfile && device->pdl) | |
368 | { | |
369 | char *pdl, /* Copy of pdl string */ | |
370 | *start, *end; /* Pointers into pdl string */ | |
371 | ||
372 | ||
373 | pdl = strdup(device->pdl); | |
374 | for (start = device->pdl; start && *start; start = end) | |
375 | { | |
376 | if ((end = strchr(start, ',')) != NULL) | |
377 | *end++ = '\0'; | |
378 | ||
379 | if (!strcasecmp(start, "application/pdf")) | |
380 | { | |
381 | testfile = "testfile.pdf"; | |
382 | break; | |
383 | } | |
384 | else if (!strcasecmp(start, "application/postscript")) | |
385 | testfile = "testfile.ps"; | |
386 | else if (!strcasecmp(start, "image/jpeg") && !testfile) | |
387 | testfile = "testfile.jpg"; | |
388 | else if (!strcasecmp(start, "text/plain") && !testfile) | |
389 | testfile = "testfile.txt"; | |
390 | else if (!strcasecmp(start, "application/vnd.hp-PCL") && !testfile) | |
391 | testfile = "testfile.pcl"; | |
392 | } | |
393 | free(pdl); | |
394 | ||
395 | if (testfile) | |
396 | { | |
397 | fprintf(stderr, | |
398 | "Using \"%s\" for printer \"%s\" based on TXT record pdl " | |
399 | "info.\n", testfile, device->name); | |
400 | } | |
401 | else | |
402 | { | |
403 | fprintf(stderr, | |
404 | "Printer \"%s\" reports the following TXT file formats:\n", | |
405 | device->name); | |
406 | fprintf(stderr, " \"%s\"\n", device->pdl); | |
407 | } | |
408 | } | |
409 | ||
410 | if (!device->ty && | |
411 | (attr = ippFindAttribute(response, "printer-make-and-model", | |
412 | IPP_TAG_TEXT)) != NULL) | |
413 | device->ty = strdup(attr->values[0].string.text); | |
414 | ||
415 | if (strcmp(version, "1.0") && testfile && device->ty) | |
416 | { | |
417 | char filename[1024], /* Filename */ | |
418 | *fileptr; /* Pointer into filename */ | |
419 | const char *typtr; /* Pointer into ty */ | |
420 | ||
421 | if (!strncasecmp(device->ty, "DeskJet", 7) || | |
422 | !strncasecmp(device->ty, "DesignJet", 9) || | |
423 | !strncasecmp(device->ty, "OfficeJet", 9) || | |
424 | !strncasecmp(device->ty, "Photosmart", 10)) | |
425 | strlcpy(filename, "HP_", sizeof(filename)); | |
426 | else | |
427 | filename[0] = '\0'; | |
428 | ||
429 | fileptr = filename + strlen(filename); | |
430 | ||
431 | if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29)) | |
432 | typtr = device->ty + 22; | |
433 | else | |
434 | typtr = device->ty; | |
435 | ||
436 | while (*typtr && fileptr < (filename + sizeof(filename) - 1)) | |
437 | { | |
438 | if (isalnum(*typtr & 255) || *typtr == '-') | |
439 | *fileptr++ = *typtr++; | |
440 | else | |
441 | { | |
442 | *fileptr++ = '_'; | |
443 | typtr++; | |
444 | } | |
445 | } | |
446 | ||
447 | *fileptr = '\0'; | |
448 | ||
449 | printf("# %s\n", device->name); | |
450 | printf("echo \"Testing %s...\"\n", device->name); | |
451 | ||
452 | if (!ipponly) | |
453 | { | |
454 | printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " | |
455 | "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n", | |
456 | device->host, filename); | |
457 | printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " | |
458 | "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | " | |
459 | "tee -a results/%s.snmpwalk\n", | |
460 | device->host, filename); | |
461 | } | |
462 | ||
463 | if (!snmponly) | |
464 | { | |
465 | printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " | |
466 | "ipp-%s.test\" > results/%s.log\n", testfile, version, | |
467 | device->uri, version, filename); | |
468 | printf("CUPS_DEBUG_LOG=results/%s.debug_log " | |
469 | "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " | |
470 | "ipp-%s.test | tee -a results/%s.log\n", filename, | |
471 | testfile, version, device->uri, | |
472 | version, filename); | |
473 | } | |
474 | ||
475 | puts(""); | |
476 | } | |
477 | else if (!device->ty) | |
478 | fprintf(stderr, | |
479 | "Ignoring \"%s\" since it doesn't provide a make and model.\n", | |
480 | device->name); | |
481 | else if (!testfile) | |
482 | fprintf(stderr, | |
483 | "Ignoring \"%s\" since it does not support a common format.\n", | |
484 | device->name); | |
485 | else | |
486 | fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n", | |
487 | device->name); | |
488 | } | |
489 | ||
490 | ippDelete(response); | |
491 | httpClose(http); | |
492 | } | |
493 | ||
494 | return (0); | |
495 | } | |
496 | ||
497 | ||
498 | /* | |
499 | * 'browse_callback()' - Browse devices. | |
500 | */ | |
501 | ||
502 | static void | |
503 | browse_callback( | |
504 | DNSServiceRef sdRef, /* I - Service reference */ | |
505 | DNSServiceFlags flags, /* I - Option flags */ | |
506 | uint32_t interfaceIndex, /* I - Interface number */ | |
507 | DNSServiceErrorType errorCode, /* I - Error, if any */ | |
508 | const char *serviceName, /* I - Name of service/device */ | |
509 | const char *regtype, /* I - Type of service */ | |
510 | const char *replyDomain, /* I - Service domain */ | |
511 | void *context) /* I - Devices array */ | |
512 | { | |
513 | #ifdef DEBUG | |
514 | fprintf(stderr, "browse_callback(sdRef=%p, flags=%x, " | |
515 | "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", " | |
516 | "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n", | |
517 | sdRef, flags, interfaceIndex, errorCode, | |
518 | serviceName ? serviceName : "(null)", | |
519 | regtype ? regtype : "(null)", | |
520 | replyDomain ? replyDomain : "(null)", | |
521 | context); | |
522 | #endif /* DEBUG */ | |
523 | ||
524 | /* | |
525 | * Only process "add" data... | |
526 | */ | |
527 | ||
528 | if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) | |
529 | return; | |
530 | ||
531 | /* | |
532 | * Get the device... | |
533 | */ | |
534 | ||
535 | get_device((cups_array_t *)context, serviceName, regtype, replyDomain); | |
536 | } | |
537 | ||
538 | ||
539 | /* | |
540 | * 'compare_devices()' - Compare two devices. | |
541 | */ | |
542 | ||
543 | static int /* O - Result of comparison */ | |
544 | compare_devices(cups_device_t *a, /* I - First device */ | |
545 | cups_device_t *b) /* I - Second device */ | |
546 | { | |
547 | int retval = strcmp(a->name, b->name); | |
548 | ||
549 | if (retval) | |
550 | return (retval); | |
551 | else | |
552 | return (-strcmp(a->regtype, b->regtype)); | |
553 | } | |
554 | ||
555 | ||
556 | /* | |
557 | * 'get_device()' - Create or update a device. | |
558 | */ | |
559 | ||
560 | static cups_device_t * /* O - Device */ | |
561 | get_device(cups_array_t *devices, /* I - Device array */ | |
562 | const char *serviceName, /* I - Name of service/device */ | |
563 | const char *regtype, /* I - Type of service */ | |
564 | const char *replyDomain) /* I - Service domain */ | |
565 | { | |
566 | cups_device_t key, /* Search key */ | |
567 | *device; /* Device */ | |
568 | char fullName[kDNSServiceMaxDomainName]; | |
569 | /* Full name for query */ | |
570 | ||
571 | ||
572 | /* | |
573 | * See if this is a new device... | |
574 | */ | |
575 | ||
576 | key.name = (char *)serviceName; | |
577 | key.regtype = (char *)regtype; | |
578 | ||
579 | for (device = cupsArrayFind(devices, &key); | |
580 | device; | |
581 | device = cupsArrayNext(devices)) | |
582 | if (strcasecmp(device->name, key.name)) | |
583 | break; | |
584 | else | |
585 | { | |
586 | if (!strcasecmp(device->domain, "local.") && | |
587 | strcasecmp(device->domain, replyDomain)) | |
588 | { | |
589 | /* | |
590 | * Update the .local listing to use the "global" domain name instead. | |
591 | * The backend will try local lookups first, then the global domain name. | |
592 | */ | |
593 | ||
594 | free(device->domain); | |
595 | device->domain = strdup(replyDomain); | |
596 | ||
597 | DNSServiceConstructFullName(fullName, device->name, regtype, | |
598 | replyDomain); | |
599 | free(device->fullName); | |
600 | device->fullName = strdup(fullName); | |
601 | } | |
602 | ||
603 | return (device); | |
604 | } | |
605 | ||
606 | /* | |
607 | * Yes, add the device... | |
608 | */ | |
609 | ||
610 | device = calloc(sizeof(cups_device_t), 1); | |
611 | device->name = strdup(serviceName); | |
612 | device->domain = strdup(replyDomain); | |
613 | device->regtype = strdup(regtype); | |
614 | ||
615 | cupsArrayAdd(devices, device); | |
616 | ||
617 | /* | |
618 | * Set the "full name" of this service, which is used for queries... | |
619 | */ | |
620 | ||
621 | DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); | |
622 | device->fullName = strdup(fullName); | |
623 | ||
624 | #ifdef DEBUG | |
625 | fprintf(stderr, "get_device: fullName=\"%s\"...\n", fullName); | |
626 | #endif /* DEBUG */ | |
627 | ||
628 | return (device); | |
629 | } | |
630 | ||
631 | ||
632 | /* | |
633 | * 'progress()' - Show query progress. | |
634 | */ | |
635 | ||
636 | static void | |
637 | progress(void) | |
638 | { | |
639 | #ifndef DEBUG | |
640 | const char *chars = "|/-\\"; | |
641 | static int count = 0; | |
642 | ||
643 | ||
644 | fprintf(stderr, "\rLooking for printers %c", chars[count]); | |
645 | fflush(stderr); | |
646 | count = (count + 1) & 3; | |
647 | #endif /* !DEBUG */ | |
648 | } | |
649 | ||
650 | ||
651 | /* | |
652 | * 'resolve_callback()' - Process resolve data. | |
653 | */ | |
654 | ||
655 | static void | |
656 | resolve_callback( | |
657 | DNSServiceRef sdRef, /* I - Service reference */ | |
658 | DNSServiceFlags flags, /* I - Data flags */ | |
659 | uint32_t interfaceIndex, /* I - Interface */ | |
660 | DNSServiceErrorType errorCode, /* I - Error, if any */ | |
661 | const char *fullName, /* I - Full service name */ | |
662 | const char *hostTarget, /* I - Hostname */ | |
663 | uint16_t port, /* I - Port number (network byte order) */ | |
664 | uint16_t txtLen, /* I - Length of TXT record data */ | |
665 | const unsigned char *txtRecord, /* I - TXT record data */ | |
666 | void *context) /* I - Device */ | |
667 | { | |
668 | char temp[257], /* TXT key value */ | |
669 | uri[1024]; /* Printer URI */ | |
670 | const void *value; /* Value from TXT record */ | |
671 | uint8_t valueLen; /* Length of value */ | |
672 | cups_device_t *device = (cups_device_t *)context; | |
673 | /* Device */ | |
674 | ||
675 | ||
676 | #ifdef DEBUG | |
677 | fprintf(stderr, "\rresolve_callback(sdRef=%p, flags=%x, " | |
678 | "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", " | |
679 | "hostTarget=\"%s\", port=%d, txtLen=%u, txtRecord=%p, " | |
680 | "context=%p)\n", | |
681 | sdRef, flags, interfaceIndex, errorCode, | |
682 | fullName ? fullName : "(null)", hostTarget ? hostTarget : "(null)", | |
683 | ntohs(port), txtLen, txtRecord, context); | |
684 | #endif /* DEBUG */ | |
685 | ||
686 | /* | |
687 | * Only process "add" data... | |
688 | */ | |
689 | ||
690 | if (errorCode != kDNSServiceErr_NoError) | |
691 | return; | |
692 | ||
693 | device->got_resolve = 1; | |
694 | device->host = strdup(hostTarget); | |
695 | device->port = ntohs(port); | |
696 | ||
697 | /* | |
698 | * Extract the "remote printer" key from the TXT record and save the URI... | |
699 | */ | |
700 | ||
701 | if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "rp", | |
702 | &valueLen)) != NULL) | |
703 | { | |
704 | if (((char *)value)[0] == '/') | |
705 | { | |
706 | /* | |
707 | * "rp" value (incorrectly) has a leading slash already... | |
708 | */ | |
709 | ||
710 | memcpy(temp, value, valueLen); | |
711 | temp[valueLen] = '\0'; | |
712 | } | |
713 | else | |
714 | { | |
715 | /* | |
716 | * Convert to resource by concatenating with a leading "/"... | |
717 | */ | |
718 | ||
719 | temp[0] = '/'; | |
720 | memcpy(temp + 1, value, valueLen); | |
721 | temp[valueLen + 1] = '\0'; | |
722 | } | |
723 | } | |
724 | else | |
725 | { | |
726 | /* | |
727 | * Default "rp" value is blank, mapping to a path of "/"... | |
728 | */ | |
729 | ||
730 | temp[0] = '/'; | |
731 | temp[1] = '\0'; | |
732 | } | |
733 | ||
734 | if (!strncmp(temp, "/printers/", 10)) | |
735 | device->cups_shared = -1; | |
736 | ||
737 | httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", | |
738 | NULL, hostTarget, ntohs(port), temp); | |
739 | device->uri = strdup(uri); | |
740 | device->rp = strdup(temp); | |
741 | ||
742 | if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "ty", | |
743 | &valueLen)) != NULL) | |
744 | { | |
745 | memcpy(temp, value, valueLen); | |
746 | temp[valueLen] = '\0'; | |
747 | ||
748 | device->ty = strdup(temp); | |
749 | } | |
750 | ||
751 | if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "pdl", | |
752 | &valueLen)) != NULL) | |
753 | { | |
754 | memcpy(temp, value, valueLen); | |
755 | temp[valueLen] = '\0'; | |
756 | ||
757 | device->pdl = strdup(temp); | |
758 | } | |
759 | ||
760 | if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "printer-type", | |
761 | &valueLen)) != NULL) | |
762 | device->cups_shared = 1; | |
763 | ||
764 | if (device->cups_shared) | |
765 | fprintf(stderr, "\rIgnoring CUPS printer %s\n", uri); | |
766 | else | |
767 | fprintf(stderr, "\rFound IPP printer %s\n", uri); | |
768 | ||
769 | progress(); | |
770 | } | |
771 | ||
772 | ||
773 | /* | |
774 | * 'unquote()' - Unquote a name string. | |
775 | */ | |
776 | ||
777 | static void | |
778 | unquote(char *dst, /* I - Destination buffer */ | |
779 | const char *src, /* I - Source string */ | |
780 | size_t dstsize) /* I - Size of destination buffer */ | |
781 | { | |
782 | char *dstend = dst + dstsize - 1; /* End of destination buffer */ | |
783 | ||
784 | ||
785 | while (*src && dst < dstend) | |
786 | { | |
787 | if (*src == '\\') | |
788 | { | |
789 | src ++; | |
790 | if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && | |
791 | isdigit(src[2] & 255)) | |
792 | { | |
793 | *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; | |
794 | src += 3; | |
795 | } | |
796 | else | |
797 | *dst++ = *src++; | |
798 | } | |
799 | else | |
800 | *dst++ = *src ++; | |
801 | } | |
802 | ||
803 | *dst = '\0'; | |
804 | } | |
805 | ||
806 | ||
807 | /* | |
808 | * 'usage()' - Show program usage and exit. | |
809 | */ | |
810 | ||
811 | static void | |
812 | usage(void) | |
813 | { | |
814 | _cupsLangPuts(stdout, _("Usage: ippdiscover [options] -a\n" | |
815 | " ippdiscover [options] \"service name\"\n" | |
816 | "\n" | |
817 | "Options:")); | |
818 | _cupsLangPuts(stdout, _(" -a Browse for all services.")); | |
819 | _cupsLangPuts(stdout, _(" -d domain Browse/resolve in specified domain.")); | |
820 | _cupsLangPuts(stdout, _(" -p program Run specified program for each service.")); | |
821 | _cupsLangPuts(stdout, _(" -t type Browse/resolve with specified type.")); | |
822 | ||
823 | exit(0); | |
824 | } | |
825 | ||
826 | ||
827 | /* | |
828 | * End of "$Id$". | |
829 | */ |