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