]> git.meshlink.io Git - catta/blob - avahi-core/browse.c
CVE-2009-0758: Reflector creates packet storm on legacy unicast traffic
[catta] / avahi-core / browse.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10  
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27
28 #include <avahi-common/timeval.h>
29 #include <avahi-common/malloc.h>
30 #include <avahi-common/error.h>
31 #include <avahi-common/domain.h>
32 #include <avahi-common/rlist.h>
33 #include <avahi-common/address.h>
34
35 #include "browse.h"
36 #include "log.h"
37 #include "querier.h"
38 #include "domain-util.h"
39 #include "rr-util.h"
40
41 #define AVAHI_LOOKUPS_PER_BROWSER_MAX 15
42
43 struct AvahiSRBLookup {
44     AvahiSRecordBrowser *record_browser;
45     
46     unsigned ref;
47
48     AvahiIfIndex interface;
49     AvahiProtocol protocol;
50     AvahiLookupFlags flags;
51     
52     AvahiKey *key;
53
54     AvahiWideAreaLookup *wide_area;
55     AvahiMulticastLookup *multicast;
56
57     AvahiRList *cname_lookups;
58     
59     AVAHI_LLIST_FIELDS(AvahiSRBLookup, lookups);
60 };
61
62 static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r);
63 static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r);
64
65 static void transport_flags_from_domain(AvahiServer *s, AvahiLookupFlags *flags, const char *domain) {
66     assert(flags);
67     assert(domain);
68
69     assert(!((*flags & AVAHI_LOOKUP_USE_MULTICAST) && (*flags & AVAHI_LOOKUP_USE_WIDE_AREA)));
70
71     if (*flags & (AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_USE_WIDE_AREA))
72         return;
73
74     if (!s->wide_area_lookup_engine ||
75         !avahi_wide_area_has_servers(s->wide_area_lookup_engine) ||
76         avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_LOCAL) ||
77         avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV4) ||
78         avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV6))
79         *flags |= AVAHI_LOOKUP_USE_MULTICAST;
80     else
81         *flags |= AVAHI_LOOKUP_USE_WIDE_AREA;
82 }
83
84 static AvahiSRBLookup* lookup_new(
85     AvahiSRecordBrowser *b,
86     AvahiIfIndex interface,
87     AvahiProtocol protocol,
88     AvahiLookupFlags flags,
89     AvahiKey *key) {
90
91     AvahiSRBLookup *l;
92     
93     assert(b);
94     assert(AVAHI_IF_VALID(interface));
95     assert(AVAHI_PROTO_VALID(protocol));
96
97     if (b->n_lookups >= AVAHI_LOOKUPS_PER_BROWSER_MAX)
98         /* We don't like cyclic CNAMEs */
99         return NULL;
100     
101     if (!(l = avahi_new(AvahiSRBLookup, 1)))
102         return NULL;
103     
104     l->ref = 1;
105     l->record_browser = b;
106     l->interface = interface;
107     l->protocol = protocol;
108     l->key = avahi_key_ref(key);
109     l->wide_area = NULL;
110     l->multicast = NULL;
111     l->cname_lookups = NULL;
112     l->flags = flags;
113
114     transport_flags_from_domain(b->server, &l->flags, key->name);
115     
116     AVAHI_LLIST_PREPEND(AvahiSRBLookup, lookups, b->lookups, l);
117
118     b->n_lookups ++;
119     
120     return l;
121 }
122
123 static void lookup_unref(AvahiSRBLookup *l) {
124     assert(l);
125     assert(l->ref >= 1);
126
127     if (--l->ref >= 1)
128         return;
129
130     AVAHI_LLIST_REMOVE(AvahiSRBLookup, lookups, l->record_browser->lookups, l);
131     l->record_browser->n_lookups --;
132
133     if (l->wide_area) {
134         avahi_wide_area_lookup_free(l->wide_area);
135         l->wide_area = NULL;
136     }
137     
138     if (l->multicast) {
139         avahi_multicast_lookup_free(l->multicast);
140         l->multicast = NULL;
141     }
142
143     while (l->cname_lookups) {
144         lookup_unref(l->cname_lookups->data);
145         l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, l->cname_lookups);
146     }
147     
148     avahi_key_unref(l->key);
149     avahi_free(l);
150 }
151
152 static AvahiSRBLookup* lookup_ref(AvahiSRBLookup *l) {
153     assert(l);
154     assert(l->ref >= 1);
155
156     l->ref++;
157     return l;
158 }
159
160 static AvahiSRBLookup *lookup_find(
161     AvahiSRecordBrowser *b,
162     AvahiIfIndex interface,
163     AvahiProtocol protocol,
164     AvahiLookupFlags flags,
165     AvahiKey *key) {
166     
167     AvahiSRBLookup *l;
168     
169     assert(b);
170
171     for (l = b->lookups; l; l = l->lookups_next) {
172
173         if ((l->interface == AVAHI_IF_UNSPEC || l->interface == interface) &&
174             (l->interface == AVAHI_PROTO_UNSPEC || l->protocol == protocol) &&
175             l->flags == flags &&
176             avahi_key_equal(l->key, key))
177
178             return l;
179     }
180
181     return NULL;
182 }
183
184 static void browser_cancel(AvahiSRecordBrowser *b) {
185     assert(b);
186
187     if (b->root_lookup) {
188         lookup_unref(b->root_lookup);
189         b->root_lookup = NULL;
190     }
191
192     if (b->defer_time_event) {
193         avahi_time_event_free(b->defer_time_event);
194         b->defer_time_event = NULL;
195     }
196 }
197
198 static void lookup_wide_area_callback(
199     AvahiWideAreaLookupEngine *e,
200     AvahiBrowserEvent event,
201     AvahiLookupResultFlags flags,
202     AvahiRecord *r,
203     void *userdata) {
204     
205     AvahiSRBLookup *l = userdata;
206     AvahiSRecordBrowser *b;
207
208     assert(e);
209     assert(l);
210     assert(l->ref >= 1);
211
212     b = l->record_browser;
213
214     if (b->dead)
215         return;
216
217     lookup_ref(l);
218     
219     switch (event) {
220         case AVAHI_BROWSER_NEW:
221             assert(r);
222             
223             if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
224                 r->key->type == AVAHI_DNS_TYPE_CNAME)
225                 /* It's a CNAME record, so let's follow it. We only follow it on wide area DNS! */
226                 lookup_handle_cname(l, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_WIDE_AREA, r);
227             else {
228                 /* It's a normal record, so let's call the user callback */
229                 assert(avahi_key_equal(r->key, l->key));
230
231                 b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, r, flags, b->userdata);
232             }
233             break;
234
235         case AVAHI_BROWSER_REMOVE:
236         case AVAHI_BROWSER_CACHE_EXHAUSTED:
237             /* Not defined for wide area DNS */
238             abort();
239
240         case AVAHI_BROWSER_ALL_FOR_NOW:
241         case AVAHI_BROWSER_FAILURE:
242
243             b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata);
244             break;
245     }
246
247     lookup_unref(l);
248
249 }
250
251 static void lookup_multicast_callback(
252     AvahiMulticastLookupEngine *e,
253     AvahiIfIndex interface,
254     AvahiProtocol protocol,
255     AvahiBrowserEvent event,
256     AvahiLookupResultFlags flags,
257     AvahiRecord *r,
258     void *userdata) {
259
260     AvahiSRBLookup *l = userdata;
261     AvahiSRecordBrowser *b;
262     
263     assert(e);
264     assert(l);
265
266     b = l->record_browser;
267
268     if (b->dead)
269         return;
270
271     lookup_ref(l);
272     
273     switch (event) {
274         case AVAHI_BROWSER_NEW:
275             assert(r);
276             
277             if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
278                 r->key->type == AVAHI_DNS_TYPE_CNAME)
279                 /* It's a CNAME record, so let's follow it. We allow browsing on both multicast and wide area. */
280                 lookup_handle_cname(l, interface, protocol, b->flags, r);
281             else {
282                 /* It's a normal record, so let's call the user callback */
283
284                 if (avahi_server_is_record_local(b->server, interface, protocol, r))
285                     flags |= AVAHI_LOOKUP_RESULT_LOCAL;
286                 
287                 b->callback(b, interface, protocol, event, r, flags, b->userdata);
288             }
289             break;
290
291         case AVAHI_BROWSER_REMOVE:
292             assert(r);
293
294             if (r->key->clazz == AVAHI_DNS_CLASS_IN &&
295                 r->key->type == AVAHI_DNS_TYPE_CNAME)
296                 /* It's a CNAME record, so let's drop that query! */
297                 lookup_drop_cname(l, interface, protocol, 0, r);
298             else {
299                 /* It's a normal record, so let's call the user callback */
300                 assert(avahi_key_equal(b->key, l->key));
301
302                 b->callback(b, interface, protocol, event, r, flags, b->userdata);
303             }
304             break;
305
306         case AVAHI_BROWSER_ALL_FOR_NOW:
307
308             b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata);
309             break;
310
311         case AVAHI_BROWSER_CACHE_EXHAUSTED:
312         case AVAHI_BROWSER_FAILURE:
313             /* Not defined for multicast DNS */
314             abort();
315
316     }
317
318     lookup_unref(l);
319 }
320
321 static int lookup_start(AvahiSRBLookup *l) {
322     assert(l);
323
324     assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST));
325     assert(!l->wide_area && !l->multicast);
326     
327     if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) {
328
329         if (!(l->wide_area = avahi_wide_area_lookup_new(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l)))
330             return -1;
331         
332     } else {
333         assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST);
334
335         if (!(l->multicast = avahi_multicast_lookup_new(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l)))
336             return -1;
337     }
338
339     return 0;
340 }
341
342 static int lookup_scan_cache(AvahiSRBLookup *l) {
343     int n = 0;
344     
345     assert(l);
346
347     assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST));
348
349     
350     if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) {
351         n = (int) avahi_wide_area_scan_cache(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l);
352         
353     } else {
354         assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST);
355         n = (int) avahi_multicast_lookup_engine_scan_cache(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l);
356     }
357
358     return n;
359 }
360
361 static AvahiSRBLookup* lookup_add(AvahiSRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiKey *key) {
362     AvahiSRBLookup *l;
363     
364     assert(b);
365     assert(!b->dead);
366
367     if ((l = lookup_find(b, interface, protocol, flags, key)))
368         return lookup_ref(l);
369
370     if (!(l = lookup_new(b, interface, protocol, flags, key)))
371         return NULL;
372
373     return l;
374 }
375
376 static int lookup_go(AvahiSRBLookup *l) {
377     int n = 0;
378     assert(l);
379
380     if (l->record_browser->dead)
381         return 0;
382     
383     lookup_ref(l);
384     
385     /* Browse the cache for the root request */
386     n = lookup_scan_cache(l);
387
388     /* Start the lookup */
389     if (!l->record_browser->dead && l->ref > 1) {
390
391         if ((l->flags & AVAHI_LOOKUP_USE_MULTICAST) || n == 0)
392             /* We do no start a query if the cache contained entries and we're on wide area */
393             
394             if (lookup_start(l) < 0)
395                 n = -1;
396     }
397
398     lookup_unref(l);
399
400     return n;
401 }
402
403 static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) {
404     AvahiKey *k;
405     AvahiSRBLookup *n;
406     
407     assert(l);
408     assert(r);
409
410     assert(r->key->clazz == AVAHI_DNS_CLASS_IN);
411     assert(r->key->type == AVAHI_DNS_TYPE_CNAME);
412
413     k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type);
414     n = lookup_add(l->record_browser, interface, protocol, flags, k); 
415     avahi_key_unref(k);
416
417     if (!n) {
418         avahi_log_debug(__FILE__": Failed to create SRBLookup.");
419         return;
420     }
421
422     l->cname_lookups = avahi_rlist_prepend(l->cname_lookups, lookup_ref(n));
423
424     lookup_go(n);
425     lookup_unref(n);
426 }
427
428 static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) {
429     AvahiKey *k;
430     AvahiSRBLookup *n = NULL;
431     AvahiRList *rl;
432
433     assert(r->key->clazz == AVAHI_DNS_CLASS_IN);
434     assert(r->key->type == AVAHI_DNS_TYPE_CNAME);
435
436     k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type);
437
438     for (rl = l->cname_lookups; rl; rl = rl->rlist_next) {
439         n = rl->data;
440
441         assert(n);
442
443         if ((n->interface == AVAHI_IF_UNSPEC || n->interface == interface) &&
444             (n->interface == AVAHI_PROTO_UNSPEC || n->protocol == protocol) &&
445             n->flags == flags &&
446             avahi_key_equal(n->key, k))
447             break;
448     }
449     
450     avahi_key_unref(k);
451
452     if (rl) {
453         l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, rl);
454         lookup_unref(n);
455     }
456 }
457
458 static void defer_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata) {
459     AvahiSRecordBrowser *b = userdata;
460     int n;
461
462     assert(b);
463     assert(!b->dead);
464
465     /* Remove the defer timeout */
466     if (b->defer_time_event) {
467         avahi_time_event_free(b->defer_time_event);
468         b->defer_time_event = NULL;
469     }
470
471     /* Create initial query */
472     assert(!b->root_lookup);
473     b->root_lookup = lookup_add(b, b->interface, b->protocol, b->flags, b->key);
474     assert(b->root_lookup);
475
476     n = lookup_go(b->root_lookup);
477
478     if (b->dead)
479         return;
480
481     if (n < 0) {
482         /* sending of the initial query failed */
483
484         avahi_server_set_errno(b->server, AVAHI_ERR_FAILURE);
485
486         b->callback(
487             b, b->interface, b->protocol, AVAHI_BROWSER_FAILURE, NULL,
488             b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST,
489             b->userdata);
490         
491         browser_cancel(b);
492         return;
493     }
494     
495     /* Tell the client that we're done with the cache */
496     b->callback(
497         b, b->interface, b->protocol, AVAHI_BROWSER_CACHE_EXHAUSTED, NULL,
498         b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST,
499         b->userdata);
500
501     if (!b->dead && b->root_lookup && b->root_lookup->flags & AVAHI_LOOKUP_USE_WIDE_AREA && n > 0) {
502
503         /* If we do wide area lookups and the the cache contained
504          * entries, we assume that it is complete, and tell the user
505          * so by firing ALL_FOR_NOW. */
506         
507         b->callback(b, b->interface, b->protocol, AVAHI_BROWSER_ALL_FOR_NOW, NULL, AVAHI_LOOKUP_RESULT_WIDE_AREA, b->userdata);
508     }
509 }
510
511 void avahi_s_record_browser_restart(AvahiSRecordBrowser *b) {
512     assert(b);
513     assert(!b->dead);
514
515     browser_cancel(b);
516
517     /* Request a new iteration of the cache scanning */
518     if (!b->defer_time_event) {
519         b->defer_time_event = avahi_time_event_new(b->server->time_event_queue, NULL, defer_callback, b);
520         assert(b->defer_time_event);
521     }
522 }
523
524 AvahiSRecordBrowser *avahi_s_record_browser_new(
525     AvahiServer *server,
526     AvahiIfIndex interface,
527     AvahiProtocol protocol,
528     AvahiKey *key,
529     AvahiLookupFlags flags,
530     AvahiSRecordBrowserCallback callback,
531     void* userdata) {
532     
533     AvahiSRecordBrowser *b;
534
535     assert(server);
536     assert(key);
537     assert(callback);
538
539     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE);
540     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL);
541     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !avahi_key_is_pattern(key), AVAHI_ERR_IS_PATTERN);
542     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_key_is_valid(key), AVAHI_ERR_INVALID_KEY);
543     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS);
544     AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !(flags & AVAHI_LOOKUP_USE_WIDE_AREA) || !(flags & AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); 
545     
546     if (!(b = avahi_new(AvahiSRecordBrowser, 1))) {
547         avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY);
548         return NULL;
549     }
550     
551     b->dead = 0;
552     b->server = server;
553     b->interface = interface;
554     b->protocol = protocol;
555     b->key = avahi_key_ref(key);
556     b->flags = flags;
557     b->callback = callback;
558     b->userdata = userdata;
559     b->n_lookups = 0;
560     AVAHI_LLIST_HEAD_INIT(AvahiSRBLookup, b->lookups);
561     b->root_lookup = NULL;
562     
563     AVAHI_LLIST_PREPEND(AvahiSRecordBrowser, browser, server->record_browsers, b);
564
565     /* The currently cached entries are scanned a bit later, and than we will start querying, too */
566     b->defer_time_event = avahi_time_event_new(server->time_event_queue, NULL, defer_callback, b);
567     assert(b->defer_time_event);
568     
569     return b;
570 }
571
572 void avahi_s_record_browser_free(AvahiSRecordBrowser *b) {
573     assert(b);
574     assert(!b->dead);
575
576     b->dead = 1;
577     b->server->need_browser_cleanup = 1;
578
579     browser_cancel(b);
580 }
581
582 void avahi_s_record_browser_destroy(AvahiSRecordBrowser *b) {
583     assert(b);
584
585     browser_cancel(b);
586     
587     AVAHI_LLIST_REMOVE(AvahiSRecordBrowser, browser, b->server->record_browsers, b);
588
589     avahi_key_unref(b->key);
590     
591     avahi_free(b);
592 }
593
594 void avahi_browser_cleanup(AvahiServer *server) {
595     AvahiSRecordBrowser *b;
596     AvahiSRecordBrowser *n;
597     
598     assert(server);
599
600     while (server->need_browser_cleanup) {
601         server->need_browser_cleanup = 0;
602         
603         for (b = server->record_browsers; b; b = n) {
604             n = b->browser_next;
605             
606             if (b->dead)
607                 avahi_s_record_browser_destroy(b);
608         }
609     } 
610
611     if (server->wide_area_lookup_engine)
612         avahi_wide_area_cleanup(server->wide_area_lookup_engine);
613     avahi_multicast_lookup_engine_cleanup(server->multicast_lookup_engine);
614 }
615