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 <pthread.h>
25#include <assert.h>
26#include <unistd.h>
27#include <stdio.h>
28#include <errno.h>
29#include <string.h>
30#include <signal.h>
31#include <netinet/in.h>
32#include <fcntl.h>
33
34#include <sys/types.h>
35#include <sys/socket.h>
36
37#include <avahi-common/simple-watch.h>
38#include "avahi-common/avahi-malloc.h"
39#include <avahi-common/error.h>
40#include <avahi-common/domain.h>
41#include <avahi-common/alternative.h>
42
43#include <avahi-client/client.h>
44#include <avahi-client/publish.h>
45#include <avahi-client/lookup.h>
46
47#include "warn.h"
48#include "dns_sd.h"
49
50enum {
51    COMMAND_POLL = 'p',
52    COMMAND_QUIT = 'q',
53    COMMAND_POLL_DONE = 'P',
54    COMMAND_POLL_FAILED = 'F'
55};
56
57struct type_info {
58    char *type;
59    AvahiStringList *subtypes;
60    int n_subtypes;
61};
62
63struct _DNSServiceRef_t {
64    int n_ref;
65
66    AvahiSimplePoll *simple_poll;
67
68    int thread_fd, main_fd;
69
70    pthread_t thread;
71    int thread_running;
72
73    pthread_mutex_t mutex;
74
75    void *context;
76    DNSServiceBrowseReply service_browser_callback;
77    DNSServiceResolveReply service_resolver_callback;
78    DNSServiceDomainEnumReply domain_browser_callback;
79    DNSServiceRegisterReply service_register_callback;
80    DNSServiceQueryRecordReply query_resolver_callback;
81
82    AvahiClient *client;
83    AvahiServiceBrowser *service_browser;
84    AvahiServiceResolver *service_resolver;
85    AvahiDomainBrowser *domain_browser;
86    AvahiRecordBrowser *record_browser;
87
88    struct type_info type_info;
89    char *service_name, *service_name_chosen, *service_domain, *service_host;
90    uint16_t service_port;
91    AvahiIfIndex service_interface;
92    AvahiStringList *service_txt;
93
94    AvahiEntryGroup *entry_group;
95};
96
97#define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); }
98
99static DNSServiceErrorType map_error(int error) {
100    switch (error) {
101        case AVAHI_OK :
102            return kDNSServiceErr_NoError;
103
104        case AVAHI_ERR_BAD_STATE :
105            return kDNSServiceErr_BadState;
106
107        case AVAHI_ERR_INVALID_HOST_NAME:
108        case AVAHI_ERR_INVALID_DOMAIN_NAME:
109        case AVAHI_ERR_INVALID_TTL:
110        case AVAHI_ERR_IS_PATTERN:
111        case AVAHI_ERR_INVALID_RECORD:
112        case AVAHI_ERR_INVALID_SERVICE_NAME:
113        case AVAHI_ERR_INVALID_SERVICE_TYPE:
114        case AVAHI_ERR_INVALID_PORT:
115        case AVAHI_ERR_INVALID_KEY:
116        case AVAHI_ERR_INVALID_ADDRESS:
117        case AVAHI_ERR_INVALID_SERVICE_SUBTYPE:
118            return kDNSServiceErr_BadParam;
119
120
121        case AVAHI_ERR_COLLISION:
122            return kDNSServiceErr_NameConflict;
123
124        case AVAHI_ERR_TOO_MANY_CLIENTS:
125        case AVAHI_ERR_TOO_MANY_OBJECTS:
126        case AVAHI_ERR_TOO_MANY_ENTRIES:
127        case AVAHI_ERR_ACCESS_DENIED:
128            return kDNSServiceErr_Refused;
129
130        case AVAHI_ERR_INVALID_OPERATION:
131        case AVAHI_ERR_INVALID_OBJECT:
132            return kDNSServiceErr_Invalid;
133
134        case AVAHI_ERR_NO_MEMORY:
135            return kDNSServiceErr_NoMemory;
136
137        case AVAHI_ERR_INVALID_INTERFACE:
138        case AVAHI_ERR_INVALID_PROTOCOL:
139            return kDNSServiceErr_BadInterfaceIndex;
140
141        case AVAHI_ERR_INVALID_FLAGS:
142            return kDNSServiceErr_BadFlags;
143
144        case AVAHI_ERR_NOT_FOUND:
145            return kDNSServiceErr_NoSuchName;
146
147        case AVAHI_ERR_VERSION_MISMATCH:
148            return kDNSServiceErr_Incompatible;
149
150        case AVAHI_ERR_NO_NETWORK:
151        case AVAHI_ERR_OS:
152        case AVAHI_ERR_INVALID_CONFIG:
153        case AVAHI_ERR_TIMEOUT:
154        case AVAHI_ERR_DBUS_ERROR:
155        case AVAHI_ERR_DISCONNECTED:
156        case AVAHI_ERR_NO_DAEMON:
157            break;
158
159    }
160
161    return kDNSServiceErr_Unknown;
162}
163
164static void type_info_init(struct type_info *i) {
165    assert(i);
166    i->type = NULL;
167    i->subtypes = NULL;
168    i->n_subtypes = 0;
169}
170
171static void type_info_free(struct type_info *i) {
172    assert(i);
173
174    avahi_free(i->type);
175    avahi_string_list_free(i->subtypes);
176
177    type_info_init(i);
178}
179
180static int type_info_parse(struct type_info *i, const char *t) {
181    char *token = NULL;
182
183    assert(i);
184    assert(t);
185
186    type_info_init(i);
187
188    for (;;) {
189        size_t l;
190
191        if (*t == 0)
192            break;
193
194        l = strcspn(t, ",");
195
196        if (l <= 0)
197            goto fail;
198
199        token = avahi_strndup(t, l);
200
201        if (!token)
202            goto fail;
203
204        if (!i->type) {
205            /* This is the first token, hence the main type */
206
207            if (!avahi_is_valid_service_type_strict(token))
208                goto fail;
209
210            i->type = token;
211            token = NULL;
212        } else {
213            char *fst;
214
215            /* This is not the first token, hence a subtype */
216
217            if (!(fst = avahi_strdup_printf("%s._sub.%s", token, i->type)))
218                goto fail;
219
220            if (!avahi_is_valid_service_subtype(fst)) {
221                avahi_free(fst);
222                goto fail;
223            }
224
225            i->subtypes = avahi_string_list_add(i->subtypes, fst);
226            avahi_free(fst);
227
228            avahi_free(token);
229            token = NULL;
230
231            i->n_subtypes++;
232        }
233
234        t += l;
235
236        if (*t == ',')
237            t++;
238    }
239
240    if (i->type)
241        return 0;
242
243fail:
244    type_info_free(i);
245    avahi_free(token);
246    return -1;
247}
248
249static const char *add_trailing_dot(const char *s, char *buf, size_t buf_len) {
250    if (!s)
251        return NULL;
252
253    if (*s == 0)
254        return s;
255
256    if (s[strlen(s)-1] == '.')
257        return s;
258
259    snprintf(buf, buf_len, "%s.", s);
260    return buf;
261}
262
263static int read_command(int fd) {
264    ssize_t r;
265    char command;
266
267    assert(fd >= 0);
268
269    if ((r = read(fd, &command, 1)) != 1) {
270        fprintf(stderr, __FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
271        return -1;
272    }
273
274    return command;
275}
276
277static int write_command(int fd, char reply) {
278    assert(fd >= 0);
279
280    if (write(fd, &reply, 1) != 1) {
281        fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
282        return -1;
283    }
284
285    return 0;
286}
287
288static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) {
289    DNSServiceRef sdref = userdata;
290    int ret;
291
292    assert(sdref);
293
294    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
295
296/*     fprintf(stderr, "pre-syscall\n"); */
297    ret = poll(ufds, nfds, timeout);
298/*     fprintf(stderr, "post-syscall\n"); */
299
300    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
301
302    return ret;
303}
304
305static void * thread_func(void *data) {
306    DNSServiceRef sdref = data;
307    sigset_t mask;
308
309    sigfillset(&mask);
310    pthread_sigmask(SIG_BLOCK, &mask, NULL);
311
312    sdref->thread = pthread_self();
313    sdref->thread_running = 1;
314
315    for (;;) {
316        char command;
317
318        if ((command = read_command(sdref->thread_fd)) < 0)
319            break;
320
321/*         fprintf(stderr, "Command: %c\n", command); */
322
323        switch (command) {
324
325            case COMMAND_POLL: {
326                int ret;
327
328                ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
329
330                for (;;) {
331                    errno = 0;
332
333                    if ((ret = avahi_simple_poll_run(sdref->simple_poll)) < 0) {
334
335                        if (errno == EINTR)
336                            continue;
337
338                        fprintf(stderr, __FILE__": avahi_simple_poll_run() failed: %s\n", strerror(errno));
339                    }
340
341                    break;
342                }
343
344                ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
345
346                if (write_command(sdref->thread_fd, ret < 0 ? COMMAND_POLL_FAILED : COMMAND_POLL_DONE) < 0)
347                    break;
348
349                break;
350            }
351
352            case COMMAND_QUIT:
353                return NULL;
354        }
355
356    }
357
358    return NULL;
359}
360
361static DNSServiceRef sdref_new(void) {
362    int fd[2] = { -1, -1 };
363    DNSServiceRef sdref = NULL;
364    pthread_mutexattr_t mutex_attr;
365
366    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
367        goto fail;
368
369    if (!(sdref = avahi_new(struct _DNSServiceRef_t, 1)))
370        goto fail;
371
372    sdref->n_ref = 1;
373    sdref->thread_fd = fd[0];
374    sdref->main_fd = fd[1];
375
376    sdref->client = NULL;
377    sdref->service_browser = NULL;
378    sdref->service_resolver = NULL;
379    sdref->domain_browser = NULL;
380    sdref->entry_group = NULL;
381
382    sdref->service_name = sdref->service_name_chosen = sdref->service_domain = sdref->service_host = NULL;
383    sdref->service_txt = NULL;
384
385    type_info_init(&sdref->type_info);
386
387    ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr));
388    pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
389    ASSERT_SUCCESS(pthread_mutex_init(&sdref->mutex, &mutex_attr));
390
391    sdref->thread_running = 0;
392
393    if (!(sdref->simple_poll = avahi_simple_poll_new()))
394        goto fail;
395
396    avahi_simple_poll_set_func(sdref->simple_poll, poll_func, sdref);
397
398    /* Start simple poll */
399    if (avahi_simple_poll_prepare(sdref->simple_poll, -1) < 0)
400        goto fail;
401
402    /* Queue an initial POLL command for the thread */
403    if (write_command(sdref->main_fd, COMMAND_POLL) < 0)
404        goto fail;
405
406    if (pthread_create(&sdref->thread, NULL, thread_func, sdref) != 0)
407        goto fail;
408
409    sdref->thread_running = 1;
410
411    return sdref;
412
413fail:
414
415    if (sdref)
416        DNSServiceRefDeallocate(sdref);
417
418    return NULL;
419}
420
421static void sdref_free(DNSServiceRef sdref) {
422    assert(sdref);
423
424    if (sdref->thread_running) {
425        ASSERT_SUCCESS(write_command(sdref->main_fd, COMMAND_QUIT));
426        avahi_simple_poll_wakeup(sdref->simple_poll);
427        ASSERT_SUCCESS(pthread_join(sdref->thread, NULL));
428    }
429
430    if (sdref->client)
431        avahi_client_free(sdref->client);
432
433    if (sdref->simple_poll)
434        avahi_simple_poll_free(sdref->simple_poll);
435
436    if (sdref->thread_fd >= 0)
437        close(sdref->thread_fd);
438
439    if (sdref->main_fd >= 0)
440        close(sdref->main_fd);
441
442    ASSERT_SUCCESS(pthread_mutex_destroy(&sdref->mutex));
443
444    avahi_free(sdref->service_name);
445    avahi_free(sdref->service_name_chosen);
446    avahi_free(sdref->service_domain);
447    avahi_free(sdref->service_host);
448
449    type_info_free(&sdref->type_info);
450
451    avahi_string_list_free(sdref->service_txt);
452
453    avahi_free(sdref);
454}
455
456static void sdref_ref(DNSServiceRef sdref) {
457    assert(sdref);
458    assert(sdref->n_ref >= 1);
459
460    sdref->n_ref++;
461}
462
463static void sdref_unref(DNSServiceRef sdref) {
464    assert(sdref);
465    assert(sdref->n_ref >= 1);
466
467    if (--(sdref->n_ref) <= 0)
468        sdref_free(sdref);
469}
470
471int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdref) {
472
473    AVAHI_WARN_LINKAGE;
474
475    if (!sdref || sdref->n_ref <= 0)
476        return -1;
477
478    return sdref->main_fd;
479}
480
481DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdref) {
482    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
483
484    AVAHI_WARN_LINKAGE;
485
486    if (!sdref || sdref->n_ref <= 0)
487        return kDNSServiceErr_BadParam;
488
489    sdref_ref(sdref);
490
491    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
492
493    /* Cleanup notification socket */
494    if (read_command(sdref->main_fd) != COMMAND_POLL_DONE)
495        goto finish;
496
497    if (avahi_simple_poll_dispatch(sdref->simple_poll) < 0)
498        goto finish;
499
500    if (sdref->n_ref > 1) /* Perhaps we should die */
501
502        /* Dispatch events */
503        if (avahi_simple_poll_prepare(sdref->simple_poll, -1) < 0)
504            goto finish;
505
506    if (sdref->n_ref > 1)
507
508        /* Request the poll */
509        if (write_command(sdref->main_fd, COMMAND_POLL) < 0)
510            goto finish;
511
512    ret = kDNSServiceErr_NoError;
513
514finish:
515
516    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
517
518    sdref_unref(sdref);
519
520    return ret;
521}
522
523void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdref) {
524    AVAHI_WARN_LINKAGE;
525
526    if (sdref)
527        sdref_unref(sdref);
528}
529
530static void service_browser_callback(
531    AvahiServiceBrowser *b,
532    AvahiIfIndex interface,
533    AVAHI_GCC_UNUSED AvahiProtocol protocol,
534    AvahiBrowserEvent event,
535    const char *name,
536    const char *type,
537    const char *domain,
538    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
539    void *userdata) {
540
541    DNSServiceRef sdref = userdata;
542    char type_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX];
543    assert(b);
544    assert(sdref);
545    assert(sdref->n_ref >= 1);
546
547    type = add_trailing_dot(type, type_fixed, sizeof(type_fixed));
548    domain  = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
549
550    switch (event) {
551        case AVAHI_BROWSER_NEW:
552            sdref->service_browser_callback(sdref, kDNSServiceFlagsAdd, interface, kDNSServiceErr_NoError, name, type, domain, sdref->context);
553            break;
554
555        case AVAHI_BROWSER_REMOVE:
556            sdref->service_browser_callback(sdref, 0, interface, kDNSServiceErr_NoError, name, type, domain, sdref->context);
557            break;
558
559        case AVAHI_BROWSER_FAILURE:
560            sdref->service_browser_callback(sdref, 0, interface, map_error(avahi_client_errno(sdref->client)), NULL, NULL, NULL, sdref->context);
561            break;
562
563        case AVAHI_BROWSER_CACHE_EXHAUSTED:
564        case AVAHI_BROWSER_ALL_FOR_NOW:
565            break;
566    }
567}
568
569static void generic_client_callback(AvahiClient *s, AvahiClientState state, void* userdata) {
570    DNSServiceRef sdref = userdata;
571    int error = kDNSServiceErr_Unknown;
572
573    assert(s);
574    assert(sdref);
575    assert(sdref->n_ref >= 1);
576
577    switch (state) {
578
579        case AVAHI_CLIENT_FAILURE:
580
581            if (sdref->service_browser_callback)
582                sdref->service_browser_callback(sdref, 0, 0, error, NULL, NULL, NULL, sdref->context);
583            else if (sdref->service_resolver_callback)
584                sdref->service_resolver_callback(sdref, 0, 0, error, NULL, NULL, 0, 0, NULL, sdref->context);
585            else if (sdref->domain_browser_callback)
586                sdref->domain_browser_callback(sdref, 0, 0, error, NULL, sdref->context);
587            else if (sdref->query_resolver_callback)
588                sdref->query_resolver_callback(sdref, 0, 0, error, NULL, 0, 0, 0, NULL, 0, sdref->context);
589
590            break;
591
592        case AVAHI_CLIENT_S_RUNNING:
593        case AVAHI_CLIENT_S_COLLISION:
594        case AVAHI_CLIENT_S_REGISTERING:
595        case AVAHI_CLIENT_CONNECTING:
596            break;
597    }
598}
599
600DNSServiceErrorType DNSSD_API DNSServiceBrowse(
601        DNSServiceRef *ret_sdref,
602        DNSServiceFlags flags,
603        uint32_t interface,
604        const char *regtype,
605        const char *domain,
606        DNSServiceBrowseReply callback,
607        void *context) {
608
609    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
610    int error;
611    DNSServiceRef sdref = NULL;
612    AvahiIfIndex ifindex;
613    struct type_info type_info;
614
615    AVAHI_WARN_LINKAGE;
616
617    if (!ret_sdref || !regtype)
618        return kDNSServiceErr_BadParam;
619    *ret_sdref = NULL;
620
621    if (interface == kDNSServiceInterfaceIndexLocalOnly || flags != 0) {
622        AVAHI_WARN_UNSUPPORTED;
623        return kDNSServiceErr_Unsupported;
624    }
625
626    type_info_init(&type_info);
627
628    if (type_info_parse(&type_info, regtype) < 0 || type_info.n_subtypes > 1) {
629        type_info_free(&type_info);
630
631        if (!avahi_is_valid_service_type_generic(regtype))
632            return kDNSServiceErr_Unsupported;
633    } else
634        regtype = type_info.subtypes ? (char*) type_info.subtypes->text : type_info.type;
635
636    if (!(sdref = sdref_new())) {
637        type_info_free(&type_info);
638        return kDNSServiceErr_Unknown;
639    }
640
641    sdref->context = context;
642    sdref->service_browser_callback = callback;
643
644    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
645
646    if (!(sdref->client = avahi_client_new(avahi_simple_poll_get(sdref->simple_poll), 0, generic_client_callback, sdref, &error))) {
647        ret =  map_error(error);
648        goto finish;
649    }
650
651    ifindex = interface == kDNSServiceInterfaceIndexAny ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface;
652
653    if (!(sdref->service_browser = avahi_service_browser_new(sdref->client, ifindex, AVAHI_PROTO_UNSPEC, regtype, domain, 0, service_browser_callback, sdref))) {
654        ret = map_error(avahi_client_errno(sdref->client));
655        goto finish;
656    }
657
658    ret = kDNSServiceErr_NoError;
659    *ret_sdref = sdref;
660
661finish:
662
663    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
664
665    if (ret != kDNSServiceErr_NoError)
666        DNSServiceRefDeallocate(sdref);
667
668    type_info_free(&type_info);
669
670    return ret;
671}
672
673static void service_resolver_callback(
674    AvahiServiceResolver *r,
675    AvahiIfIndex interface,
676    AVAHI_GCC_UNUSED AvahiProtocol protocol,
677    AvahiResolverEvent event,
678    const char *name,
679    const char *type,
680    const char *domain,
681    const char *host_name,
682    AVAHI_GCC_UNUSED const AvahiAddress *a,
683    uint16_t port,
684    AvahiStringList *txt,
685    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
686    void *userdata) {
687
688    DNSServiceRef sdref = userdata;
689
690    assert(r);
691    assert(sdref);
692    assert(sdref->n_ref >= 1);
693
694    switch (event) {
695        case AVAHI_RESOLVER_FOUND: {
696
697            char host_name_fixed[AVAHI_DOMAIN_NAME_MAX];
698            char full_name[AVAHI_DOMAIN_NAME_MAX];
699            int ret;
700            char *p = NULL;
701            size_t l = 0;
702
703            host_name = add_trailing_dot(host_name, host_name_fixed, sizeof(host_name_fixed));
704
705            if ((p = avahi_new0(char, (l = avahi_string_list_serialize(txt, NULL, 0))+1)))
706                avahi_string_list_serialize(txt, p, l);
707
708            ret = avahi_service_name_join(full_name, sizeof(full_name), name, type, domain);
709            assert(ret == AVAHI_OK);
710
711            strcat(full_name, ".");
712
713            sdref->service_resolver_callback(sdref, 0, interface, kDNSServiceErr_NoError, full_name, host_name, htons(port), l, (unsigned char*) p, sdref->context);
714
715            avahi_free(p);
716            break;
717        }
718
719        case AVAHI_RESOLVER_FAILURE:
720            sdref->service_resolver_callback(sdref, 0, interface, map_error(avahi_client_errno(sdref->client)), NULL, NULL, 0, 0, NULL, sdref->context);
721            break;
722    }
723}
724
725DNSServiceErrorType DNSSD_API DNSServiceResolve(
726    DNSServiceRef *ret_sdref,
727    DNSServiceFlags flags,
728    uint32_t interface,
729    const char *name,
730    const char *regtype,
731    const char *domain,
732    DNSServiceResolveReply callback,
733    void *context) {
734
735    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
736    int error;
737    DNSServiceRef sdref = NULL;
738    AvahiIfIndex ifindex;
739
740    AVAHI_WARN_LINKAGE;
741
742    if (!ret_sdref || !name || !regtype || !domain || !callback)
743        return kDNSServiceErr_BadParam;
744    *ret_sdref = NULL;
745
746    if (interface == kDNSServiceInterfaceIndexLocalOnly || flags != 0) {
747        AVAHI_WARN_UNSUPPORTED;
748        return kDNSServiceErr_Unsupported;
749    }
750
751    if (!(sdref = sdref_new()))
752        return kDNSServiceErr_Unknown;
753
754    sdref->context = context;
755    sdref->service_resolver_callback = callback;
756
757    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
758
759    if (!(sdref->client = avahi_client_new(avahi_simple_poll_get(sdref->simple_poll), 0, generic_client_callback, sdref, &error))) {
760        ret =  map_error(error);
761        goto finish;
762    }
763
764    ifindex = interface == kDNSServiceInterfaceIndexAny ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface;
765
766    if (!(sdref->service_resolver = avahi_service_resolver_new(sdref->client, ifindex, AVAHI_PROTO_UNSPEC, name, regtype, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, sdref))) {
767        ret = map_error(avahi_client_errno(sdref->client));
768        goto finish;
769    }
770
771
772    ret = kDNSServiceErr_NoError;
773    *ret_sdref = sdref;
774
775finish:
776
777    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
778
779    if (ret != kDNSServiceErr_NoError)
780        DNSServiceRefDeallocate(sdref);
781
782    return ret;
783}
784
785int DNSSD_API DNSServiceConstructFullName (
786    char *fullName,
787    const char *service,
788    const char *regtype,
789    const char *domain) {
790
791    AVAHI_WARN_LINKAGE;
792
793    if (!fullName || !regtype || !domain)
794        return -1;
795
796    if (avahi_service_name_join(fullName, kDNSServiceMaxDomainName, service, regtype, domain) < 0)
797        return -1;
798
799    return 0;
800}
801
802static void domain_browser_callback(
803    AvahiDomainBrowser *b,
804    AvahiIfIndex interface,
805    AVAHI_GCC_UNUSED AvahiProtocol protocol,
806    AvahiBrowserEvent event,
807    const char *domain,
808    AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
809    void *userdata) {
810
811    DNSServiceRef sdref = userdata;
812    static char domain_fixed[AVAHI_DOMAIN_NAME_MAX];
813
814    assert(b);
815    assert(sdref);
816    assert(sdref->n_ref >= 1);
817
818    domain  = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
819
820    switch (event) {
821        case AVAHI_BROWSER_NEW:
822            sdref->domain_browser_callback(sdref, kDNSServiceFlagsAdd, interface, kDNSServiceErr_NoError, domain, sdref->context);
823            break;
824
825        case AVAHI_BROWSER_REMOVE:
826            sdref->domain_browser_callback(sdref, 0, interface, kDNSServiceErr_NoError, domain, sdref->context);
827            break;
828
829        case AVAHI_BROWSER_FAILURE:
830            sdref->domain_browser_callback(sdref, 0, interface, map_error(avahi_client_errno(sdref->client)), domain, sdref->context);
831            break;
832
833        case AVAHI_BROWSER_CACHE_EXHAUSTED:
834        case AVAHI_BROWSER_ALL_FOR_NOW:
835            break;
836    }
837}
838
839DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains(
840    DNSServiceRef *ret_sdref,
841    DNSServiceFlags flags,
842    uint32_t interface,
843    DNSServiceDomainEnumReply callback,
844    void *context) {
845
846    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
847    int error;
848    DNSServiceRef sdref = NULL;
849    AvahiIfIndex ifindex;
850
851    AVAHI_WARN_LINKAGE;
852
853    if (!ret_sdref || !callback)
854        return kDNSServiceErr_BadParam;
855    *ret_sdref = NULL;
856
857    if (interface == kDNSServiceInterfaceIndexLocalOnly ||
858        (flags != kDNSServiceFlagsBrowseDomains &&  flags != kDNSServiceFlagsRegistrationDomains)) {
859        AVAHI_WARN_UNSUPPORTED;
860        return kDNSServiceErr_Unsupported;
861    }
862
863    if (!(sdref = sdref_new()))
864        return kDNSServiceErr_Unknown;
865
866    sdref->context = context;
867    sdref->domain_browser_callback = callback;
868
869    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
870
871    if (!(sdref->client = avahi_client_new(avahi_simple_poll_get(sdref->simple_poll), 0, generic_client_callback, sdref, &error))) {
872        ret =  map_error(error);
873        goto finish;
874    }
875
876    ifindex = interface == kDNSServiceInterfaceIndexAny ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface;
877
878    if (!(sdref->domain_browser = avahi_domain_browser_new(sdref->client, ifindex, AVAHI_PROTO_UNSPEC, "local",
879                                                           flags == kDNSServiceFlagsRegistrationDomains ? AVAHI_DOMAIN_BROWSER_REGISTER : AVAHI_DOMAIN_BROWSER_BROWSE,
880                                                           0, domain_browser_callback, sdref))) {
881        ret = map_error(avahi_client_errno(sdref->client));
882        goto finish;
883    }
884
885    ret = kDNSServiceErr_NoError;
886    *ret_sdref = sdref;
887
888finish:
889
890    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
891
892    if (ret != kDNSServiceErr_NoError)
893        DNSServiceRefDeallocate(sdref);
894
895    return ret;
896}
897
898static void reg_report_error(DNSServiceRef sdref, DNSServiceErrorType error) {
899    char regtype_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX];
900    const char *regtype, *domain;
901    assert(sdref);
902    assert(sdref->n_ref >= 1);
903
904    if (!sdref->service_register_callback)
905        return;
906
907    regtype = add_trailing_dot(sdref->type_info.type, regtype_fixed, sizeof(regtype_fixed));
908    domain = add_trailing_dot(sdref->service_domain, domain_fixed, sizeof(domain_fixed));
909
910    sdref->service_register_callback(
911        sdref, 0, error,
912        sdref->service_name_chosen ? sdref->service_name_chosen : sdref->service_name,
913        regtype,
914        domain,
915        sdref->context);
916}
917
918static int reg_create_service(DNSServiceRef sdref) {
919    int ret;
920    AvahiStringList *l;
921
922    assert(sdref);
923    assert(sdref->n_ref >= 1);
924
925    if ((ret = avahi_entry_group_add_service_strlst(
926        sdref->entry_group,
927        sdref->service_interface,
928        AVAHI_PROTO_UNSPEC,
929        0,
930        sdref->service_name_chosen,
931        sdref->type_info.type,
932        sdref->service_domain,
933        sdref->service_host,
934        sdref->service_port,
935        sdref->service_txt)) < 0)
936        return ret;
937
938    for (l = sdref->type_info.subtypes; l; l = l->next) {
939        /* Create a subtype entry */
940
941        if (avahi_entry_group_add_service_subtype(
942                sdref->entry_group,
943                sdref->service_interface,
944                AVAHI_PROTO_UNSPEC,
945                0,
946                sdref->service_name_chosen,
947                sdref->type_info.type,
948                sdref->service_domain,
949                (const char*) l->text) < 0)
950            return ret;
951    }
952
953    if ((ret = avahi_entry_group_commit(sdref->entry_group)) < 0)
954        return ret;
955
956    return 0;
957}
958
959static void reg_client_callback(AvahiClient *s, AvahiClientState state, void* userdata) {
960    DNSServiceRef sdref = userdata;
961
962    assert(s);
963    assert(sdref);
964    assert(sdref->n_ref >= 1);
965
966    /* We've not been setup completely */
967    if (!sdref->entry_group)
968        return;
969
970    switch (state) {
971        case AVAHI_CLIENT_FAILURE:
972            reg_report_error(sdref, kDNSServiceErr_Unknown);
973            break;
974
975        case AVAHI_CLIENT_S_RUNNING: {
976            int ret;
977
978            if (!sdref->service_name) {
979                const char *n;
980                /* If the service name is taken from the host name, copy that */
981
982                avahi_free(sdref->service_name_chosen);
983                sdref->service_name_chosen = NULL;
984
985                if (!(n = avahi_client_get_host_name(sdref->client))) {
986                    reg_report_error(sdref, map_error(avahi_client_errno(sdref->client)));
987                    return;
988                }
989
990                if (!(sdref->service_name_chosen = avahi_strdup(n))) {
991                    reg_report_error(sdref, kDNSServiceErr_NoMemory);
992                    return;
993                }
994            }
995
996            if (!sdref->service_name_chosen) {
997
998                assert(sdref->service_name);
999
1000                if (!(sdref->service_name_chosen = avahi_strdup(sdref->service_name))) {
1001                    reg_report_error(sdref, kDNSServiceErr_NoMemory);
1002                    return;
1003                }
1004            }
1005
1006            /* Register the service */
1007
1008            if ((ret = reg_create_service(sdref)) < 0) {
1009                reg_report_error(sdref, map_error(ret));
1010                return;
1011            }
1012
1013            break;
1014        }
1015
1016        case AVAHI_CLIENT_S_COLLISION:
1017        case AVAHI_CLIENT_S_REGISTERING:
1018
1019            /* Remove our entry */
1020            avahi_entry_group_reset(sdref->entry_group);
1021
1022            break;
1023
1024        case AVAHI_CLIENT_CONNECTING:
1025            /* Ignore */
1026            break;
1027    }
1028
1029}
1030
1031static void reg_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
1032    DNSServiceRef sdref = userdata;
1033
1034    assert(g);
1035
1036    switch (state) {
1037        case AVAHI_ENTRY_GROUP_ESTABLISHED:
1038
1039            /* Inform the user */
1040            reg_report_error(sdref, kDNSServiceErr_NoError);
1041
1042            break;
1043
1044        case AVAHI_ENTRY_GROUP_COLLISION: {
1045            char *n;
1046            int ret;
1047
1048            /* Remove our entry */
1049            avahi_entry_group_reset(sdref->entry_group);
1050
1051            assert(sdref->service_name_chosen);
1052
1053            /* Pick a new name */
1054            if (!(n = avahi_alternative_service_name(sdref->service_name_chosen))) {
1055                reg_report_error(sdref, kDNSServiceErr_NoMemory);
1056                return;
1057            }
1058            avahi_free(sdref->service_name_chosen);
1059            sdref->service_name_chosen = n;
1060
1061            /* Register the service with that new name */
1062            if ((ret = reg_create_service(sdref)) < 0) {
1063                reg_report_error(sdref, map_error(ret));
1064                return;
1065            }
1066
1067            break;
1068        }
1069
1070        case AVAHI_ENTRY_GROUP_REGISTERING:
1071        case AVAHI_ENTRY_GROUP_UNCOMMITED:
1072            /* Ignore */
1073            break;
1074
1075        case AVAHI_ENTRY_GROUP_FAILURE:
1076            /* Inform the user */
1077            reg_report_error(sdref, map_error(avahi_client_errno(sdref->client)));
1078            break;
1079
1080    }
1081}
1082
1083DNSServiceErrorType DNSSD_API DNSServiceRegister (
1084        DNSServiceRef *ret_sdref,
1085        DNSServiceFlags flags,
1086        uint32_t interface,
1087        const char *name,
1088        const char *regtype,
1089        const char *domain,
1090        const char *host,
1091        uint16_t port,
1092        uint16_t txtLen,
1093        const void *txtRecord,
1094        DNSServiceRegisterReply callback,
1095        void *context) {
1096
1097    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
1098    int error;
1099    DNSServiceRef sdref = NULL;
1100    AvahiStringList *txt = NULL;
1101    struct type_info type_info;
1102
1103    AVAHI_WARN_LINKAGE;
1104
1105    if (!ret_sdref || !regtype)
1106        return kDNSServiceErr_BadParam;
1107    *ret_sdref = NULL;
1108
1109    if (!txtRecord) {
1110        txtLen = 1;
1111        txtRecord = "";
1112    }
1113
1114    if (interface == kDNSServiceInterfaceIndexLocalOnly || flags) {
1115        AVAHI_WARN_UNSUPPORTED;
1116        return kDNSServiceErr_Unsupported;
1117    }
1118
1119    if (txtLen > 0)
1120        if (avahi_string_list_parse(txtRecord, txtLen, &txt) < 0)
1121            return kDNSServiceErr_Invalid;
1122
1123    if (type_info_parse(&type_info, regtype) < 0) {
1124        avahi_string_list_free(txt);
1125        return kDNSServiceErr_Invalid;
1126    }
1127
1128    if (!(sdref = sdref_new())) {
1129        avahi_string_list_free(txt);
1130        type_info_free(&type_info);
1131        return kDNSServiceErr_Unknown;
1132    }
1133
1134    sdref->context = context;
1135    sdref->service_register_callback = callback;
1136
1137    sdref->type_info = type_info;
1138    sdref->service_name = avahi_strdup(name);
1139    sdref->service_domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
1140    sdref->service_host = host ? avahi_normalize_name_strdup(host) : NULL;
1141    sdref->service_interface = interface == kDNSServiceInterfaceIndexAny ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface;
1142    sdref->service_port = ntohs(port);
1143    sdref->service_txt = txt;
1144
1145    /* Some OOM checking would be cool here */
1146
1147    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
1148
1149    if (!(sdref->client = avahi_client_new(avahi_simple_poll_get(sdref->simple_poll), 0, reg_client_callback, sdref, &error))) {
1150        ret =  map_error(error);
1151        goto finish;
1152    }
1153
1154    if (!sdref->service_domain) {
1155        const char *d;
1156
1157        if (!(d = avahi_client_get_domain_name(sdref->client))) {
1158            ret = map_error(avahi_client_errno(sdref->client));
1159            goto finish;
1160        }
1161
1162        if (!(sdref->service_domain = avahi_strdup(d))) {
1163            ret = kDNSServiceErr_NoMemory;
1164            goto finish;
1165        }
1166    }
1167
1168    if (!(sdref->entry_group = avahi_entry_group_new(sdref->client, reg_entry_group_callback, sdref))) {
1169        ret = map_error(avahi_client_errno(sdref->client));
1170        goto finish;
1171    }
1172
1173    if (avahi_client_get_state(sdref->client) == AVAHI_CLIENT_S_RUNNING) {
1174        const char *n;
1175
1176        if (sdref->service_name)
1177            n = sdref->service_name;
1178        else {
1179            if (!(n = avahi_client_get_host_name(sdref->client))) {
1180                ret = map_error(avahi_client_errno(sdref->client));
1181                goto finish;
1182            }
1183        }
1184
1185        if (!(sdref->service_name_chosen = avahi_strdup(n))) {
1186            ret = kDNSServiceErr_NoMemory;
1187            goto finish;
1188        }
1189
1190
1191        if ((error = reg_create_service(sdref)) < 0) {
1192            ret = map_error(error);
1193            goto finish;
1194        }
1195    }
1196
1197    ret = kDNSServiceErr_NoError;
1198    *ret_sdref = sdref;
1199
1200finish:
1201
1202    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
1203
1204    if (ret != kDNSServiceErr_NoError)
1205        DNSServiceRefDeallocate(sdref);
1206
1207    return ret;
1208}
1209
1210DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord(
1211         DNSServiceRef sdref,
1212         DNSRecordRef rref,
1213         DNSServiceFlags flags,
1214         uint16_t rdlen,
1215         const void *rdata,
1216         AVAHI_GCC_UNUSED uint32_t ttl) {
1217
1218    int ret = kDNSServiceErr_Unknown;
1219    AvahiStringList *txt = NULL;
1220
1221    AVAHI_WARN_LINKAGE;
1222
1223    if (!sdref || sdref->n_ref <= 0)
1224        return kDNSServiceErr_BadParam;
1225
1226    if (flags || rref) {
1227        AVAHI_WARN_UNSUPPORTED;
1228        return kDNSServiceErr_Unsupported;
1229    }
1230
1231    if (rdlen > 0)
1232        if (avahi_string_list_parse(rdata, rdlen, &txt) < 0)
1233            return kDNSServiceErr_Invalid;
1234
1235    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
1236
1237    if (!avahi_string_list_equal(txt, sdref->service_txt)) {
1238
1239        avahi_string_list_free(sdref->service_txt);
1240        sdref->service_txt = txt;
1241
1242        if (avahi_client_get_state(sdref->client) == AVAHI_CLIENT_S_RUNNING &&
1243            sdref->entry_group &&
1244            (avahi_entry_group_get_state(sdref->entry_group) == AVAHI_ENTRY_GROUP_ESTABLISHED ||
1245            avahi_entry_group_get_state(sdref->entry_group) == AVAHI_ENTRY_GROUP_REGISTERING))
1246
1247            if (avahi_entry_group_update_service_txt_strlst(
1248                        sdref->entry_group,
1249                        sdref->service_interface,
1250                        AVAHI_PROTO_UNSPEC,
1251                        0,
1252                        sdref->service_name_chosen,
1253                        sdref->type_info.type,
1254                        sdref->service_domain,
1255                        sdref->service_txt) < 0) {
1256
1257                ret = map_error(avahi_client_errno(sdref->client));
1258                goto finish;
1259            }
1260
1261    } else
1262        avahi_string_list_free(txt);
1263
1264    ret = kDNSServiceErr_NoError;
1265
1266finish:
1267    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
1268
1269    return ret;
1270}
1271
1272static void query_resolver_callback(
1273        AvahiRecordBrowser *r,
1274        AvahiIfIndex interface,
1275        AVAHI_GCC_UNUSED AvahiProtocol protocol,
1276        AvahiBrowserEvent event,
1277        const char *name,
1278        uint16_t clazz,
1279        uint16_t type,
1280        const void* rdata,
1281        size_t size,
1282        AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
1283        void *userdata) {
1284
1285    DNSServiceRef sdref = userdata;
1286
1287    assert(r);
1288    assert(sdref);
1289    assert(sdref->n_ref >= 1);
1290
1291    switch (event) {
1292
1293    case AVAHI_BROWSER_NEW:
1294    case AVAHI_BROWSER_REMOVE: {
1295
1296        DNSServiceFlags qflags = 0;
1297        if (event == AVAHI_BROWSER_NEW)
1298            qflags |= kDNSServiceFlagsAdd;
1299
1300        sdref->query_resolver_callback(sdref, qflags, interface, kDNSServiceErr_NoError, name, type, clazz, size, rdata, 0, sdref->context);
1301        break;
1302    }
1303
1304    case AVAHI_BROWSER_ALL_FOR_NOW:
1305    case AVAHI_BROWSER_CACHE_EXHAUSTED:
1306        /* not implemented */
1307        break;
1308
1309    case AVAHI_BROWSER_FAILURE:
1310        sdref->query_resolver_callback(sdref, 0, interface, map_error(avahi_client_errno(sdref->client)), NULL, 0, 0, 0, NULL, 0, sdref->context);
1311        break;
1312    }
1313}
1314
1315DNSServiceErrorType DNSSD_API DNSServiceQueryRecord (
1316    DNSServiceRef *ret_sdref,
1317    DNSServiceFlags flags,
1318    uint32_t interface,
1319    const char *fullname,
1320    uint16_t type,
1321    uint16_t clazz,
1322    DNSServiceQueryRecordReply callback,
1323    void *context) {
1324
1325    DNSServiceErrorType ret = kDNSServiceErr_Unknown;
1326    int error;
1327    DNSServiceRef sdref = NULL;
1328    AvahiIfIndex ifindex;
1329
1330    AVAHI_WARN_LINKAGE;
1331
1332    if (!ret_sdref || !fullname)
1333        return kDNSServiceErr_BadParam;
1334    *ret_sdref = NULL;
1335
1336    if (interface == kDNSServiceInterfaceIndexLocalOnly || flags != 0) {
1337        AVAHI_WARN_UNSUPPORTED;
1338        return kDNSServiceErr_Unsupported;
1339    }
1340
1341    if (!(sdref = sdref_new()))
1342        return kDNSServiceErr_Unknown;
1343
1344    sdref->context = context;
1345    sdref->query_resolver_callback = callback;
1346
1347    ASSERT_SUCCESS(pthread_mutex_lock(&sdref->mutex));
1348
1349    if (!(sdref->client = avahi_client_new(avahi_simple_poll_get(sdref->simple_poll), 0, generic_client_callback, sdref, &error))) {
1350        ret =  map_error(error);
1351        goto finish;
1352    }
1353
1354    ifindex = interface == kDNSServiceInterfaceIndexAny ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface;
1355
1356    if (!(sdref->record_browser = avahi_record_browser_new(sdref->client, ifindex, AVAHI_PROTO_UNSPEC, fullname, clazz, type, 0, query_resolver_callback, sdref))) {
1357        ret = map_error(avahi_client_errno(sdref->client));
1358        goto finish;
1359    }
1360
1361    ret = kDNSServiceErr_NoError;
1362    *ret_sdref = sdref;
1363
1364finish:
1365
1366    ASSERT_SUCCESS(pthread_mutex_unlock(&sdref->mutex));
1367
1368    if (ret != kDNSServiceErr_NoError)
1369        DNSServiceRefDeallocate(sdref);
1370
1371    return ret;
1372}
1373