1 /* AirScan (a.k.a. eSCL) backend for SANE
2 *
3 * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4 * See LICENSE for license terms and conditions
5 *
6 * ZeroConf (device discovery)
7 */
8
9 #include "airscan.h"
10
11 #include <arpa/inet.h>
12 #include <net/if.h>
13
14 #include <fnmatch.h>
15 #include <stdlib.h>
16 #include <string.h>
17
18 /******************** Constants *********************/
19 /* Max time to wait until device table is ready, in seconds
20 */
21 #define ZEROCONF_READY_TIMEOUT 5
22
23 /******************** Local Types *********************/
24 /* zeroconf_device represents a single device
25 */
26 struct zeroconf_device {
27 unsigned int devid; /* Unique ident */
28 uuid uuid; /* Device UUID */
29 ip_addrset *addrs; /* Device's addresses */
30 const char *mdns_name; /* Device's MDNS name, NULL for WSDD */
31 const char *model; /* Device model name */
32 unsigned int protocols; /* Supported protocols, set of 1<<ID_PROTO */
33 unsigned int methods; /* How device was discovered, set of
34 1 << ZEROCONF_METHOD */
35 ll_node node_list; /* In zeroconf_device_list */
36 ll_head findings; /* zeroconf_finding, by method */
37 zeroconf_device *buddy; /* "Buddy" device, MDNS vs WSDD */
38 };
39
40 /* Global variables
41 */
42 log_ctx *zeroconf_log;
43
44 /* Static variables
45 */
46 static ll_head zeroconf_device_list;
47 static pthread_cond_t zeroconf_initscan_cond;
48 static int zeroconf_initscan_bits;
49 static eloop_timer *zeroconf_initscan_timer;
50
51 /******************** Forward declarations *********************/
52 static zeroconf_endpoint*
53 zeroconf_endpoint_copy_single (const zeroconf_endpoint *endpoint);
54
55 static const char*
56 zeroconf_ident_split (const char *ident, unsigned int *devid, ID_PROTO *proto);
57
58 /******************** Discovery methods *********************/
59 /* Map ZEROCONF_METHOD to ID_PROTO
60 */
61 static ID_PROTO
zeroconf_method_to_proto(ZEROCONF_METHOD method)62 zeroconf_method_to_proto (ZEROCONF_METHOD method)
63 {
64 switch (method) {
65 case ZEROCONF_MDNS_HINT:
66 return ID_PROTO_UNKNOWN;
67
68 case ZEROCONF_USCAN_TCP:
69 case ZEROCONF_USCANS_TCP:
70 return ID_PROTO_ESCL;
71
72 case ZEROCONF_WSD:
73 return ID_PROTO_WSD;
74
75 case NUM_ZEROCONF_METHOD:
76 break;
77 }
78
79 return ID_PROTO_UNKNOWN;
80 }
81
82 /* Get ZEROCONF_METHOD, for debugging
83 */
84 static const char*
zeroconf_method_name(ZEROCONF_METHOD method)85 zeroconf_method_name (ZEROCONF_METHOD method)
86 {
87 switch (method) {
88 case ZEROCONF_MDNS_HINT: return "ZEROCONF_MDNS_HINT";
89 case ZEROCONF_USCAN_TCP: return "ZEROCONF_USCAN_TCP";
90 case ZEROCONF_USCANS_TCP: return "ZEROCONF_USCANS_TCP";
91 case ZEROCONF_WSD: return "ZEROCONF_WSD";
92
93 case NUM_ZEROCONF_METHOD:
94 break;
95 }
96
97 return NULL;
98 }
99
100 /******************** Devices *********************/
101 /* Add new zeroconf_device
102 */
103 static zeroconf_device*
zeroconf_device_add(zeroconf_finding * finding)104 zeroconf_device_add (zeroconf_finding *finding)
105 {
106 zeroconf_device *device = mem_new(zeroconf_device, 1);
107
108 device->devid = devid_alloc();
109 device->uuid = finding->uuid;
110 device->addrs = ip_addrset_new();
111 if (finding->name != NULL) {
112 device->mdns_name = str_dup(finding->name);
113 }
114 device->model = finding->model;
115
116 ll_init(&device->findings);
117 ll_push_end(&zeroconf_device_list, &device->node_list);
118
119 return device;
120 }
121
122 /* Delete the device
123 */
124 static void
zeroconf_device_del(zeroconf_device * device)125 zeroconf_device_del (zeroconf_device *device)
126 {
127 ll_del(&device->node_list);
128 ip_addrset_free(device->addrs);
129 mem_free((char*) device->mdns_name);
130 devid_free(device->devid);
131 mem_free(device);
132 }
133
134 /* Check if device is MDNS device
135 */
136 static bool
zeroconf_device_is_mdns(zeroconf_device * device)137 zeroconf_device_is_mdns (zeroconf_device *device)
138 {
139 return device->mdns_name != NULL;
140 }
141
142 /* Rebuild device->ifaces, device->protocols and device->methods
143 */
144 static void
zeroconf_device_rebuild_sets(zeroconf_device * device)145 zeroconf_device_rebuild_sets (zeroconf_device *device)
146 {
147 ll_node *node;
148
149 device->protocols = 0;
150 device->methods = 0;
151
152 for (LL_FOR_EACH(node, &device->findings)) {
153 zeroconf_finding *finding;
154 ID_PROTO proto;
155
156 finding = OUTER_STRUCT(node, zeroconf_finding, list_node);
157 proto = zeroconf_method_to_proto(finding->method);
158
159 if (proto != ID_PROTO_UNKNOWN) {
160 device->protocols |= 1 << proto;
161 }
162 device->methods |= 1 << finding->method;
163 }
164 }
165
166 /* Update device->model
167 */
168 static void
zeroconf_device_update_model(zeroconf_device * device)169 zeroconf_device_update_model (zeroconf_device *device)
170 {
171 ll_node *node;
172 zeroconf_finding *hint = NULL, *wsd = NULL;
173
174 for (LL_FOR_EACH(node, &device->findings)) {
175 zeroconf_finding *finding;
176 finding = OUTER_STRUCT(node, zeroconf_finding, list_node);
177
178 switch (finding->method) {
179 case ZEROCONF_USCAN_TCP:
180 case ZEROCONF_USCANS_TCP:
181 device->model = finding->model;
182 return;
183
184 case ZEROCONF_MDNS_HINT:
185 if (hint == NULL) {
186 hint = finding;
187 }
188 break;
189
190 case ZEROCONF_WSD:
191 if (wsd == NULL) {
192 wsd = finding;
193 }
194 break;
195
196 default:
197 log_internal_error(zeroconf_log);
198 }
199 }
200
201 device->model = hint ? hint->model : wsd->model;
202 }
203
204 /* Add zeroconf_finding to zeroconf_device
205 */
206 static void
zeroconf_device_add_finding(zeroconf_device * device,zeroconf_finding * finding)207 zeroconf_device_add_finding (zeroconf_device *device,
208 zeroconf_finding *finding)
209 {
210 log_assert(zeroconf_log, finding->device == NULL);
211
212 finding->device = device;
213
214 ll_push_end(&device->findings, &finding->list_node);
215 ip_addrset_merge(device->addrs, finding->addrs);
216
217 if (finding->endpoints != NULL) {
218 ID_PROTO proto = zeroconf_method_to_proto(finding->method);
219 if (proto != ID_PROTO_UNKNOWN) {
220 device->protocols |= 1 << proto;
221 }
222 device->methods |= 1 << finding->method;
223 }
224
225 zeroconf_device_update_model(device);
226 }
227
228 /* Delete zeroconf_finding from zeroconf_device
229 */
230 static void
zeroconf_device_del_finding(zeroconf_finding * finding)231 zeroconf_device_del_finding (zeroconf_finding *finding)
232 {
233 zeroconf_device *device = finding->device;
234
235 log_assert(zeroconf_log, device != NULL);
236
237 ll_del(&finding->list_node);
238 if (ll_empty(&device->findings)) {
239 zeroconf_device_del(device);
240 return;
241 }
242
243 zeroconf_device_rebuild_sets(device);
244 zeroconf_device_update_model(device);
245 }
246
247 /* Get model name
248 */
249 static const char*
zeroconf_device_model(zeroconf_device * device)250 zeroconf_device_model (zeroconf_device *device)
251 {
252 if (device->model != NULL) {
253 return device->model;
254 }
255
256 /* If model name is not available, fall back to UUID */
257 return device->uuid.text;
258 }
259
260 /* Get device name
261 */
262 static const char*
zeroconf_device_name(zeroconf_device * device)263 zeroconf_device_name (zeroconf_device *device)
264 {
265 if (zeroconf_device_is_mdns(device)) {
266 return device->mdns_name;
267 }
268
269 if (device->buddy != NULL) {
270 return device->buddy->mdns_name;
271 }
272
273 return zeroconf_device_model(device);
274 }
275
276 /* Get protocols, exposed by device
277 */
278 static unsigned int
zeroconf_device_protocols(zeroconf_device * device)279 zeroconf_device_protocols (zeroconf_device *device)
280 {
281 unsigned int protocols = device->protocols;
282
283 if (!conf.proto_auto) {
284 return protocols;
285 }
286
287 if ((protocols & (1 << ID_PROTO_ESCL)) != 0) {
288 return 1 << ID_PROTO_ESCL;
289 }
290
291 if ((protocols & (1 << ID_PROTO_WSD)) != 0) {
292 return 1 << ID_PROTO_WSD;
293 }
294
295 return 0;
296 }
297
298 /* Get device endpoints.
299 * Caller is responsible to free the returned list
300 */
301 static zeroconf_endpoint*
zeroconf_device_endpoints(zeroconf_device * device,ID_PROTO proto)302 zeroconf_device_endpoints (zeroconf_device *device, ID_PROTO proto)
303 {
304 zeroconf_endpoint *endpoints = NULL;
305 ll_node *node;
306
307 for (LL_FOR_EACH(node, &device->findings)) {
308 zeroconf_finding *finding;
309 finding = OUTER_STRUCT(node, zeroconf_finding, list_node);
310
311 if (zeroconf_method_to_proto(finding->method) == proto) {
312 zeroconf_endpoint *ep, *ep2;
313
314 for (ep = finding->endpoints; ep != NULL; ep = ep->next) {
315 ep2 = zeroconf_endpoint_copy_single(ep);
316 ep2->next = endpoints;
317 endpoints = ep2;
318 }
319 }
320 }
321
322 return zeroconf_endpoint_list_sort_dedup(endpoints);
323 }
324
325 /* Find zeroconf_device by ident
326 * Protocol, encoded into ident, returned via second parameter
327 */
328 static zeroconf_device*
zeroconf_device_find_by_ident(const char * ident,ID_PROTO * proto)329 zeroconf_device_find_by_ident (const char *ident, ID_PROTO *proto)
330 {
331 unsigned int devid;
332 const char *name;
333 ll_node *node;
334 zeroconf_device *device = NULL;
335
336 name = zeroconf_ident_split(ident, &devid, proto);
337 if (name == NULL) {
338 return NULL;
339 }
340
341 /* Lookup device */
342 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
343 device = OUTER_STRUCT(node, zeroconf_device, node_list);
344 if (device->devid == devid &&
345 !strcmp(name, zeroconf_device_name(device))) {
346 break;
347 }
348 }
349
350 if (device == NULL)
351 return NULL;
352
353 /* Check that device supports requested protocol */
354 if ((device->protocols & (1 << *proto)) != 0) {
355 return device;
356 }
357
358 return NULL;
359 }
360
361 /* Check if device is blacklisted
362 *
363 * Returns reason string, if device is blacklisted, NULL, if not
364 */
365 static const char*
zeroconf_device_is_blacklisted(zeroconf_device * device)366 zeroconf_device_is_blacklisted (zeroconf_device *device)
367 {
368 conf_blacklist *ent;
369 const char *name, *model;
370
371 if (conf.blacklist == NULL) {
372 return NULL;
373 }
374
375 name = zeroconf_device_name(device);
376 model = zeroconf_device_model(device);
377
378 for (ent = conf.blacklist; ent != NULL; ent = ent->next) {
379 if (ent->name != NULL && !fnmatch(ent->name, name, 0)) {
380 return "name";
381 }
382
383 if (ent->model != NULL && !fnmatch(ent->model, model, 0)) {
384 return "model";
385 }
386
387 if (ent->net.addr.af != AF_UNSPEC &&
388 ip_addrset_on_network(device->addrs, ent->net)) {
389 return "address";
390 }
391 }
392
393 return NULL;
394 }
395
396 /******************** Merging devices *********************/
397 /* Recompute device->buddy for all devices
398 */
399 static void
zeroconf_merge_recompute_buddies(void)400 zeroconf_merge_recompute_buddies (void)
401 {
402 ll_node *node, *node2;
403 zeroconf_device *device, *device2;
404
405 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
406 device = OUTER_STRUCT(node, zeroconf_device, node_list);
407 device->buddy = NULL;
408 }
409
410 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
411 device = OUTER_STRUCT(node, zeroconf_device, node_list);
412
413 for (node2 = ll_next(&zeroconf_device_list, node); node2 != NULL;
414 node2 = ll_next(&zeroconf_device_list, node2)) {
415
416 device2 = OUTER_STRUCT(node2, zeroconf_device, node_list);
417
418 if (zeroconf_device_is_mdns(device) !=
419 zeroconf_device_is_mdns(device2)) {
420 if (ip_addrset_is_intersect(device->addrs, device2->addrs)) {
421 device->buddy = device2;
422 device2->buddy = device;
423 }
424 }
425 }
426 }
427 }
428
429 /* Check that new finding should me merged with existent device
430 */
431 static bool
zeroconf_merge_check(zeroconf_device * device,zeroconf_finding * finding)432 zeroconf_merge_check (zeroconf_device *device, zeroconf_finding *finding)
433 {
434 if ((device->mdns_name == NULL) != (finding->name == NULL)) {
435 return false;
436 }
437
438 if (device->mdns_name != NULL &&
439 strcasecmp(device->mdns_name, finding->name)) {
440 return false;
441 }
442
443 if (uuid_equal(device->uuid, finding->uuid)) {
444 return true;
445 }
446
447 return false;
448 }
449
450 /* Find device, suitable for merging with specified findind
451 */
452 static zeroconf_device*
zeroconf_merge_find(zeroconf_finding * finding)453 zeroconf_merge_find (zeroconf_finding *finding)
454 {
455 ll_node *node;
456
457 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
458 zeroconf_device *device;
459 device = OUTER_STRUCT(node, zeroconf_device, node_list);
460
461 if (zeroconf_merge_check(device, finding)) {
462 return device;
463 }
464 }
465
466 return NULL;
467 }
468
469 /******************** Ident Strings *********************/
470 /* Encode ID_PROTO for device ident
471 */
472 static char
zeroconf_ident_proto_encode(ID_PROTO proto)473 zeroconf_ident_proto_encode (ID_PROTO proto)
474 {
475 switch (proto) {
476 case ID_PROTO_ESCL: return 'e';
477 case ID_PROTO_WSD: return 'w';
478
479 case ID_PROTO_UNKNOWN:
480 case NUM_ID_PROTO:
481 break;
482 }
483
484 log_internal_error(zeroconf_log);
485 return 0;
486 }
487
488 /* Decode ID_PROTO from device ident
489 */
490 static ID_PROTO
zeroconf_ident_proto_decode(char c)491 zeroconf_ident_proto_decode (char c)
492 {
493 switch (c) {
494 case 'e': return ID_PROTO_ESCL;
495 case 'w': return ID_PROTO_WSD;
496 }
497
498 return ID_PROTO_UNKNOWN;
499 }
500
501 /* Make device ident string
502 * The returned string must be released with mem_free()
503 */
504 static const char*
zeroconf_ident_make(const char * name,unsigned int devid,ID_PROTO proto)505 zeroconf_ident_make (const char *name, unsigned int devid, ID_PROTO proto)
506 {
507 return str_printf("%c%x:%s", zeroconf_ident_proto_encode(proto),
508 devid, name);
509 }
510
511 /* Split device ident string.
512 * Returns NULL on error, device name on success.
513 * Device name points somewhere into the input buffer
514 */
515 static const char*
zeroconf_ident_split(const char * ident,unsigned int * devid,ID_PROTO * proto)516 zeroconf_ident_split (const char *ident, unsigned int *devid, ID_PROTO *proto)
517 {
518 const char *name;
519 char *end;
520
521 /* Find name */
522 name = strchr(ident, ':');
523 if (name == NULL) {
524 return NULL;
525 }
526
527 name ++;
528
529 /* Decode proto and devid */
530 *proto = zeroconf_ident_proto_decode(*ident);
531 if (*proto == ID_PROTO_UNKNOWN) {
532 return NULL;
533 }
534
535 ident ++;
536 *devid = (unsigned int) strtoul(ident, &end, 16);
537 if (end == ident || *end != ':') {
538 return NULL;
539 }
540
541 return name;
542 }
543
544 /******************** Endpoints *********************/
545 /* Create new zeroconf_endpoint. Newly created endpoint
546 * takes ownership of uri string
547 */
548 zeroconf_endpoint*
zeroconf_endpoint_new(ID_PROTO proto,http_uri * uri)549 zeroconf_endpoint_new (ID_PROTO proto, http_uri *uri)
550 {
551 zeroconf_endpoint *endpoint = mem_new(zeroconf_endpoint, 1);
552
553 endpoint->proto = proto;
554 endpoint->uri = uri;
555 if (proto == ID_PROTO_ESCL) {
556 // We own the uri, so modify without making a separate copy.
557 http_uri_fix_end_slash(endpoint->uri);
558 }
559
560 return endpoint;
561 }
562
563 /* Clone a single zeroconf_endpoint
564 */
565 static zeroconf_endpoint*
zeroconf_endpoint_copy_single(const zeroconf_endpoint * endpoint)566 zeroconf_endpoint_copy_single (const zeroconf_endpoint *endpoint)
567 {
568 zeroconf_endpoint *endpoint2 = mem_new(zeroconf_endpoint, 1);
569
570 *endpoint2 = *endpoint;
571 endpoint2->uri = http_uri_clone(endpoint->uri);
572 endpoint2->next = NULL;
573
574 return endpoint2;
575 }
576
577 /* Free single zeroconf_endpoint
578 */
579 static void
zeroconf_endpoint_free_single(zeroconf_endpoint * endpoint)580 zeroconf_endpoint_free_single (zeroconf_endpoint *endpoint)
581 {
582 http_uri_free(endpoint->uri);
583 mem_free(endpoint);
584 }
585
586 /* Create a copy of zeroconf_endpoint list
587 */
588 zeroconf_endpoint*
zeroconf_endpoint_list_copy(const zeroconf_endpoint * list)589 zeroconf_endpoint_list_copy (const zeroconf_endpoint *list)
590 {
591 zeroconf_endpoint *newlist = NULL, *last = NULL, *endpoint;
592
593 while (list != NULL) {
594 endpoint = zeroconf_endpoint_copy_single(list);
595 if (last != NULL) {
596 last->next = endpoint;
597 } else {
598 newlist = endpoint;
599 }
600 last = endpoint;
601 list = list->next;
602 }
603
604 return newlist;
605 }
606
607 /* Free zeroconf_endpoint list
608 */
609 void
zeroconf_endpoint_list_free(zeroconf_endpoint * list)610 zeroconf_endpoint_list_free (zeroconf_endpoint *list)
611 {
612 while (list != NULL) {
613 zeroconf_endpoint *next = list->next;
614 zeroconf_endpoint_free_single(list);
615 list = next;
616 }
617 }
618
619 /* Compare two endpoints, for sorting
620 */
621 static int
zeroconf_endpoint_cmp(const zeroconf_endpoint * e1,const zeroconf_endpoint * e2)622 zeroconf_endpoint_cmp (const zeroconf_endpoint *e1, const zeroconf_endpoint *e2)
623 {
624 const struct sockaddr *a1 = http_uri_addr(e1->uri);
625 const struct sockaddr *a2 = http_uri_addr(e2->uri);
626
627 if (a1 != NULL && a2 != NULL) {
628 bool ll1 = ip_sockaddr_is_linklocal(a1);
629 bool ll2 = ip_sockaddr_is_linklocal(a2);
630 int cmp;
631
632 /* Prefer directly reachable addresses */
633 cmp = netif_distance_cmp(a1, a2);
634 if (cmp != 0) {
635 return cmp;
636 }
637
638 /* Prefer normal addresses, rather that link-local */
639 if (ll1 != ll2) {
640 return ll1 ? 1 : -1;
641 }
642
643 /* Be in trend: prefer IPv6 addresses */
644 if (a1->sa_family != a2->sa_family) {
645 return a1->sa_family == AF_INET6 ? -1 : 1;
646 }
647 }
648
649 /* Otherwise, sort lexicographically */
650 return strcmp(http_uri_str(e1->uri), http_uri_str(e2->uri));
651 }
652
653 /* Revert zeroconf_endpoint list
654 */
655 static zeroconf_endpoint*
zeroconf_endpoint_list_revert(zeroconf_endpoint * list)656 zeroconf_endpoint_list_revert (zeroconf_endpoint *list)
657 {
658 zeroconf_endpoint *prev = NULL, *next;
659
660 while (list != NULL) {
661 next = list->next;
662 list->next = prev;
663 prev = list;
664 list = next;
665 }
666
667 return prev;
668 }
669
670 /* Sort list of endpoints
671 */
672 zeroconf_endpoint*
zeroconf_endpoint_list_sort(zeroconf_endpoint * list)673 zeroconf_endpoint_list_sort (zeroconf_endpoint *list)
674 {
675 zeroconf_endpoint *halves[2] = {NULL, NULL};
676 int half = 0;
677
678 if (list == NULL || list->next == NULL) {
679 return list;
680 }
681
682 /* Split list into halves */
683 while (list != NULL) {
684 zeroconf_endpoint *next = list->next;
685
686 list->next = halves[half];
687 halves[half] = list;
688
689 half ^= 1;
690 list = next;
691 }
692
693 /* Sort each half, recursively */
694 for (half = 0; half < 2; half ++) {
695 halves[half] = zeroconf_endpoint_list_sort(halves[half]);
696 }
697
698 /* Now merge the sorted halves */
699 list = NULL;
700 while (halves[0] != NULL || halves[1] != NULL) {
701 zeroconf_endpoint *next;
702
703 if (halves[0] == NULL) {
704 half = 1;
705 } else if (halves[1] == NULL) {
706 half = 0;
707 } else if (zeroconf_endpoint_cmp(halves[0], halves[1]) < 0) {
708 half = 0;
709 } else {
710 half = 1;
711 }
712
713 next = halves[half]->next;
714 halves[half]->next = list;
715 list = halves[half];
716 halves[half] = next;
717 }
718
719 /* And revert the list, as after merging it is reverted */
720 return zeroconf_endpoint_list_revert(list);
721 }
722
723 /* Sort list of endpoints and remove duplicates
724 */
725 zeroconf_endpoint*
zeroconf_endpoint_list_sort_dedup(zeroconf_endpoint * list)726 zeroconf_endpoint_list_sort_dedup (zeroconf_endpoint *list)
727 {
728 zeroconf_endpoint *addr, *next;
729
730 if (list == NULL) {
731 return NULL;
732 }
733
734 list = zeroconf_endpoint_list_sort(list);
735
736 addr = list;
737 while ((next = addr->next) != NULL) {
738 if (zeroconf_endpoint_cmp(addr, next) == 0) {
739 addr->next = next->next;
740 zeroconf_endpoint_free_single(next);
741 } else {
742 addr = next;
743 }
744 }
745
746 return list;
747 }
748
749 /* Check if endpoints list contains a non-link-local address
750 * of the specified address family
751 */
752 bool
zeroconf_endpoint_list_has_non_link_local_addr(int af,const zeroconf_endpoint * list)753 zeroconf_endpoint_list_has_non_link_local_addr (int af,
754 const zeroconf_endpoint *list)
755 {
756 for (;list != NULL; list = list->next) {
757 const struct sockaddr *addr = http_uri_addr(list->uri);
758 if (addr != NULL && addr->sa_family == af) {
759 if (!ip_sockaddr_is_linklocal(addr)) {
760 return true;
761 }
762 }
763 }
764
765 return false;
766 }
767
768 /******************** Static configuration *********************/
769 /* Look for device's static configuration by device name
770 */
771 static conf_device*
zeroconf_find_static_by_name(const char * name)772 zeroconf_find_static_by_name (const char *name)
773 {
774 conf_device *dev_conf;
775
776 for (dev_conf = conf.devices; dev_conf != NULL; dev_conf = dev_conf->next) {
777 if (!strcasecmp(dev_conf->name, name)) {
778 return dev_conf;
779 }
780 }
781
782 return NULL;
783 }
784
785 /* Look for device's static configuration by device ident
786 */
787 static conf_device*
zeroconf_find_static_by_ident(const char * ident)788 zeroconf_find_static_by_ident (const char *ident)
789 {
790 conf_device *dev_conf;
791 ID_PROTO proto;
792 unsigned int devid;
793 const char *name;
794
795 name = zeroconf_ident_split(ident, &devid, &proto);
796 if (name == NULL) {
797 return NULL;
798 }
799
800 for (dev_conf = conf.devices; dev_conf != NULL; dev_conf = dev_conf->next) {
801 if (dev_conf->devid == devid &&
802 dev_conf->proto == proto &&
803 !strcmp(dev_conf->name, name)) {
804 return dev_conf;
805 }
806 }
807
808 return NULL;
809 }
810
811 /******************** Events from discovery providers *********************/
812 /* Publish the zeroconf_finding.
813 */
814 void
zeroconf_finding_publish(zeroconf_finding * finding)815 zeroconf_finding_publish (zeroconf_finding *finding)
816 {
817 size_t count, i;
818 zeroconf_device *device;
819 char ifname[IF_NAMESIZE];
820 const ip_addr *addrs;
821 ID_PROTO proto = zeroconf_method_to_proto(finding->method);
822
823 /* Print log messages */
824 if (if_indextoname(finding->ifindex, ifname) == NULL) {
825 strcpy(ifname, "?");
826 }
827
828 log_debug(zeroconf_log, "found %s", finding->uuid.text);
829 log_debug(zeroconf_log, " method: %s",
830 zeroconf_method_name(finding->method));
831 log_debug(zeroconf_log, " interface: %d (%s)", finding->ifindex, ifname);
832 log_debug(zeroconf_log, " name: %s",
833 finding->name ? finding->name : "-");
834 log_debug(zeroconf_log, " model: %s",
835 finding->model ? finding->model : "-");
836
837 log_debug(zeroconf_log, " addresses:");
838 addrs = ip_addrset_addresses(finding->addrs, &count);
839 for (i = 0; i < count; i ++) {
840 ip_straddr straddr = ip_addr_to_straddr(addrs[i], true);
841 log_debug(zeroconf_log, " %s", straddr.text);
842 }
843
844 if (proto != ID_PROTO_UNKNOWN) {
845 zeroconf_endpoint *ep;
846
847 log_debug(zeroconf_log, " protocol: %s", id_proto_name(proto));
848 log_debug(zeroconf_log, " endpoints:");
849 for (ep = finding->endpoints; ep != NULL; ep = ep->next) {
850 log_debug(zeroconf_log, " %s", http_uri_str(ep->uri));
851 }
852 }
853
854 /* Handle new finding */
855 device = zeroconf_merge_find(finding);
856 if (device != NULL) {
857 log_debug(zeroconf_log, " device: %4.4x (found)", device->devid);
858 } else {
859 device = zeroconf_device_add(finding);
860 log_debug(zeroconf_log, " device: %4.4x (created)", device->devid);
861 }
862
863 zeroconf_device_add_finding(device, finding);
864 zeroconf_merge_recompute_buddies();
865 pthread_cond_broadcast(&zeroconf_initscan_cond);
866 }
867
868 /* Withdraw the finding
869 */
870 void
zeroconf_finding_withdraw(zeroconf_finding * finding)871 zeroconf_finding_withdraw (zeroconf_finding *finding)
872 {
873 char ifname[IF_NAMESIZE] = "?";
874
875 if_indextoname(finding->ifindex, ifname);
876
877 log_debug(zeroconf_log, "device gone %s", finding->uuid.text);
878 log_debug(zeroconf_log, " method: %s", zeroconf_method_name(finding->method));
879 log_debug(zeroconf_log, " interface: %d (%s)", finding->ifindex, ifname);
880
881 zeroconf_device_del_finding(finding);
882 zeroconf_merge_recompute_buddies();
883 pthread_cond_broadcast(&zeroconf_initscan_cond);
884 }
885
886 /* Notify zeroconf subsystem that initial scan
887 * for the method is done
888 */
889 void
zeroconf_finding_done(ZEROCONF_METHOD method)890 zeroconf_finding_done (ZEROCONF_METHOD method)
891 {
892 log_debug(zeroconf_log, "%s: initial scan finished",
893 zeroconf_method_name(method));
894
895 zeroconf_initscan_bits &= ~(1 << method);
896 pthread_cond_broadcast(&zeroconf_initscan_cond);
897 }
898
899 /******************** Support for SANE API *********************/
900 /* zeroconf_initscan_timer callback
901 */
902 static void
zeroconf_initscan_timer_callback(void * unused)903 zeroconf_initscan_timer_callback (void *unused)
904 {
905 (void) unused;
906
907 log_debug(zeroconf_log, "initial scan timer expired");
908
909 mdns_initscan_timer_expired();
910 wsdd_initscan_timer_expired();
911
912 zeroconf_initscan_timer = NULL;
913 pthread_cond_broadcast(&zeroconf_initscan_cond);
914 }
915
916 /* Check if initial scan is done
917 */
918 static bool
zeroconf_initscan_done(void)919 zeroconf_initscan_done (void)
920 {
921 ll_node *node;
922 zeroconf_device *device;
923
924 /* If all discovery methods are done, we are done */
925 if (zeroconf_initscan_bits == 0) {
926 return true;
927 }
928
929 /* Regardless of options, all DNS-SD methods must be done */
930 if ((zeroconf_initscan_bits & ~(1 << ZEROCONF_WSD)) != 0) {
931 log_debug(zeroconf_log, "device_list wait: DNS-SD not finished...");
932 return false;
933 }
934
935 /* If we are here, ZEROCONF_WSD is not done yet,
936 * and if we are not in fast-wsdd mode, we must wait
937 */
938 log_assert(zeroconf_log,
939 (zeroconf_initscan_bits & (1 << ZEROCONF_WSD)) != 0);
940
941 if (conf.wsdd_mode != WSDD_FAST) {
942 log_debug(zeroconf_log, "device_list wait: WSDD not finished...");
943 return false;
944 }
945
946 /* Check for completion, device by device:
947 *
948 * In manual protocol switch mode, WSDD buddy must be
949 * found for device, so we have a choice. Otherwise, it's
950 * enough if device has supported protocols
951 */
952 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
953 device = OUTER_STRUCT(node, zeroconf_device, node_list);
954
955 if (!conf.proto_auto) {
956 if (zeroconf_device_is_mdns(device) && device->buddy == NULL) {
957 log_debug(zeroconf_log,
958 "device_list wait: waiting for WSDD buddy for '%s' (%d)",
959 zeroconf_device_name(device), device->devid);
960 return false;
961 }
962 } else {
963 if (device->protocols == 0) {
964 log_debug(zeroconf_log,
965 "device_list wait: waiting for any proto for '%s' (%d)",
966 zeroconf_device_name(device), device->devid);
967 return false;
968 }
969 }
970 }
971
972 return true;
973 }
974
975 /* Wait until initial scan is done
976 */
977 static void
zeroconf_initscan_wait(void)978 zeroconf_initscan_wait (void)
979 {
980 bool ok = false;
981
982 log_debug(zeroconf_log, "device_list wait: requested");
983
984 for (;;) {
985 ok = zeroconf_initscan_done();
986 if (ok || zeroconf_initscan_timer == NULL) {
987 break;
988 }
989 eloop_cond_wait(&zeroconf_initscan_cond);
990 }
991
992 log_debug(zeroconf_log, "device_list wait: %s", ok ? "OK" : "timeout" );
993 }
994
995 /* Compare SANE_Device*, for qsort
996 */
997 static int
zeroconf_device_list_qsort_cmp(const void * p1,const void * p2)998 zeroconf_device_list_qsort_cmp (const void *p1, const void *p2)
999 {
1000 int cmp;
1001 const SANE_Device *d1 = *(SANE_Device**) p1;
1002 const SANE_Device *d2 = *(SANE_Device**) p2;
1003
1004 cmp = strcasecmp(d1->model, d2->model);
1005 if (cmp == 0) {
1006 cmp = strcasecmp(d1->vendor, d2->vendor);
1007 }
1008 if (cmp == 0) {
1009 cmp = strcmp(d1->name, d2->name);
1010 }
1011
1012 return cmp;
1013 }
1014
1015 /* Format list of protocols, for zeroconf_device_list_log
1016 */
1017 static void
zeroconf_device_list_fmt_protocols(char * buf,size_t buflen,unsigned int protocols)1018 zeroconf_device_list_fmt_protocols (char *buf, size_t buflen, unsigned int protocols)
1019 {
1020 ID_PROTO proto;
1021 size_t off = 0;
1022
1023 buf[0] = '\0';
1024 for (proto = 0; proto < NUM_ID_PROTO; proto ++) {
1025 if ((protocols & (1 << proto)) != 0) {
1026 off += snprintf(buf + off, buflen - off, " %s",
1027 id_proto_name(proto));
1028 }
1029 }
1030
1031 if (buf[0] == '\0') {
1032 strcpy(buf, " none");
1033 }
1034 }
1035
1036 /* Log device information in a context of zeroconf_device_list_get
1037 */
1038 static void
zeroconf_device_list_log(zeroconf_device * device,const char * name,unsigned int protocols)1039 zeroconf_device_list_log (zeroconf_device *device, const char *name,
1040 unsigned int protocols)
1041 {
1042 char can[64];
1043 char use[64];
1044
1045 zeroconf_device_list_fmt_protocols(can, sizeof(can), device->protocols);
1046 zeroconf_device_list_fmt_protocols(use, sizeof(use), protocols);
1047
1048 log_debug(zeroconf_log, "%s (%d): can:%s, use:%s", name, device->devid,
1049 can, use);
1050 }
1051
1052 /* Get list of devices, in SANE format
1053 */
1054 const SANE_Device**
zeroconf_device_list_get(void)1055 zeroconf_device_list_get (void)
1056 {
1057 size_t dev_count = 0, dev_count_static = 0;
1058 conf_device *dev_conf;
1059 const SANE_Device **dev_list = sane_device_array_new();
1060 ll_node *node;
1061 int i;
1062
1063 log_debug(zeroconf_log, "zeroconf_device_list_get: requested");
1064
1065 /* Wait until device table is ready */
1066 zeroconf_initscan_wait();
1067
1068 /* Build list of devices */
1069 log_debug(zeroconf_log, "zeroconf_device_list_get: building list of devices");
1070
1071 dev_count = 0;
1072
1073 for (dev_conf = conf.devices; dev_conf != NULL; dev_conf = dev_conf->next) {
1074 SANE_Device *info;
1075 const char *proto;
1076 const char *host;
1077 size_t hostlen;
1078
1079 if (dev_conf->uri == NULL) {
1080 continue;
1081 }
1082
1083 info = mem_new(SANE_Device, 1);
1084 proto = id_proto_name(dev_conf->proto);
1085
1086 dev_list = sane_device_array_append(dev_list, info);
1087 dev_count ++;
1088
1089 info->name = zeroconf_ident_make(dev_conf->name, dev_conf->devid,
1090 dev_conf->proto);
1091 info->vendor = str_dup(proto);
1092 info->model = str_dup(dev_conf->name);
1093
1094 host = http_uri_get_host(dev_conf->uri);
1095 hostlen = strlen(host);
1096 if (host[0] == '[') {
1097 host ++;
1098 hostlen -= 2;
1099 }
1100
1101 info->type = str_printf("ip=%.*s", (int) hostlen, host);
1102 }
1103
1104 dev_count_static = dev_count;
1105
1106 for (LL_FOR_EACH(node, &zeroconf_device_list)) {
1107 zeroconf_device *device;
1108 ID_PROTO proto;
1109 const char *name, *model, *blacklisted;
1110 unsigned int protocols;
1111
1112 device = OUTER_STRUCT(node, zeroconf_device, node_list);
1113 name = zeroconf_device_name(device);
1114 model = zeroconf_device_model(device);
1115 protocols = zeroconf_device_protocols(device);
1116
1117 zeroconf_device_list_log(device, name, protocols);
1118
1119 if (zeroconf_find_static_by_name(name) != NULL) {
1120 /* Static configuration overrides discovery */
1121 log_debug(zeroconf_log,
1122 "%s (%d): skipping, device clashes statically configured",
1123 name, device->devid);
1124 continue;
1125 }
1126
1127 blacklisted = zeroconf_device_is_blacklisted(device);
1128 if (blacklisted != NULL) {
1129 log_debug(zeroconf_log,
1130 "%s (%d): skipping, device is blacklisted by %s",
1131 name, device->devid, blacklisted);
1132 continue;
1133 }
1134
1135 if (conf.proto_auto && !zeroconf_device_is_mdns(device)) {
1136 zeroconf_device *device2 = device->buddy;
1137 if (device2 != NULL && zeroconf_device_protocols(device2) != 0) {
1138 log_debug(zeroconf_log,
1139 "%s (%d): skipping, shadowed by %s (%d)",
1140 name, device->devid,
1141 zeroconf_device_name(device2), device2->devid);
1142 continue;
1143 }
1144 }
1145
1146 if (protocols == 0) {
1147 log_debug(zeroconf_log,
1148 "%s (%d): skipping, none of supported protocols discovered",
1149 name, device->devid);
1150 continue;
1151 }
1152
1153 for (proto = 0; proto < NUM_ID_PROTO; proto ++) {
1154 if ((protocols & (1 << proto)) != 0) {
1155 SANE_Device *info = mem_new(SANE_Device, 1);
1156 const char *proto_name = id_proto_name(proto);
1157 char *type;
1158
1159 dev_list = sane_device_array_append(dev_list, info);
1160 dev_count ++;
1161
1162 info->name = zeroconf_ident_make(name, device->devid, proto);
1163 info->vendor = str_dup(proto_name);
1164 info->model = str_dup(conf.model_is_netname ? name : model);
1165
1166 //info->type = str_printf("%s network scanner", proto_name);
1167 type = str_printf("ip=", proto_name);
1168 type = ip_addrset_friendly_str(device->addrs, type);
1169 info->type = type;
1170 }
1171 }
1172 }
1173
1174 qsort(dev_list + dev_count_static, dev_count - dev_count_static,
1175 sizeof(*dev_list), zeroconf_device_list_qsort_cmp);
1176
1177 log_debug(zeroconf_log, "zeroconf_device_list_get: resulting list:");
1178 for (i = 0; dev_list[i] != NULL; i ++) {
1179 log_debug(zeroconf_log,
1180 " %-4s \"%s\"", dev_list[i]->vendor, dev_list[i]->name);
1181 }
1182
1183 return dev_list;
1184 }
1185
1186 /* Free list of devices, returned by zeroconf_device_list_get()
1187 */
1188 void
zeroconf_device_list_free(const SANE_Device ** dev_list)1189 zeroconf_device_list_free (const SANE_Device **dev_list)
1190 {
1191 if (dev_list != NULL) {
1192 unsigned int i;
1193 const SANE_Device *info;
1194
1195 for (i = 0; (info = dev_list[i]) != NULL; i ++) {
1196 mem_free((void*) info->name);
1197 mem_free((void*) info->vendor);
1198 mem_free((void*) info->model);
1199 mem_free((void*) info->type);
1200 mem_free((void*) info);
1201 }
1202
1203 sane_device_array_free(dev_list);
1204 }
1205 }
1206
1207 /*
1208 * The format "protocol:name:url" is accepted to directly specify a device
1209 * without listing it in the config or finding it with autodiscovery. Try
1210 * to parse an identifier as that format. On success, returns a newly allocated
1211 * zeroconf_devinfo that the caller must free with zeroconf_devinfo_free(). On
1212 * failure, returns NULL.
1213 */
1214 static zeroconf_devinfo*
zeroconf_parse_devinfo_from_ident(const char * ident)1215 zeroconf_parse_devinfo_from_ident(const char *ident)
1216 {
1217 int buf_size;
1218 char *buf = NULL;
1219 ID_PROTO proto;
1220 char *name;
1221 char *uri_str;
1222 http_uri *uri;
1223 zeroconf_devinfo *devinfo;
1224
1225 if (ident == NULL) {
1226 return NULL;
1227 }
1228
1229 /* Copy the string so we can modify it in place while parsing. */
1230 buf_size = strlen(ident) + 1;
1231 buf = alloca(buf_size);
1232 memcpy(buf, ident, buf_size);
1233
1234 name = strchr(buf, ':');
1235 if (name == NULL) {
1236 return NULL;
1237 }
1238 *name = '\0';
1239 name++;
1240
1241 proto = id_proto_by_name(buf);
1242 if (proto == ID_PROTO_UNKNOWN) {
1243 return NULL;
1244 }
1245
1246 uri_str = strchr(name, ':');
1247 if (uri_str == NULL) {
1248 return NULL;
1249 }
1250 *uri_str = '\0';
1251 uri_str++;
1252
1253 if (*name == '\0') {
1254 return NULL;
1255 }
1256
1257 uri = http_uri_new(uri_str, true);
1258 if (uri == NULL) {
1259 return NULL;
1260 }
1261
1262 /* Build a zeroconf_devinfo */
1263 devinfo = mem_new(zeroconf_devinfo, 1);
1264 devinfo->ident = str_dup(ident);
1265 devinfo->name = str_dup(name);
1266 devinfo->endpoints = zeroconf_endpoint_new(proto, uri);
1267 return devinfo;
1268 }
1269
1270 /* Lookup device by ident (ident is reported as SANE_Device::name)
1271 * by zeroconf_device_list_get())
1272 *
1273 * Caller becomes owner of resources (name and list of endpoints),
1274 * referred by the returned zeroconf_devinfo
1275 *
1276 * Caller must free these resources, using zeroconf_devinfo_free()
1277 */
1278 zeroconf_devinfo*
zeroconf_devinfo_lookup(const char * ident)1279 zeroconf_devinfo_lookup (const char *ident)
1280 {
1281 conf_device *dev_conf = NULL;
1282 zeroconf_device *device = NULL;
1283 zeroconf_devinfo *devinfo;
1284 ID_PROTO proto = ID_PROTO_UNKNOWN;
1285
1286 /* Check if the caller passed a direct device specification first. */
1287 devinfo = zeroconf_parse_devinfo_from_ident(ident);
1288 if (devinfo != NULL) {
1289 return devinfo;
1290 }
1291
1292 /* Wait until device table is ready */
1293 zeroconf_initscan_wait();
1294
1295 /* Lookup a device, static first */
1296 dev_conf = zeroconf_find_static_by_ident(ident);
1297 if (dev_conf == NULL) {
1298 device = zeroconf_device_find_by_ident(ident, &proto);
1299 if (device == NULL) {
1300 return NULL;
1301 }
1302 }
1303
1304 /* Build a zeroconf_devinfo */
1305 devinfo = mem_new(zeroconf_devinfo, 1);
1306 devinfo->ident = str_dup(ident);
1307
1308 if (dev_conf != NULL) {
1309 http_uri *uri = http_uri_clone(dev_conf->uri);
1310 devinfo->name = str_dup(dev_conf->name);
1311 devinfo->endpoints = zeroconf_endpoint_new(dev_conf->proto, uri);
1312 } else {
1313 devinfo->name = str_dup(zeroconf_device_name(device));
1314 devinfo->endpoints = zeroconf_device_endpoints(device, proto);
1315 }
1316
1317 return devinfo;
1318 }
1319
1320 /* Free zeroconf_devinfo, returned by zeroconf_devinfo_lookup()
1321 */
1322 void
zeroconf_devinfo_free(zeroconf_devinfo * devinfo)1323 zeroconf_devinfo_free (zeroconf_devinfo *devinfo)
1324 {
1325 mem_free((char*) devinfo->ident);
1326 mem_free((char*) devinfo->name);
1327 zeroconf_endpoint_list_free(devinfo->endpoints);
1328 mem_free(devinfo);
1329 }
1330
1331 /******************** Initialization and cleanup *********************/
1332 /* ZeroConf start/stop callback
1333 */
1334 static void
zeroconf_start_stop_callback(bool start)1335 zeroconf_start_stop_callback (bool start)
1336 {
1337 if (start) {
1338 zeroconf_initscan_timer = eloop_timer_new(ZEROCONF_READY_TIMEOUT * 1000,
1339 zeroconf_initscan_timer_callback, NULL);
1340 } else {
1341 if (zeroconf_initscan_timer != NULL) {
1342 eloop_timer_cancel(zeroconf_initscan_timer);
1343 zeroconf_initscan_timer = NULL;
1344 }
1345
1346 pthread_cond_broadcast(&zeroconf_initscan_cond);
1347 }
1348 }
1349
1350 /* Initialize ZeroConf
1351 */
1352 SANE_Status
zeroconf_init(void)1353 zeroconf_init (void)
1354 {
1355 char *s;
1356 conf_device *dev;
1357
1358 /* Initialize zeroconf */
1359 zeroconf_log = log_ctx_new("zeroconf", NULL);
1360
1361 ll_init(&zeroconf_device_list);
1362
1363 pthread_cond_init(&zeroconf_initscan_cond, NULL);
1364
1365 if (conf.discovery) {
1366 zeroconf_initscan_bits = (1 << ZEROCONF_MDNS_HINT) |
1367 (1 << ZEROCONF_USCAN_TCP) |
1368 (1 << ZEROCONF_USCANS_TCP) |
1369 (1 << ZEROCONF_WSD);
1370 }
1371
1372 eloop_add_start_stop_callback(zeroconf_start_stop_callback);
1373
1374 /* Dump zeroconf configuration to the log */
1375 log_trace(zeroconf_log, "zeroconf configuration:");
1376
1377 s = conf.discovery ? "enable" : "disable";
1378 log_trace(zeroconf_log, " discovery = %s", s);
1379
1380 s = conf.model_is_netname ? "network" : "hardware";
1381 log_trace(zeroconf_log, " model = %s", s);
1382
1383 s = conf.proto_auto ? "auto" : "manual";
1384 log_trace(zeroconf_log, " protocol = %s", s);
1385
1386 s = "?";
1387 (void) s; /* Silence CLANG analyzer warning */
1388
1389 switch (conf.wsdd_mode) {
1390 case WSDD_FAST: s = "fast"; break;
1391 case WSDD_FULL: s = "full"; break;
1392 case WSDD_OFF: s = "OFF"; break;
1393 }
1394 log_trace(zeroconf_log, " ws-discovery = %s", s);
1395
1396 if (conf.devices != NULL) {
1397 log_trace(zeroconf_log, "statically configured devices:");
1398
1399 for (dev = conf.devices; dev != NULL; dev = dev->next) {
1400 if (dev->uri != NULL) {
1401 log_trace(zeroconf_log, " %s = %s, %s", dev->name,
1402 http_uri_str(dev->uri), id_proto_name(dev->proto));
1403 } else {
1404 log_trace(zeroconf_log, " %s = disable", dev->name);
1405 }
1406 }
1407 }
1408
1409 if (conf.blacklist != NULL) {
1410 conf_blacklist *ent;
1411
1412 log_trace(zeroconf_log, "blacklist:");
1413 for (ent = conf.blacklist; ent != NULL; ent = ent->next) {
1414 if (ent->model != NULL) {
1415 log_trace(zeroconf_log, " model = %s", ent->model);
1416 }
1417
1418 if (ent->name != NULL) {
1419 log_trace(zeroconf_log, " name = %s", ent->name);
1420 }
1421
1422 if (ent->net.addr.af != AF_UNSPEC) {
1423 ip_straddr straddr = ip_network_to_straddr(ent->net);
1424 log_trace(zeroconf_log, " ip = %s", straddr.text);
1425 }
1426 }
1427 }
1428
1429 return SANE_STATUS_GOOD;
1430 }
1431
1432 /* Cleanup ZeroConf
1433 */
1434 void
zeroconf_cleanup(void)1435 zeroconf_cleanup (void)
1436 {
1437 if (zeroconf_log != NULL) {
1438 log_ctx_free(zeroconf_log);
1439 zeroconf_log = NULL;
1440 pthread_cond_destroy(&zeroconf_initscan_cond);
1441 }
1442 }
1443
1444 /* vim:ts=8:sw=4:et
1445 */
1446