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 <string.h>
27
28#include <avahi-common/domain.h>
29#include <avahi-common/malloc.h>
30#include <avahi-common/error.h>
31
32#include "browse.h"
33#include "log.h"
34#include "rr.h"
35
36typedef struct AvahiDNSServerInfo AvahiDNSServerInfo;
37
38struct AvahiDNSServerInfo {
39    AvahiSDNSServerBrowser *browser;
40
41    AvahiIfIndex interface;
42    AvahiProtocol protocol;
43    AvahiRecord *srv_record;
44    AvahiSHostNameResolver *host_name_resolver;
45    AvahiAddress address;
46    AvahiLookupResultFlags flags;
47
48    AVAHI_LLIST_FIELDS(AvahiDNSServerInfo, info);
49};
50
51struct AvahiSDNSServerBrowser {
52    AvahiServer *server;
53
54    AvahiSRecordBrowser *record_browser;
55    AvahiSDNSServerBrowserCallback callback;
56    void* userdata;
57    AvahiProtocol aprotocol;
58    AvahiLookupFlags user_flags;
59
60    unsigned n_info;
61
62    AVAHI_LLIST_FIELDS(AvahiSDNSServerBrowser, browser);
63    AVAHI_LLIST_HEAD(AvahiDNSServerInfo, info);
64};
65
66static AvahiDNSServerInfo* get_server_info(AvahiSDNSServerBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiRecord *r) {
67    AvahiDNSServerInfo *i;
68
69    assert(b);
70    assert(r);
71
72    for (i = b->info; i; i = i->info_next)
73        if (i->interface == interface &&
74            i->protocol == protocol &&
75            avahi_record_equal_no_ttl(r, i->srv_record))
76            return i;
77
78    return NULL;
79}
80
81static void server_info_free(AvahiSDNSServerBrowser *b, AvahiDNSServerInfo *i) {
82    assert(b);
83    assert(i);
84
85    avahi_record_unref(i->srv_record);
86    if (i->host_name_resolver)
87        avahi_s_host_name_resolver_free(i->host_name_resolver);
88
89    AVAHI_LLIST_REMOVE(AvahiDNSServerInfo, info, b->info, i);
90
91    assert(b->n_info >= 1);
92    b->n_info--;
93
94    avahi_free(i);
95}
96
97static void host_name_resolver_callback(
98    AvahiSHostNameResolver *r,
99    AVAHI_GCC_UNUSED AvahiIfIndex interface,
100    AVAHI_GCC_UNUSED AvahiProtocol protocol,
101    AvahiResolverEvent event,
102    const char *host_name,
103    const AvahiAddress *a,
104    AvahiLookupResultFlags flags,
105    void* userdata) {
106
107    AvahiDNSServerInfo *i = userdata;
108
109    assert(r);
110    assert(host_name);
111    assert(i);
112
113    switch (event) {
114        case AVAHI_RESOLVER_FOUND: {
115            i->address = *a;
116
117            i->browser->callback(
118                i->browser,
119                i->interface,
120                i->protocol,
121                AVAHI_BROWSER_NEW,
122                i->srv_record->data.srv.name,
123                &i->address,
124                i->srv_record->data.srv.port,
125                i->flags | flags,
126                i->browser->userdata);
127
128            break;
129        }
130
131        case AVAHI_RESOLVER_FAILURE:
132            /* Ignore */
133            break;
134    }
135
136    avahi_s_host_name_resolver_free(i->host_name_resolver);
137    i->host_name_resolver = NULL;
138}
139
140static void record_browser_callback(
141    AvahiSRecordBrowser*rr,
142    AvahiIfIndex interface,
143    AvahiProtocol protocol,
144    AvahiBrowserEvent event,
145    AvahiRecord *record,
146    AvahiLookupResultFlags flags,
147    void* userdata) {
148
149    AvahiSDNSServerBrowser *b = userdata;
150
151    assert(rr);
152    assert(b);
153
154    /* Filter flags */
155    flags &= AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST | AVAHI_LOOKUP_RESULT_WIDE_AREA;
156
157    switch (event) {
158        case AVAHI_BROWSER_NEW: {
159            AvahiDNSServerInfo *i;
160
161            assert(record);
162            assert(record->key->type == AVAHI_DNS_TYPE_SRV);
163
164            if (get_server_info(b, interface, protocol, record))
165                return;
166
167            if (b->n_info >= 10)
168                return;
169
170            if (!(i = avahi_new(AvahiDNSServerInfo, 1)))
171                return; /* OOM */
172
173            i->browser = b;
174            i->interface = interface;
175            i->protocol = protocol;
176            i->srv_record = avahi_record_ref(record);
177            i->host_name_resolver = avahi_s_host_name_resolver_new(
178                b->server,
179                interface, protocol,
180                record->data.srv.name,
181                b->aprotocol,
182                b->user_flags,
183                host_name_resolver_callback, i);
184            i->flags = flags;
185
186            AVAHI_LLIST_PREPEND(AvahiDNSServerInfo, info, b->info, i);
187
188            b->n_info++;
189            break;
190        }
191
192        case AVAHI_BROWSER_REMOVE: {
193            AvahiDNSServerInfo *i;
194
195            assert(record);
196            assert(record->key->type == AVAHI_DNS_TYPE_SRV);
197
198            if (!(i = get_server_info(b, interface, protocol, record)))
199                return;
200
201            if (!i->host_name_resolver)
202                b->callback(
203                    b,
204                    interface,
205                    protocol,
206                    event,
207                    i->srv_record->data.srv.name,
208                    &i->address,
209                    i->srv_record->data.srv.port,
210                    i->flags | flags,
211                    b->userdata);
212
213            server_info_free(b, i);
214            break;
215        }
216
217        case AVAHI_BROWSER_FAILURE:
218        case AVAHI_BROWSER_ALL_FOR_NOW:
219        case AVAHI_BROWSER_CACHE_EXHAUSTED:
220
221            b->callback(
222                b,
223                interface,
224                protocol,
225                event,
226                NULL,
227                NULL,
228                0,
229                flags,
230                b->userdata);
231
232            break;
233    }
234}
235
236AvahiSDNSServerBrowser *avahi_s_dns_server_browser_new(
237    AvahiServer *server,
238    AvahiIfIndex interface,
239    AvahiProtocol protocol,
240    const char *domain,
241    AvahiDNSServerType type,
242    AvahiProtocol aprotocol,
243    AvahiLookupFlags flags,
244    AvahiSDNSServerBrowserCallback callback,
245    void* userdata) {
246
247    static const char * const type_table[AVAHI_DNS_SERVER_MAX] = {
248        "_domain._udp",
249        "_dns-update._udp"
250    };
251
252    AvahiSDNSServerBrowser *b;
253    AvahiKey *k = NULL;
254    char n[AVAHI_DOMAIN_NAME_MAX];
255    int r;
256
257    assert(server);
258    assert(callback);
259
260    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE);
261    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL);
262    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL);
263    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME);
264    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS);
265    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, type < AVAHI_DNS_SERVER_MAX, AVAHI_ERR_INVALID_FLAGS);
266
267    if (!domain)
268        domain = server->domain_name;
269
270    if ((r = avahi_service_name_join(n, sizeof(n), NULL, type_table[type], domain)) < 0) {
271        avahi_server_set_errno(server, r);
272        return NULL;
273    }
274
275    if (!(b = avahi_new(AvahiSDNSServerBrowser, 1))) {
276        avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY);
277        return NULL;
278    }
279
280    b->server = server;
281    b->callback = callback;
282    b->userdata = userdata;
283    b->aprotocol = aprotocol;
284    b->n_info = 0;
285    b->user_flags = flags;
286
287    AVAHI_LLIST_HEAD_INIT(AvahiDNSServerInfo, b->info);
288    AVAHI_LLIST_PREPEND(AvahiSDNSServerBrowser, browser, server->dns_server_browsers, b);
289
290    if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV))) {
291        avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY);
292        goto fail;
293    }
294
295    if (!(b->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, b)))
296        goto fail;
297
298    avahi_key_unref(k);
299
300    return b;
301
302fail:
303
304    if (k)
305        avahi_key_unref(k);
306
307    avahi_s_dns_server_browser_free(b);
308    return NULL;
309}
310
311void avahi_s_dns_server_browser_free(AvahiSDNSServerBrowser *b) {
312    assert(b);
313
314    while (b->info)
315        server_info_free(b, b->info);
316
317    AVAHI_LLIST_REMOVE(AvahiSDNSServerBrowser, browser, b->server->dns_server_browsers, b);
318
319    if (b->record_browser)
320        avahi_s_record_browser_free(b->record_browser);
321
322    avahi_free(b);
323}
324
325