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