1/***
2  This file is part of avahi.
3
4  avahi is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Lesser General Public License as
6  published by the Free Software Foundation; either version 2.1 of the
7  License, or (at your option) any later version.
8
9  avahi is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12  Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public
15  License along with avahi; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17  USA.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <string.h>
25#include <stdio.h>
26#include <stdlib.h>
27
28#include <avahi-common/domain.h>
29#include <avahi-common/timeval.h>
30#include "avahi-common/avahi-malloc.h"
31#include <avahi-common/error.h>
32
33#include "browse.h"
34#include "log.h"
35
36#define TIMEOUT_MSEC 5000
37
38struct AvahiSServiceResolver {
39    AvahiServer *server;
40    char *service_name;
41    char *service_type;
42    char *domain_name;
43    AvahiProtocol address_protocol;
44
45    AvahiIfIndex interface;
46    AvahiProtocol protocol;
47
48    AvahiSRecordBrowser *record_browser_srv;
49    AvahiSRecordBrowser *record_browser_txt;
50    AvahiSRecordBrowser *record_browser_a;
51    AvahiSRecordBrowser *record_browser_aaaa;
52
53    AvahiRecord *srv_record, *txt_record, *address_record;
54    AvahiLookupResultFlags srv_flags, txt_flags, address_flags;
55
56    AvahiSServiceResolverCallback callback;
57    void* userdata;
58    AvahiLookupFlags user_flags;
59
60    AvahiTimeEvent *time_event;
61
62    AVAHI_LLIST_FIELDS(AvahiSServiceResolver, resolver);
63};
64
65static void finish(AvahiSServiceResolver *r, AvahiResolverEvent event) {
66    AvahiLookupResultFlags flags;
67
68    assert(r);
69
70    if (r->time_event) {
71        avahi_time_event_free(r->time_event);
72        r->time_event = NULL;
73    }
74
75    flags =
76        r->txt_flags |
77        r->srv_flags |
78        r->address_flags;
79
80    switch (event) {
81        case AVAHI_RESOLVER_FAILURE:
82
83            r->callback(
84                r,
85                r->interface,
86                r->protocol,
87                event,
88                r->service_name,
89                r->service_type,
90                r->domain_name,
91                NULL,
92                NULL,
93                0,
94                NULL,
95                flags,
96                r->userdata);
97
98            break;
99
100        case AVAHI_RESOLVER_FOUND: {
101            AvahiAddress a;
102
103            assert(event == AVAHI_RESOLVER_FOUND);
104
105            assert(r->srv_record);
106
107            if (r->address_record) {
108                switch (r->address_record->key->type) {
109                    case AVAHI_DNS_TYPE_A:
110                        a.proto = AVAHI_PROTO_INET;
111                        a.data.ipv4 = r->address_record->data.a.address;
112                        break;
113
114                    case AVAHI_DNS_TYPE_AAAA:
115                        a.proto = AVAHI_PROTO_INET6;
116                        a.data.ipv6 = r->address_record->data.aaaa.address;
117                        break;
118
119                    default:
120                        assert(0);
121                }
122            }
123
124            r->callback(
125                r,
126                r->interface,
127                r->protocol,
128                event,
129                r->service_name,
130                r->service_type,
131                r->domain_name,
132                r->srv_record->data.srv.name,
133                r->address_record ? &a : NULL,
134                r->srv_record->data.srv.port,
135                r->txt_record ? r->txt_record->data.txt.string_list : NULL,
136                flags,
137                r->userdata);
138
139            break;
140        }
141    }
142}
143
144static void time_event_callback(AvahiTimeEvent *e, void *userdata) {
145    AvahiSServiceResolver *r = userdata;
146
147    assert(e);
148    assert(r);
149
150    avahi_server_set_errno(r->server, AVAHI_ERR_TIMEOUT);
151    finish(r, AVAHI_RESOLVER_FAILURE);
152}
153
154static void start_timeout(AvahiSServiceResolver *r) {
155    struct timeval tv;
156    assert(r);
157
158    if (r->time_event)
159        return;
160
161    avahi_elapse_time(&tv, TIMEOUT_MSEC, 0);
162
163    r->time_event = avahi_time_event_new(r->server->time_event_queue, &tv, time_event_callback, r);
164}
165
166static void record_browser_callback(
167    AvahiSRecordBrowser*rr,
168    AvahiIfIndex interface,
169    AvahiProtocol protocol,
170    AvahiBrowserEvent event,
171    AvahiRecord *record,
172    AvahiLookupResultFlags flags,
173    void* userdata) {
174
175    AvahiSServiceResolver *r = userdata;
176
177    assert(rr);
178    assert(r);
179
180    if (rr == r->record_browser_aaaa || rr == r->record_browser_a)
181        r->address_flags = flags;
182    else if (rr == r->record_browser_srv)
183        r->srv_flags = flags;
184    else if (rr == r->record_browser_txt)
185        r->txt_flags = flags;
186
187    switch (event) {
188
189        case AVAHI_BROWSER_NEW: {
190            int changed = 0;
191            assert(record);
192
193            if (r->interface > 0 && interface > 0 &&  interface != r->interface)
194                return;
195
196            if (r->protocol != AVAHI_PROTO_UNSPEC && protocol != AVAHI_PROTO_UNSPEC && protocol != r->protocol)
197                return;
198
199            if (r->interface <= 0)
200                r->interface = interface;
201
202            if (r->protocol == AVAHI_PROTO_UNSPEC)
203                r->protocol = protocol;
204
205            switch (record->key->type) {
206                case AVAHI_DNS_TYPE_SRV:
207                    if (!r->srv_record) {
208                        r->srv_record = avahi_record_ref(record);
209                        changed = 1;
210
211                        if (r->record_browser_a) {
212                            avahi_s_record_browser_free(r->record_browser_a);
213                            r->record_browser_a = NULL;
214                        }
215
216                        if (r->record_browser_aaaa) {
217                            avahi_s_record_browser_free(r->record_browser_aaaa);
218                            r->record_browser_aaaa = NULL;
219                        }
220
221                        if (!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)) {
222
223                            if (r->address_protocol == AVAHI_PROTO_INET || r->address_protocol == AVAHI_PROTO_UNSPEC) {
224                                AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A);
225                                r->record_browser_a = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r);
226                                avahi_key_unref(k);
227                            }
228
229                            if (r->address_protocol == AVAHI_PROTO_INET6 || r->address_protocol == AVAHI_PROTO_UNSPEC) {
230                                AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA);
231                                r->record_browser_aaaa = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r);
232                                avahi_key_unref(k);
233                            }
234                        }
235                    }
236                    break;
237
238                case AVAHI_DNS_TYPE_TXT:
239
240                    assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT));
241
242                    if (!r->txt_record) {
243                        r->txt_record = avahi_record_ref(record);
244                        changed = 1;
245                    }
246                    break;
247
248                case AVAHI_DNS_TYPE_A:
249                case AVAHI_DNS_TYPE_AAAA:
250
251                    assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS));
252
253                    if (!r->address_record) {
254                        r->address_record = avahi_record_ref(record);
255                        changed = 1;
256                    }
257                    break;
258
259                default:
260                    abort();
261            }
262
263
264            if (changed &&
265                r->srv_record &&
266                (r->txt_record || (r->user_flags & AVAHI_LOOKUP_NO_TXT)) &&
267                (r->address_record || (r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)))
268                finish(r, AVAHI_RESOLVER_FOUND);
269
270            break;
271
272        }
273
274        case AVAHI_BROWSER_REMOVE:
275
276            assert(record);
277
278            switch (record->key->type) {
279                case AVAHI_DNS_TYPE_SRV:
280
281                    if (r->srv_record && avahi_record_equal_no_ttl(record, r->srv_record)) {
282                        avahi_record_unref(r->srv_record);
283                        r->srv_record = NULL;
284
285                        if (r->record_browser_a) {
286                            avahi_s_record_browser_free(r->record_browser_a);
287                            r->record_browser_a = NULL;
288                        }
289
290                        if (r->record_browser_aaaa) {
291                            avahi_s_record_browser_free(r->record_browser_aaaa);
292                            r->record_browser_aaaa = NULL;
293                        }
294
295                        /** Look for a replacement */
296                        avahi_s_record_browser_restart(r->record_browser_srv);
297                        start_timeout(r);
298                    }
299
300                    break;
301
302                case AVAHI_DNS_TYPE_TXT:
303
304                    assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT));
305
306                    if (r->txt_record && avahi_record_equal_no_ttl(record, r->txt_record)) {
307                        avahi_record_unref(r->txt_record);
308                        r->txt_record = NULL;
309
310                        /** Look for a replacement */
311                        avahi_s_record_browser_restart(r->record_browser_txt);
312                        start_timeout(r);
313                    }
314                    break;
315
316                case AVAHI_DNS_TYPE_A:
317                case AVAHI_DNS_TYPE_AAAA:
318
319                    assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS));
320
321                    if (r->address_record && avahi_record_equal_no_ttl(record, r->address_record)) {
322                        avahi_record_unref(r->address_record);
323                        r->address_record = NULL;
324
325                        /** Look for a replacement */
326                        if (r->record_browser_aaaa)
327                            avahi_s_record_browser_restart(r->record_browser_aaaa);
328                        if (r->record_browser_a)
329                            avahi_s_record_browser_restart(r->record_browser_a);
330                        start_timeout(r);
331                    }
332                    break;
333
334                default:
335                    abort();
336            }
337
338            break;
339
340        case AVAHI_BROWSER_CACHE_EXHAUSTED:
341        case AVAHI_BROWSER_ALL_FOR_NOW:
342            break;
343
344        case AVAHI_BROWSER_FAILURE:
345
346            if (rr == r->record_browser_a && r->record_browser_aaaa) {
347                /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */
348                avahi_s_record_browser_free(r->record_browser_a);
349                r->record_browser_a = NULL;
350                break;
351            }
352
353            if (rr == r->record_browser_aaaa && r->record_browser_a) {
354                /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */
355                avahi_s_record_browser_free(r->record_browser_aaaa);
356                r->record_browser_aaaa = NULL;
357                break;
358            }
359
360            /* Hmm, everything's lost, tell the user */
361
362            if (r->record_browser_srv)
363                avahi_s_record_browser_free(r->record_browser_srv);
364            if (r->record_browser_txt)
365                avahi_s_record_browser_free(r->record_browser_txt);
366            if (r->record_browser_a)
367                avahi_s_record_browser_free(r->record_browser_a);
368            if (r->record_browser_aaaa)
369                avahi_s_record_browser_free(r->record_browser_aaaa);
370
371            r->record_browser_srv = r->record_browser_txt = r->record_browser_a = r->record_browser_aaaa = NULL;
372
373            finish(r, AVAHI_RESOLVER_FAILURE);
374            break;
375    }
376}
377
378AvahiSServiceResolver *avahi_s_service_resolver_new(
379    AvahiServer *server,
380    AvahiIfIndex interface,
381    AvahiProtocol protocol,
382    const char *name,
383    const char *type,
384    const char *domain,
385    AvahiProtocol aprotocol,
386    AvahiLookupFlags flags,
387    AvahiSServiceResolverCallback callback,
388    void* userdata) {
389
390    AvahiSServiceResolver *r;
391    AvahiKey *k;
392    char n[AVAHI_DOMAIN_NAME_MAX];
393    int ret;
394
395    assert(server);
396    assert(type);
397    assert(callback);
398
399    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE);
400    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL);
401    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL);
402    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME);
403    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !name || avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME);
404    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE);
405    AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), AVAHI_ERR_INVALID_FLAGS);
406
407    if (!domain)
408        domain = server->domain_name;
409
410    if ((ret = avahi_service_name_join(n, sizeof(n), name, type, domain)) < 0) {
411        avahi_server_set_errno(server, ret);
412        return NULL;
413    }
414
415    if (!(r = avahi_new(AvahiSServiceResolver, 1))) {
416        avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY);
417        return NULL;
418    }
419
420    r->server = server;
421    r->service_name = avahi_strdup(name);
422    r->service_type = avahi_normalize_name_strdup(type);
423    r->domain_name = avahi_normalize_name_strdup(domain);
424    r->callback = callback;
425    r->userdata = userdata;
426    r->address_protocol = aprotocol;
427    r->srv_record = r->txt_record = r->address_record = NULL;
428    r->srv_flags = r->txt_flags = r->address_flags = 0;
429    r->interface = interface;
430    r->protocol = protocol;
431    r->user_flags = flags;
432    r->record_browser_a = r->record_browser_aaaa = r->record_browser_srv = r->record_browser_txt = NULL;
433    r->time_event = NULL;
434    AVAHI_LLIST_PREPEND(AvahiSServiceResolver, resolver, server->service_resolvers, r);
435
436    k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV);
437    r->record_browser_srv = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r);
438    avahi_key_unref(k);
439
440    if (!r->record_browser_srv) {
441        avahi_s_service_resolver_free(r);
442        return NULL;
443    }
444
445    if (!(flags & AVAHI_LOOKUP_NO_TXT)) {
446        k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT);
447        r->record_browser_txt = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS),  record_browser_callback, r);
448        avahi_key_unref(k);
449
450        if (!r->record_browser_txt) {
451            avahi_s_service_resolver_free(r);
452            return NULL;
453        }
454    }
455
456    start_timeout(r);
457
458    return r;
459}
460
461void avahi_s_service_resolver_free(AvahiSServiceResolver *r) {
462    assert(r);
463
464    AVAHI_LLIST_REMOVE(AvahiSServiceResolver, resolver, r->server->service_resolvers, r);
465
466    if (r->time_event)
467        avahi_time_event_free(r->time_event);
468
469    if (r->record_browser_srv)
470        avahi_s_record_browser_free(r->record_browser_srv);
471    if (r->record_browser_txt)
472        avahi_s_record_browser_free(r->record_browser_txt);
473    if (r->record_browser_a)
474        avahi_s_record_browser_free(r->record_browser_a);
475    if (r->record_browser_aaaa)
476        avahi_s_record_browser_free(r->record_browser_aaaa);
477
478    if (r->srv_record)
479        avahi_record_unref(r->srv_record);
480    if (r->txt_record)
481        avahi_record_unref(r->txt_record);
482    if (r->address_record)
483        avahi_record_unref(r->address_record);
484
485    avahi_free(r->service_name);
486    avahi_free(r->service_type);
487    avahi_free(r->domain_name);
488    avahi_free(r);
489}
490