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 <assert.h>
25 
26 #include <pthread.h>
27 
28 #include <avahi-common/strlst.h>
29 #include "avahi-common/avahi-malloc.h"
30 #include <avahi-common/domain.h>
31 #include <avahi-common/simple-watch.h>
32 #include <avahi-common/error.h>
33 #include <avahi-common/llist.h>
34 
35 #include <avahi-client/client.h>
36 #include <avahi-client/publish.h>
37 #include <avahi-client/lookup.h>
38 
39 #include "howl.h"
40 #include "warn.h"
41 
42 #define OID_MAX 50
43 
44 enum {
45     COMMAND_POLL = 'p',
46     COMMAND_QUIT = 'q',
47     COMMAND_POLL_DONE = 'P',
48     COMMAND_POLL_FAILED = 'F'
49 };
50 
51 typedef enum {
52     OID_UNUSED = 0,
53     OID_SERVICE_BROWSER,
54     OID_SERVICE_RESOLVER,
55     OID_DOMAIN_BROWSER,
56     OID_ENTRY_GROUP
57 } oid_type;
58 
59 typedef struct service_data service_data;
60 
61 typedef struct oid_data {
62     oid_type type;
63     sw_opaque extra;
64     sw_discovery discovery;
65     void *object;
66     sw_result (*reply)(void);
67     service_data *service_data;
68 } oid_data;
69 
70 
71 struct service_data {
72     char *name, *regtype, *domain, *host;
73     uint16_t port;
74     AvahiIfIndex interface;
75     AvahiStringList *txt;
76     AVAHI_LLIST_FIELDS(service_data, services);
77 };
78 
79 struct _sw_discovery {
80     int n_ref;
81     AvahiSimplePoll *simple_poll;
82     AvahiClient *client;
83 
84     oid_data oid_table[OID_MAX];
85     sw_discovery_oid oid_index;
86 
87     int thread_fd, main_fd;
88 
89     pthread_t thread;
90     int thread_running;
91 
92     pthread_mutex_t mutex, salt_mutex;
93 
94     AVAHI_LLIST_HEAD(service_data, services);
95 };
96 
97 #define ASSERT_SUCCESS(r) { int __ret = (r); assert(__ret == 0); }
98 
99 #define OID_GET_INDEX(data) ((sw_discovery_oid) (((data) - ((data)->discovery->oid_table))))
100 
101 static sw_discovery discovery_ref(sw_discovery self);
102 static void discovery_unref(sw_discovery self);
103 
add_trailing_dot(const char * s,char * buf,size_t buf_len)104 static const char *add_trailing_dot(const char *s, char *buf, size_t buf_len) {
105     if (!s)
106         return NULL;
107 
108     if (*s == 0)
109         return s;
110 
111     if (s[strlen(s)-1] == '.')
112         return s;
113 
114     snprintf(buf, buf_len, "%s.", s);
115     return buf;
116 }
117 
map_error(int error)118 static sw_result map_error(int error) {
119     switch (error) {
120         case AVAHI_OK:
121             return SW_OKAY;
122 
123         case AVAHI_ERR_NO_MEMORY:
124             return SW_E_MEM;
125     }
126 
127     return SW_E_UNKNOWN;
128 }
129 
read_command(int fd)130 static int read_command(int fd) {
131     ssize_t r;
132     char command;
133 
134     assert(fd >= 0);
135 
136     if ((r = read(fd, &command, 1)) != 1) {
137         fprintf(stderr, __FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
138         return -1;
139     }
140 
141     return command;
142 }
143 
write_command(int fd,char reply)144 static int write_command(int fd, char reply) {
145     assert(fd >= 0);
146 
147     if (write(fd, &reply, 1) != 1) {
148         fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
149         return -1;
150     }
151 
152     return 0;
153 }
154 
poll_func(struct pollfd * ufds,unsigned int nfds,int timeout,void * userdata)155 static int poll_func(struct pollfd *ufds, unsigned int nfds, int timeout, void *userdata) {
156     sw_discovery self = userdata;
157     int ret;
158 
159     assert(self);
160 
161     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
162     ret = poll(ufds, nfds, timeout);
163     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
164 
165     return ret;
166 }
167 
thread_func(void * data)168 static void * thread_func(void *data) {
169     sw_discovery self = data;
170     sigset_t mask;
171 
172     sigfillset(&mask);
173     pthread_sigmask(SIG_BLOCK, &mask, NULL);
174 
175     self->thread = pthread_self();
176     self->thread_running = 1;
177 
178     for (;;) {
179         char command;
180 
181         if ((command = read_command(self->thread_fd)) < 0)
182             break;
183 
184 /*         fprintf(stderr, "Command: %c\n", command); */
185 
186         switch (command) {
187 
188             case COMMAND_POLL: {
189                 int ret;
190 
191                 ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
192 
193                 for (;;) {
194                     errno = 0;
195 
196                     if ((ret = avahi_simple_poll_run(self->simple_poll)) < 0) {
197 
198                         if (errno == EINTR)
199                             continue;
200 
201                         fprintf(stderr, __FILE__": avahi_simple_poll_run() failed: %s\n", strerror(errno));
202                     }
203 
204                     break;
205                 }
206 
207                 ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
208 
209                 if (write_command(self->thread_fd, ret < 0 ? COMMAND_POLL_FAILED : COMMAND_POLL_DONE) < 0)
210                     break;
211 
212                 break;
213             }
214 
215             case COMMAND_QUIT:
216                 return NULL;
217         }
218 
219     }
220 
221     return NULL;
222 }
223 
oid_alloc(sw_discovery self,oid_type type)224 static int oid_alloc(sw_discovery self, oid_type type) {
225     sw_discovery_oid i;
226     assert(self);
227 
228     for (i = 0; i < OID_MAX; i++) {
229 
230         while (self->oid_index >= OID_MAX)
231             self->oid_index -= OID_MAX;
232 
233         if (self->oid_table[self->oid_index].type == OID_UNUSED) {
234             self->oid_table[self->oid_index].type = type;
235             self->oid_table[self->oid_index].discovery = self;
236 
237             assert(OID_GET_INDEX(&self->oid_table[self->oid_index]) == self->oid_index);
238 
239             return self->oid_index ++;
240         }
241 
242         self->oid_index ++;
243     }
244 
245     /* No free entry found */
246 
247     return (sw_discovery_oid) -1;
248 }
249 
oid_release(sw_discovery self,sw_discovery_oid oid)250 static void oid_release(sw_discovery self, sw_discovery_oid oid) {
251     assert(self);
252     assert(oid < OID_MAX);
253 
254     assert(self->oid_table[oid].type != OID_UNUSED);
255 
256     self->oid_table[oid].type = OID_UNUSED;
257     self->oid_table[oid].discovery = NULL;
258     self->oid_table[oid].reply = NULL;
259     self->oid_table[oid].object = NULL;
260     self->oid_table[oid].extra = NULL;
261     self->oid_table[oid].service_data = NULL;
262 }
263 
oid_get(sw_discovery self,sw_discovery_oid oid)264 static oid_data* oid_get(sw_discovery self, sw_discovery_oid oid) {
265     assert(self);
266 
267     if (oid >= OID_MAX)
268         return NULL;
269 
270     if (self->oid_table[oid].type == OID_UNUSED)
271         return NULL;
272 
273     return &self->oid_table[oid];
274 }
275 
service_data_new(sw_discovery self)276 static service_data* service_data_new(sw_discovery self) {
277     service_data *sdata;
278 
279     assert(self);
280 
281     if (!(sdata = avahi_new0(service_data, 1)))
282         return NULL;
283 
284     AVAHI_LLIST_PREPEND(service_data, services, self->services, sdata);
285 
286     return sdata;
287 
288 }
289 
service_data_free(sw_discovery self,service_data * sdata)290 static void service_data_free(sw_discovery self, service_data* sdata) {
291     assert(self);
292     assert(sdata);
293 
294     AVAHI_LLIST_REMOVE(service_data, services, self->services, sdata);
295 
296     avahi_free(sdata->name);
297     avahi_free(sdata->regtype);
298     avahi_free(sdata->domain);
299     avahi_free(sdata->host);
300     avahi_string_list_free(sdata->txt);
301     avahi_free(sdata);
302 }
303 
304 static void reg_client_callback(oid_data *data, AvahiClientState state);
305 
client_callback(AvahiClient * s,AvahiClientState state,void * userdata)306 static void client_callback(AvahiClient *s, AvahiClientState state, void* userdata) {
307     sw_discovery self = userdata;
308     sw_discovery_oid oid;
309 
310     assert(s);
311     assert(self);
312 
313     discovery_ref(self);
314 
315     for (oid = 0; oid < OID_MAX; oid++) {
316 
317         switch (self->oid_table[oid].type) {
318 
319             case OID_ENTRY_GROUP:
320                 reg_client_callback(&self->oid_table[oid], state);
321                 break;
322 
323             case OID_DOMAIN_BROWSER:
324             case OID_SERVICE_BROWSER:
325                 ((sw_discovery_browse_reply) self->oid_table[oid].reply)(self, oid, SW_DISCOVERY_BROWSE_INVALID, 0, NULL, NULL, NULL, self->oid_table[oid].extra);
326                 break;
327 
328             case OID_SERVICE_RESOLVER:
329             case OID_UNUSED:
330                 ;
331         }
332     }
333 
334     discovery_unref(self);
335 }
336 
sw_discovery_init(sw_discovery * self)337 sw_result sw_discovery_init(sw_discovery * self) {
338     int fd[2] = { -1, -1};
339     sw_result result = SW_E_UNKNOWN;
340     pthread_mutexattr_t mutex_attr;
341     int error;
342 
343     assert(self);
344 
345     AVAHI_WARN_LINKAGE;
346 
347     *self = NULL;
348 
349     if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
350         goto fail;
351 
352     if (!(*self = avahi_new(struct _sw_discovery, 1))) {
353         result = SW_E_MEM;
354         goto fail;
355     }
356 
357     (*self)->n_ref = 1;
358     (*self)->thread_fd = fd[0];
359     (*self)->main_fd = fd[1];
360 
361     (*self)->client = NULL;
362     (*self)->simple_poll = NULL;
363 
364     memset((*self)->oid_table, 0, sizeof((*self)->oid_table));
365     (*self)->oid_index = 0;
366 
367     (*self)->thread_running = 0;
368 
369     AVAHI_LLIST_HEAD_INIT(service_info, (*self)->services);
370 
371     ASSERT_SUCCESS(pthread_mutexattr_init(&mutex_attr));
372     pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
373     ASSERT_SUCCESS(pthread_mutex_init(&(*self)->mutex, &mutex_attr));
374     ASSERT_SUCCESS(pthread_mutex_init(&(*self)->salt_mutex, &mutex_attr));
375 
376     if (!((*self)->simple_poll = avahi_simple_poll_new()))
377         goto fail;
378 
379     avahi_simple_poll_set_func((*self)->simple_poll, poll_func, *self);
380 
381     if (!((*self)->client = avahi_client_new(avahi_simple_poll_get((*self)->simple_poll), 0, client_callback, *self, &error))) {
382         result = map_error(error);
383         goto fail;
384     }
385 
386     /* Start simple poll */
387     if (avahi_simple_poll_prepare((*self)->simple_poll, -1) < 0)
388         goto fail;
389 
390     /* Queue an initial POLL command for the thread */
391     if (write_command((*self)->main_fd, COMMAND_POLL) < 0)
392         goto fail;
393 
394     if (pthread_create(&(*self)->thread, NULL, thread_func, *self) != 0)
395         goto fail;
396 
397     (*self)->thread_running = 1;
398 
399     return SW_OKAY;
400 
401 fail:
402 
403     if (*self)
404         sw_discovery_fina(*self);
405 
406     return result;
407 }
408 
stop_thread(sw_discovery self)409 static int stop_thread(sw_discovery self) {
410     assert(self);
411 
412     if (!self->thread_running)
413         return 0;
414 
415     if (write_command(self->main_fd, COMMAND_QUIT) < 0)
416         return -1;
417 
418     avahi_simple_poll_wakeup(self->simple_poll);
419 
420     ASSERT_SUCCESS(pthread_join(self->thread, NULL));
421     self->thread_running = 0;
422     return 0;
423 }
424 
discovery_ref(sw_discovery self)425 static sw_discovery discovery_ref(sw_discovery self) {
426     assert(self);
427     assert(self->n_ref >= 1);
428 
429     self->n_ref++;
430 
431     return self;
432 }
433 
discovery_unref(sw_discovery self)434 static void discovery_unref(sw_discovery self) {
435     assert(self);
436     assert(self->n_ref >= 1);
437 
438     if (--self->n_ref > 0)
439         return;
440 
441     stop_thread(self);
442 
443     if (self->client)
444         avahi_client_free(self->client);
445 
446     if (self->simple_poll)
447         avahi_simple_poll_free(self->simple_poll);
448 
449     if (self->thread_fd >= 0)
450         close(self->thread_fd);
451 
452     if (self->main_fd >= 0)
453         close(self->main_fd);
454 
455     ASSERT_SUCCESS(pthread_mutex_destroy(&self->mutex));
456     ASSERT_SUCCESS(pthread_mutex_destroy(&self->salt_mutex));
457 
458     while (self->services)
459         service_data_free(self, self->services);
460 
461     avahi_free(self);
462 }
463 
sw_discovery_fina(sw_discovery self)464 sw_result sw_discovery_fina(sw_discovery self) {
465     assert(self);
466 
467     AVAHI_WARN_LINKAGE;
468 
469     stop_thread(self);
470     discovery_unref(self);
471 
472     return SW_OKAY;
473 }
474 
sw_discovery_run(sw_discovery self)475 sw_result sw_discovery_run(sw_discovery self) {
476     assert(self);
477 
478     AVAHI_WARN_LINKAGE;
479 
480     return sw_salt_run((sw_salt) self);
481 }
482 
sw_discovery_stop_run(sw_discovery self)483 sw_result sw_discovery_stop_run(sw_discovery self) {
484     assert(self);
485 
486     AVAHI_WARN_LINKAGE;
487 
488     return sw_salt_stop_run((sw_salt) self);
489 }
490 
sw_discovery_socket(sw_discovery self)491 int sw_discovery_socket(sw_discovery self) {
492     assert(self);
493 
494     AVAHI_WARN_LINKAGE;
495 
496     return self->main_fd;
497 }
498 
sw_discovery_read_socket(sw_discovery self)499 sw_result sw_discovery_read_socket(sw_discovery self) {
500     sw_result result = SW_E_UNKNOWN;
501 
502     assert(self);
503 
504     discovery_ref(self);
505 
506     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
507 
508     /* Cleanup notification socket */
509     if (read_command(self->main_fd) != COMMAND_POLL_DONE)
510         goto finish;
511 
512     if (avahi_simple_poll_dispatch(self->simple_poll) < 0)
513         goto finish;
514 
515     if (self->n_ref > 1) /* Perhaps we should die */
516 
517         /* Dispatch events */
518         if (avahi_simple_poll_prepare(self->simple_poll, -1) < 0)
519             goto finish;
520 
521     if (self->n_ref > 1)
522 
523         /* Request the poll */
524         if (write_command(self->main_fd, COMMAND_POLL) < 0)
525             goto finish;
526 
527     result = SW_OKAY;
528 
529 finish:
530 
531     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
532 
533     discovery_unref(self);
534 
535     return result;
536 }
537 
sw_discovery_salt(sw_discovery self,sw_salt * salt)538 sw_result sw_discovery_salt(sw_discovery self, sw_salt *salt) {
539     assert(self);
540     assert(salt);
541 
542     AVAHI_WARN_LINKAGE;
543 
544     *salt = (sw_salt) self;
545 
546     return SW_OKAY;
547 }
548 
sw_salt_step(sw_salt self,sw_uint32 * msec)549 sw_result sw_salt_step(sw_salt self, sw_uint32 * msec) {
550     struct pollfd p;
551     int r;
552     sw_result result;
553 
554     AVAHI_WARN_LINKAGE;
555 
556     if (!((sw_discovery) self)->thread_running)
557         return SW_E_UNKNOWN;
558 
559     memset(&p, 0, sizeof(p));
560     p.fd = ((sw_discovery) self)->main_fd;
561     p.events = POLLIN;
562 
563     if ((r = poll(&p, 1, msec ? (int) *msec : -1)) < 0) {
564 
565         /* Don't treat EINTR as error */
566         if (errno == EINTR)
567             return SW_OKAY;
568 
569         return SW_E_UNKNOWN;
570 
571     } else if (r == 0) {
572 
573         /* Timeoout */
574         return SW_OKAY;
575 
576     } else {
577         /* Success */
578 
579         if (p.revents != POLLIN)
580             return SW_E_UNKNOWN;
581 
582         if ((result = sw_discovery_read_socket((sw_discovery) self)) != SW_OKAY)
583             return result;
584     }
585 
586     return SW_OKAY;
587 }
588 
sw_salt_run(sw_salt self)589 sw_result sw_salt_run(sw_salt self) {
590     sw_result ret;
591 
592     AVAHI_WARN_LINKAGE;
593 
594     assert(self);
595 
596     for (;;)
597         if ((ret = sw_salt_step(self, NULL)) != SW_OKAY)
598             return ret;
599 }
600 
sw_salt_stop_run(sw_salt self)601 sw_result sw_salt_stop_run(sw_salt self) {
602     AVAHI_WARN_LINKAGE;
603 
604     assert(self);
605 
606     if (stop_thread((sw_discovery) self) < 0)
607         return SW_E_UNKNOWN;
608 
609     return SW_OKAY;
610 }
611 
sw_salt_lock(sw_salt self)612 sw_result sw_salt_lock(sw_salt self) {
613     AVAHI_WARN_LINKAGE;
614 
615     assert(self);
616     ASSERT_SUCCESS(pthread_mutex_lock(&((sw_discovery) self)->salt_mutex));
617 
618     return SW_OKAY;
619 }
620 
sw_salt_unlock(sw_salt self)621 sw_result sw_salt_unlock(sw_salt self) {
622     assert(self);
623 
624     AVAHI_WARN_LINKAGE;
625 
626     ASSERT_SUCCESS(pthread_mutex_unlock(&((sw_discovery) self)->salt_mutex));
627 
628     return SW_OKAY;
629 }
630 
reg_report_status(oid_data * data,sw_discovery_publish_status status)631 static void reg_report_status(oid_data *data, sw_discovery_publish_status status) {
632     sw_discovery_publish_reply reply;
633 
634     assert(data);
635 
636     reply = (sw_discovery_publish_reply) data->reply;
637 
638     reply(data->discovery,
639           OID_GET_INDEX(data),
640           status,
641           data->extra);
642 }
643 
reg_create_service(oid_data * data)644 static int reg_create_service(oid_data *data) {
645     int ret;
646     const char *real_type;
647 
648     assert(data);
649 
650     real_type = avahi_get_type_from_subtype(data->service_data->regtype);
651 
652     if ((ret = avahi_entry_group_add_service_strlst(
653              data->object,
654              data->service_data->interface,
655              AVAHI_PROTO_INET,
656              0,
657              data->service_data->name,
658              real_type ? real_type : data->service_data->regtype,
659              data->service_data->domain,
660              data->service_data->host,
661              data->service_data->port,
662              data->service_data->txt)) < 0)
663         return ret;
664 
665     if (real_type) {
666         /* Create a subtype entry */
667 
668         if (avahi_entry_group_add_service_subtype(
669                 data->object,
670                 data->service_data->interface,
671                 AVAHI_PROTO_INET,
672                 0,
673                 data->service_data->name,
674                 real_type,
675                 data->service_data->domain,
676                 data->service_data->regtype) < 0)
677             return ret;
678 
679     }
680 
681     if ((ret = avahi_entry_group_commit(data->object)) < 0)
682         return ret;
683 
684     return 0;
685 }
686 
reg_client_callback(oid_data * data,AvahiClientState state)687 static void reg_client_callback(oid_data *data, AvahiClientState state) {
688     assert(data);
689 
690     /* We've not been setup completely */
691     if (!data->object)
692         return;
693 
694     switch (state) {
695         case AVAHI_CLIENT_FAILURE:
696             reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
697             break;
698 
699         case AVAHI_CLIENT_S_RUNNING: {
700             int ret;
701 
702             /* Register the service */
703             if ((ret = reg_create_service(data)) < 0) {
704                 reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
705                 return;
706             }
707 
708             break;
709         }
710 
711         case AVAHI_CLIENT_S_COLLISION:
712         case AVAHI_CLIENT_S_REGISTERING:
713 
714             /* Remove our entry */
715             avahi_entry_group_reset(data->object);
716             break;
717 
718         case AVAHI_CLIENT_CONNECTING:
719             /* Ignore */
720             break;
721     }
722 
723 }
724 
reg_entry_group_callback(AvahiEntryGroup * g,AvahiEntryGroupState state,void * userdata)725 static void reg_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
726     oid_data *data = userdata;
727 
728     assert(g);
729     assert(data);
730 
731     switch (state) {
732         case AVAHI_ENTRY_GROUP_ESTABLISHED:
733 
734             reg_report_status(data, SW_DISCOVERY_PUBLISH_STARTED);
735             break;
736 
737         case AVAHI_ENTRY_GROUP_COLLISION:
738 
739             reg_report_status(data, SW_DISCOVERY_PUBLISH_NAME_COLLISION);
740             break;
741 
742         case AVAHI_ENTRY_GROUP_REGISTERING:
743         case AVAHI_ENTRY_GROUP_UNCOMMITED:
744             /* Ignore */
745             break;
746 
747         case AVAHI_ENTRY_GROUP_FAILURE:
748             reg_report_status(data, SW_DISCOVERY_PUBLISH_INVALID);
749             break;
750 
751     }
752 }
753 
sw_discovery_publish(sw_discovery self,sw_uint32 interface_index,sw_const_string name,sw_const_string type,sw_const_string domain,sw_const_string host,sw_port port,sw_octets text_record,sw_uint32 text_record_len,sw_discovery_publish_reply reply,sw_opaque extra,sw_discovery_oid * oid)754 sw_result sw_discovery_publish(
755     sw_discovery self,
756     sw_uint32 interface_index,
757     sw_const_string name,
758     sw_const_string type,
759     sw_const_string domain,
760     sw_const_string host,
761     sw_port port,
762     sw_octets text_record,
763     sw_uint32 text_record_len,
764     sw_discovery_publish_reply reply,
765     sw_opaque extra,
766     sw_discovery_oid * oid) {
767 
768     oid_data *data;
769     sw_result result = SW_E_UNKNOWN;
770     service_data *sdata;
771     AvahiStringList *txt = NULL;
772 
773     assert(self);
774     assert(name);
775     assert(type);
776     assert(reply);
777     assert(oid);
778 
779     AVAHI_WARN_LINKAGE;
780 
781     if (text_record && text_record_len > 0)
782         if (avahi_string_list_parse(text_record, text_record_len, &txt) < 0)
783             return SW_E_UNKNOWN;
784 
785     if ((*oid = oid_alloc(self, OID_ENTRY_GROUP)) == (sw_discovery_oid) -1) {
786         avahi_string_list_free(txt);
787         return SW_E_UNKNOWN;
788     }
789 
790     if (!(sdata = service_data_new(self))) {
791         avahi_string_list_free(txt);
792         oid_release(self, *oid);
793         return SW_E_MEM;
794     }
795 
796     data = oid_get(self, *oid);
797     assert(data);
798     data->reply = (sw_result (*)(void)) reply;
799     data->extra = extra;
800     data->service_data = sdata;
801 
802     sdata->interface = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
803     sdata->name = avahi_strdup(name);
804     sdata->regtype = type ? avahi_normalize_name_strdup(type) : NULL;
805     sdata->domain = domain ? avahi_normalize_name_strdup(domain) : NULL;
806     sdata->host = host ? avahi_normalize_name_strdup(host) : NULL;
807     sdata->port = port;
808     sdata->txt = txt;
809 
810     /* Some OOM checking would be cool here */
811 
812     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
813 
814     if (!(data->object = avahi_entry_group_new(self->client, reg_entry_group_callback, data))) {
815         result = map_error(avahi_client_errno(self->client));
816         goto finish;
817     }
818 
819     if (avahi_client_get_state(self->client) == AVAHI_CLIENT_S_RUNNING) {
820         int error;
821 
822         if ((error = reg_create_service(data)) < 0) {
823             result = map_error(error);
824             goto finish;
825         }
826     }
827 
828     result = SW_OKAY;
829 
830 finish:
831 
832     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
833 
834     if (result != SW_OKAY)
835         if (*oid != (sw_discovery_oid) -1)
836             sw_discovery_cancel(self, *oid);
837 
838     return result;
839 }
840 
domain_browser_callback(AvahiDomainBrowser * b,AvahiIfIndex interface,AVAHI_GCC_UNUSED AvahiProtocol protocol,AvahiBrowserEvent event,const char * domain,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)841 static void domain_browser_callback(
842     AvahiDomainBrowser *b,
843     AvahiIfIndex interface,
844     AVAHI_GCC_UNUSED AvahiProtocol protocol,
845     AvahiBrowserEvent event,
846     const char *domain,
847     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
848     void *userdata) {
849 
850     oid_data* data = userdata;
851     sw_discovery_browse_reply reply;
852     static char domain_fixed[AVAHI_DOMAIN_NAME_MAX];
853 
854     assert(b);
855     assert(data);
856 
857     reply = (sw_discovery_browse_reply) data->reply;
858 
859     domain  = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
860 
861     switch (event) {
862         case AVAHI_BROWSER_NEW:
863             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_DOMAIN, interface, NULL, NULL, domain, data->extra);
864             break;
865 
866         case AVAHI_BROWSER_REMOVE:
867             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_DOMAIN, interface, NULL, NULL, domain, data->extra);
868             break;
869 
870         case AVAHI_BROWSER_FAILURE:
871             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, NULL, NULL, domain, data->extra);
872             break;
873 
874         case AVAHI_BROWSER_CACHE_EXHAUSTED:
875         case AVAHI_BROWSER_ALL_FOR_NOW:
876             break;
877     }
878 }
879 
sw_discovery_browse_domains(sw_discovery self,sw_uint32 interface_index,sw_discovery_browse_reply reply,sw_opaque extra,sw_discovery_oid * oid)880 sw_result sw_discovery_browse_domains(
881     sw_discovery self,
882     sw_uint32 interface_index,
883     sw_discovery_browse_reply reply,
884     sw_opaque extra,
885     sw_discovery_oid * oid) {
886 
887     oid_data *data;
888     AvahiIfIndex ifindex;
889     sw_result result = SW_E_UNKNOWN;
890 
891     assert(self);
892     assert(reply);
893     assert(oid);
894 
895     AVAHI_WARN_LINKAGE;
896 
897     if ((*oid = oid_alloc(self, OID_DOMAIN_BROWSER)) == (sw_discovery_oid) -1)
898         return SW_E_UNKNOWN;
899 
900     data = oid_get(self, *oid);
901     assert(data);
902     data->reply = (sw_result (*)(void)) reply;
903     data->extra = extra;
904 
905     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
906 
907     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
908 
909     if (!(data->object = avahi_domain_browser_new(self->client, ifindex, AVAHI_PROTO_INET, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, domain_browser_callback, data))) {
910         result = map_error(avahi_client_errno(self->client));
911         goto finish;
912     }
913 
914     result = SW_OKAY;
915 
916 finish:
917 
918     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
919 
920     if (result != SW_OKAY)
921         if (*oid != (sw_discovery_oid) -1)
922             sw_discovery_cancel(self, *oid);
923 
924     return result;
925 }
926 
service_resolver_callback(AvahiServiceResolver * r,AvahiIfIndex interface,AVAHI_GCC_UNUSED AvahiProtocol protocol,AvahiResolverEvent event,const char * name,const char * type,const char * domain,const char * host_name,const AvahiAddress * a,uint16_t port,AvahiStringList * txt,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)927 static void service_resolver_callback(
928     AvahiServiceResolver *r,
929     AvahiIfIndex interface,
930     AVAHI_GCC_UNUSED AvahiProtocol protocol,
931     AvahiResolverEvent event,
932     const char *name,
933     const char *type,
934     const char *domain,
935     const char *host_name,
936     const AvahiAddress *a,
937     uint16_t port,
938     AvahiStringList *txt,
939     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
940     void *userdata) {
941 
942     oid_data* data = userdata;
943     sw_discovery_resolve_reply reply;
944 
945     assert(r);
946     assert(data);
947 
948     reply = (sw_discovery_resolve_reply) data->reply;
949 
950     switch (event) {
951         case AVAHI_RESOLVER_FOUND: {
952 
953             char host_name_fixed[AVAHI_DOMAIN_NAME_MAX];
954             uint8_t *p = NULL;
955             size_t l = 0;
956             sw_ipv4_address addr;
957 
958             sw_ipv4_address_init_from_saddr(&addr, a->data.ipv4.address);
959 
960             host_name = add_trailing_dot(host_name, host_name_fixed, sizeof(host_name_fixed));
961 
962             if ((p = avahi_new0(uint8_t, (l = avahi_string_list_serialize(txt, NULL, 0))+1)))
963                 avahi_string_list_serialize(txt, p, l);
964 
965             reply(data->discovery, OID_GET_INDEX(data), interface, name, type, domain, addr, port, p, l, data->extra);
966 
967             avahi_free(p);
968             break;
969         }
970 
971         case AVAHI_RESOLVER_FAILURE:
972 
973             /* Apparently there is no way in HOWL to inform about failed resolvings ... */
974 
975             avahi_warn("A service failed to resolve in the HOWL compatiblity layer of Avahi which is used by '%s'. "
976                        "Since the HOWL API doesn't offer any means to inform the application about this, we have to ignore the failure. "
977                        "Please fix your application to use the native API of Avahi!",
978                        avahi_exe_name());
979 
980             break;
981     }
982 }
983 
sw_discovery_resolve(sw_discovery self,sw_uint32 interface_index,sw_const_string name,sw_const_string type,sw_const_string domain,sw_discovery_resolve_reply reply,sw_opaque extra,sw_discovery_oid * oid)984 sw_result sw_discovery_resolve(
985     sw_discovery self,
986     sw_uint32 interface_index,
987     sw_const_string name,
988     sw_const_string type,
989     sw_const_string domain,
990     sw_discovery_resolve_reply reply,
991     sw_opaque extra,
992     sw_discovery_oid * oid) {
993 
994     oid_data *data;
995     AvahiIfIndex ifindex;
996     sw_result result = SW_E_UNKNOWN;
997 
998     assert(self);
999     assert(name);
1000     assert(type);
1001     assert(reply);
1002     assert(oid);
1003 
1004     AVAHI_WARN_LINKAGE;
1005 
1006     if ((*oid = oid_alloc(self, OID_SERVICE_RESOLVER)) == (sw_discovery_oid) -1)
1007         return SW_E_UNKNOWN;
1008 
1009     data = oid_get(self, *oid);
1010     assert(data);
1011     data->reply = (sw_result (*)(void)) reply;
1012     data->extra = extra;
1013 
1014     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
1015 
1016     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
1017 
1018     if (!(data->object = avahi_service_resolver_new(self->client, ifindex, AVAHI_PROTO_INET, name, type, domain, AVAHI_PROTO_INET, 0, service_resolver_callback, data))) {
1019         result = map_error(avahi_client_errno(self->client));
1020         goto finish;
1021     }
1022 
1023     result = SW_OKAY;
1024 
1025 finish:
1026 
1027     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
1028 
1029     if (result != SW_OKAY)
1030         if (*oid != (sw_discovery_oid) -1)
1031             sw_discovery_cancel(self, *oid);
1032 
1033     return result;
1034 }
1035 
service_browser_callback(AvahiServiceBrowser * b,AvahiIfIndex interface,AVAHI_GCC_UNUSED AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,void * userdata)1036 static void service_browser_callback(
1037     AvahiServiceBrowser *b,
1038     AvahiIfIndex interface,
1039     AVAHI_GCC_UNUSED AvahiProtocol protocol,
1040     AvahiBrowserEvent event,
1041     const char *name,
1042     const char *type,
1043     const char *domain,
1044     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
1045     void *userdata) {
1046 
1047     oid_data* data = userdata;
1048     char type_fixed[AVAHI_DOMAIN_NAME_MAX], domain_fixed[AVAHI_DOMAIN_NAME_MAX];
1049     sw_discovery_browse_reply reply;
1050 
1051     assert(b);
1052     assert(data);
1053 
1054     reply = (sw_discovery_browse_reply) data->reply;
1055 
1056     type = add_trailing_dot(type, type_fixed, sizeof(type_fixed));
1057     domain = add_trailing_dot(domain, domain_fixed, sizeof(domain_fixed));
1058 
1059     switch (event) {
1060         case AVAHI_BROWSER_NEW:
1061             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_ADD_SERVICE, interface, name, type, domain, data->extra);
1062             break;
1063 
1064         case AVAHI_BROWSER_REMOVE:
1065             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_REMOVE_SERVICE, interface, name, type, domain, data->extra);
1066             break;
1067 
1068         case AVAHI_BROWSER_FAILURE:
1069             reply(data->discovery, OID_GET_INDEX(data), SW_DISCOVERY_BROWSE_INVALID, interface, name, type, domain, data->extra);
1070             break;
1071 
1072         case AVAHI_BROWSER_CACHE_EXHAUSTED:
1073         case AVAHI_BROWSER_ALL_FOR_NOW:
1074             break;
1075     }
1076 }
1077 
sw_discovery_browse(sw_discovery self,sw_uint32 interface_index,sw_const_string type,sw_const_string domain,sw_discovery_browse_reply reply,sw_opaque extra,sw_discovery_oid * oid)1078 sw_result sw_discovery_browse(
1079     sw_discovery self,
1080     sw_uint32 interface_index,
1081     sw_const_string type,
1082     sw_const_string domain,
1083     sw_discovery_browse_reply reply,
1084     sw_opaque extra,
1085     sw_discovery_oid * oid) {
1086 
1087     oid_data *data;
1088     AvahiIfIndex ifindex;
1089     sw_result result = SW_E_UNKNOWN;
1090 
1091     assert(self);
1092     assert(type);
1093     assert(reply);
1094     assert(oid);
1095 
1096     AVAHI_WARN_LINKAGE;
1097 
1098     if ((*oid = oid_alloc(self, OID_SERVICE_BROWSER)) == (sw_discovery_oid) -1)
1099         return SW_E_UNKNOWN;
1100 
1101     data = oid_get(self, *oid);
1102     assert(data);
1103     data->reply = (sw_result (*)(void)) reply;
1104     data->extra = extra;
1105 
1106     ifindex = interface_index == 0 ? AVAHI_IF_UNSPEC : (AvahiIfIndex) interface_index;
1107 
1108     ASSERT_SUCCESS(pthread_mutex_lock(&self->mutex));
1109 
1110     if (!(data->object = avahi_service_browser_new(self->client, ifindex, AVAHI_PROTO_INET, type, domain, 0, service_browser_callback, data))) {
1111         result = map_error(avahi_client_errno(self->client));
1112         goto finish;
1113     }
1114 
1115     result = SW_OKAY;
1116 
1117 finish:
1118 
1119     ASSERT_SUCCESS(pthread_mutex_unlock(&self->mutex));
1120 
1121     if (result != SW_OKAY)
1122         if (*oid != (sw_discovery_oid) -1)
1123             sw_discovery_cancel(self, *oid);
1124 
1125     return result;
1126 }
1127 
sw_discovery_cancel(sw_discovery self,sw_discovery_oid oid)1128 sw_result sw_discovery_cancel(sw_discovery self, sw_discovery_oid oid) {
1129     oid_data *data;
1130     assert(self);
1131 
1132     AVAHI_WARN_LINKAGE;
1133 
1134     if (!(data = oid_get(self, oid)))
1135         return SW_E_UNKNOWN;
1136 
1137     if (data->object) {
1138         switch (data->type) {
1139             case OID_SERVICE_BROWSER:
1140                 avahi_service_browser_free(data->object);
1141                 break;
1142 
1143             case OID_SERVICE_RESOLVER:
1144                 avahi_service_resolver_free(data->object);
1145                 break;
1146 
1147             case OID_DOMAIN_BROWSER:
1148                 avahi_domain_browser_free(data->object);
1149                 break;
1150 
1151             case OID_ENTRY_GROUP:
1152                 avahi_entry_group_free(data->object);
1153                 break;
1154 
1155             case OID_UNUSED:
1156             ;
1157         }
1158     }
1159 
1160     if (data->service_data) {
1161         assert(data->type == OID_ENTRY_GROUP);
1162         service_data_free(self, data->service_data);
1163     }
1164 
1165     oid_release(self, oid);
1166 
1167     return SW_OKAY;
1168 }
1169 
sw_discovery_init_with_flags(sw_discovery * self,sw_discovery_init_flags flags)1170 sw_result sw_discovery_init_with_flags(
1171     sw_discovery * self,
1172     sw_discovery_init_flags flags) {
1173 
1174     assert(self);
1175 
1176     AVAHI_WARN_LINKAGE;
1177 
1178     if (flags != SW_DISCOVERY_USE_SHARED_SERVICE)
1179         return SW_E_NO_IMPL;
1180 
1181     return sw_discovery_init(self);
1182 }
1183