Replace functions which called once with their bodies
[pidgin-git.git] / libpurple / protocols / bonjour / mdns_avahi.c
blob00943cc1ea7a7cb916a3720d177865ef95ac93b2
1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Library General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
17 #include "internal.h"
18 #include <purple.h>
20 #include "mdns_interface.h"
21 #include "buddy.h"
22 #include "bonjour.h"
24 #include <avahi-client/client.h>
25 #include <avahi-client/lookup.h>
26 #include <avahi-client/publish.h>
28 #include <avahi-common/address.h>
29 #include <avahi-common/malloc.h>
30 #include <avahi-common/error.h>
31 #include <avahi-common/strlst.h>
33 #include <avahi-glib/glib-malloc.h>
34 #include <avahi-glib/glib-watch.h>
36 /* Avahi only defines the types that it actually uses (which at this time doesn't include NULL) */
37 #ifndef AVAHI_DNS_TYPE_NULL
38 #define AVAHI_DNS_TYPE_NULL 0x0A
39 #endif
41 /* data used by avahi bonjour implementation */
42 typedef struct {
43 AvahiClient *client;
44 AvahiGLibPoll *glib_poll;
45 AvahiServiceBrowser *sb;
46 AvahiEntryGroup *group;
47 AvahiEntryGroup *buddy_icon_group;
48 } AvahiSessionImplData;
50 typedef struct {
51 AvahiServiceResolver *resolver;
52 AvahiIfIndex interface;
53 AvahiProtocol protocol;
54 gchar *name;
55 gchar *type;
56 gchar *domain;
57 /* This is a reference to the entry in BonjourBuddy->ips */
58 const char *ip;
59 } AvahiSvcResolverData;
61 typedef struct {
62 GSList *resolvers;
63 AvahiRecordBrowser *buddy_icon_rec_browser;
64 } AvahiBuddyImplData;
66 static gint
67 _find_resolver_data(gconstpointer a, gconstpointer b) {
68 const AvahiSvcResolverData *rd_a = a;
69 const AvahiSvcResolverData *rd_b = b;
70 gint ret = 1;
72 if(rd_a->interface == rd_b->interface
73 && rd_a->protocol == rd_b->protocol
74 && purple_strequal(rd_a->name, rd_b->name)
75 && purple_strequal(rd_a->type, rd_b->type)
76 && purple_strequal(rd_a->domain, rd_b->domain)) {
77 ret = 0;
80 return ret;
83 static gint
84 _find_resolver_data_by_resolver(gconstpointer a, gconstpointer b) {
85 const AvahiSvcResolverData *rd_a = a;
86 const AvahiServiceResolver *resolver = b;
87 gint ret = 1;
89 if(rd_a->resolver == resolver)
90 ret = 0;
92 return ret;
95 static void
96 _cleanup_resolver_data(AvahiSvcResolverData *rd) {
97 if (rd->resolver)
98 avahi_service_resolver_free(rd->resolver);
99 g_free(rd->name);
100 g_free(rd->type);
101 g_free(rd->domain);
102 g_free(rd);
106 static void
107 _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
108 AvahiResolverEvent event, const char *name, const char *type, const char *domain,
109 const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
110 AvahiLookupResultFlags flags, void *userdata) {
112 PurpleBuddy *pb;
113 BonjourBuddy *bb;
114 PurpleAccount *account = userdata;
115 AvahiStringList *l;
116 size_t size;
117 char *key, *value;
118 char ip[AVAHI_ADDRESS_STR_MAX];
119 AvahiBuddyImplData *b_impl;
120 AvahiSvcResolverData *rd;
121 GSList *res;
123 g_return_if_fail(r != NULL);
125 pb = purple_blist_find_buddy(account, name);
126 bb = (pb != NULL) ? purple_buddy_get_protocol_data(pb) : NULL;
128 switch (event) {
129 case AVAHI_RESOLVER_FAILURE:
130 purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
131 avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
133 avahi_service_resolver_free(r);
134 if (bb != NULL) {
135 b_impl = bb->mdns_impl_data;
136 res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver);
137 if (res != NULL) {
138 rd = res->data;
139 b_impl->resolvers = g_slist_delete_link(b_impl->resolvers, res);
141 /* We've already freed the resolver */
142 rd->resolver = NULL;
143 _cleanup_resolver_data(rd);
145 /* If this was the last resolver, remove the buddy */
146 if (b_impl->resolvers == NULL)
147 bonjour_buddy_signed_off(pb);
150 break;
151 case AVAHI_RESOLVER_FOUND:
153 purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n",
154 name, account, bb);
156 /* create a buddy record */
157 if (bb == NULL)
158 bb = bonjour_buddy_new(name, account);
159 b_impl = bb->mdns_impl_data;
161 /* If we're reusing an existing buddy, it may be a new resolver or an existing one. */
162 res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver);
163 if (res != NULL)
164 rd = res->data;
165 else {
166 rd = g_new0(AvahiSvcResolverData, 1);
167 rd->resolver = r;
168 rd->interface = interface;
169 rd->protocol = protocol;
170 rd->name = g_strdup(name);
171 rd->type = g_strdup(type);
172 rd->domain = g_strdup(domain);
174 b_impl->resolvers = g_slist_prepend(b_impl->resolvers, rd);
178 /* Get the ip as a string */
179 ip[0] = '\0';
180 avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a);
182 if (protocol == AVAHI_PROTO_INET6)
183 append_iface_if_linklocal(ip, interface);
185 purple_debug_info("bonjour", "_resolve_callback - name:%s ip:%s prev_ip:%s\n",
186 name, ip, rd->ip);
188 if (rd->ip == NULL || !purple_strequal(rd->ip, ip)) {
189 /* We store duplicates in bb->ips, so we always remove the one */
190 if (rd->ip != NULL) {
191 bb->ips = g_slist_remove(bb->ips, rd->ip);
192 g_free((gchar *) rd->ip);
194 /* IPv6 goes at the front of the list and IPv4 at the end so that we "prefer" IPv6, if present */
195 if (protocol == AVAHI_PROTO_INET6) {
196 rd->ip = g_strdup_printf("%s", ip);
197 bb->ips = g_slist_prepend(bb->ips, (gchar *) rd->ip);
198 } else {
199 rd->ip = g_strdup(ip);
200 bb->ips = g_slist_append(bb->ips, (gchar *) rd->ip);
204 bb->port_p2pj = port;
206 /* Obtain the parameters from the text_record */
207 clear_bonjour_buddy_values(bb);
208 for(l = txt; l != NULL; l = l->next) {
209 if (avahi_string_list_get_pair(l, &key, &value, &size) < 0)
210 continue;
211 set_bonjour_buddy_value(bb, key, value, size);
212 /* TODO: Since we're using the glib allocator, I think we
213 * can use the values instead of re-copying them */
214 avahi_free(key);
215 avahi_free(value);
218 if (!bonjour_buddy_check(bb)) {
219 b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
220 _cleanup_resolver_data(rd);
221 /* If this was the last resolver, remove the buddy */
222 if (b_impl->resolvers == NULL) {
223 if (pb != NULL)
224 bonjour_buddy_signed_off(pb);
225 else
226 bonjour_buddy_delete(bb);
228 } else
229 /* Add or update the buddy in our buddy list */
230 bonjour_buddy_add_to_purple(bb, pb);
232 break;
233 default:
234 purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event);
239 static void
240 _browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface,
241 AvahiProtocol protocol, AvahiBrowserEvent event,
242 const char *name, const char *type, const char *domain,
243 AvahiLookupResultFlags flags, void *userdata) {
245 PurpleAccount *account = userdata;
246 PurpleBuddy *pb = NULL;
248 switch (event) {
249 case AVAHI_BROWSER_FAILURE:
250 purple_debug_error("bonjour", "_browser_callback - Failure: %s\n",
251 avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
252 /* TODO: This is an error that should be handled. */
253 break;
254 case AVAHI_BROWSER_NEW:
255 /* A new peer has joined the network and uses iChat bonjour */
256 purple_debug_info("bonjour", "_browser_callback - new service\n");
257 /* Make sure it isn't us */
258 if (purple_utf8_strcasecmp(name, bonjour_get_jid(account)) != 0) {
259 if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
260 interface, protocol, name, type, domain, protocol,
261 0, _resolver_callback, account)) {
262 purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n",
263 avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
266 break;
267 case AVAHI_BROWSER_REMOVE:
268 purple_debug_info("bonjour", "_browser_callback - Remove service\n");
269 pb = purple_blist_find_buddy(account, name);
270 if (pb != NULL) {
271 BonjourBuddy *bb = purple_buddy_get_protocol_data(pb);
272 AvahiBuddyImplData *b_impl;
273 GSList *l;
274 AvahiSvcResolverData *rd_search;
276 g_return_if_fail(bb != NULL);
278 b_impl = bb->mdns_impl_data;
280 /* There may be multiple presences, we should only get rid of this one */
282 rd_search = g_new0(AvahiSvcResolverData, 1);
283 rd_search->interface = interface;
284 rd_search->protocol = protocol;
285 rd_search->name = (gchar *) name;
286 rd_search->type = (gchar *) type;
287 rd_search->domain = (gchar *) domain;
289 l = g_slist_find_custom(b_impl->resolvers, rd_search, _find_resolver_data);
291 g_free(rd_search);
293 if (l != NULL) {
294 AvahiSvcResolverData *rd = l->data;
295 b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd);
296 /* This IP is no longer available */
297 if (rd->ip != NULL) {
298 bb->ips = g_slist_remove(bb->ips, rd->ip);
299 g_free((gchar *) rd->ip);
301 _cleanup_resolver_data(rd);
303 /* If this was the last resolver, remove the buddy */
304 if (b_impl->resolvers == NULL)
305 bonjour_buddy_signed_off(pb);
308 break;
309 case AVAHI_BROWSER_ALL_FOR_NOW:
310 case AVAHI_BROWSER_CACHE_EXHAUSTED:
311 break;
312 default:
313 purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event);
317 static void
318 _buddy_icon_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
319 BonjourDnsSd *data = userdata;
320 AvahiSessionImplData *idata = data->mdns_impl_data;
322 g_return_if_fail(g == idata->buddy_icon_group || idata->buddy_icon_group == NULL);
324 switch(state) {
325 case AVAHI_ENTRY_GROUP_ESTABLISHED:
326 purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
327 break;
328 case AVAHI_ENTRY_GROUP_COLLISION:
329 purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
330 break;
331 case AVAHI_ENTRY_GROUP_FAILURE:
332 purple_debug_error("bonjour", "Error registering buddy icon data: %s.\n",
333 avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
334 break;
335 case AVAHI_ENTRY_GROUP_UNCOMMITED:
336 case AVAHI_ENTRY_GROUP_REGISTERING:
337 break;
342 static void
343 _entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
344 AvahiSessionImplData *idata = userdata;
346 g_return_if_fail(g == idata->group || idata->group == NULL);
348 switch(state) {
349 case AVAHI_ENTRY_GROUP_ESTABLISHED:
350 purple_debug_info("bonjour", "Successfully registered service.\n");
351 break;
352 case AVAHI_ENTRY_GROUP_COLLISION:
353 purple_debug_error("bonjour", "Collision registering entry group.\n");
354 /* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/
355 break;
356 case AVAHI_ENTRY_GROUP_FAILURE:
357 purple_debug_error("bonjour", "Error registering entry group: %s\n.",
358 avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
359 /* TODO: Handle error - this should log out the account.*/
360 break;
361 case AVAHI_ENTRY_GROUP_UNCOMMITED:
362 case AVAHI_ENTRY_GROUP_REGISTERING:
363 break;
368 static void
369 _buddy_icon_record_cb(AvahiRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
370 AvahiBrowserEvent event, const char *name, uint16_t clazz, uint16_t type,
371 const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) {
372 BonjourBuddy *buddy = userdata;
373 AvahiBuddyImplData *idata = buddy->mdns_impl_data;
375 switch (event) {
376 case AVAHI_BROWSER_CACHE_EXHAUSTED:
377 case AVAHI_BROWSER_ALL_FOR_NOW:
378 /* Ignore these "meta" informational events */
379 return;
380 case AVAHI_BROWSER_NEW:
381 bonjour_buddy_got_buddy_icon(buddy, rdata, size);
382 break;
383 case AVAHI_BROWSER_REMOVE:
384 case AVAHI_BROWSER_FAILURE:
385 purple_debug_error("bonjour", "Error retrieving buddy icon record: %s\n",
386 avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
387 break;
390 /* Stop listening */
391 avahi_record_browser_free(b);
392 if (idata->buddy_icon_rec_browser == b) {
393 idata->buddy_icon_rec_browser = NULL;
397 /****************************
398 * mdns_interface functions *
399 ****************************/
401 gboolean _mdns_init_session(BonjourDnsSd *data) {
402 AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1);
403 const AvahiPoll *poll_api;
404 int error;
406 /* Tell avahi to use g_malloc and g_free */
407 avahi_set_allocator (avahi_glib_allocator ());
409 /* This currently depends on the glib mainloop,
410 * we should make it use the libpurple abstraction */
412 idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT);
414 poll_api = avahi_glib_poll_get(idata->glib_poll);
416 idata->client = avahi_client_new(poll_api, 0, NULL, data, &error);
418 if (idata->client == NULL) {
419 purple_debug_error("bonjour", "Error initializing Avahi: %s\n", avahi_strerror(error));
420 avahi_glib_poll_free(idata->glib_poll);
421 g_free(idata);
422 return FALSE;
425 data->mdns_impl_data = idata;
427 bonjour_dns_sd_set_jid(data->account, avahi_client_get_host_name(idata->client));
429 return TRUE;
432 gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
433 int publish_result = 0;
434 AvahiSessionImplData *idata = data->mdns_impl_data;
435 AvahiStringList *lst = NULL;
437 g_return_val_if_fail(idata != NULL, FALSE);
439 if (!idata->group) {
440 idata->group = avahi_entry_group_new(idata->client,
441 _entry_group_cb, idata);
442 if (!idata->group) {
443 purple_debug_error("bonjour",
444 "Unable to initialize the data for the mDNS (%s).\n",
445 avahi_strerror(avahi_client_errno(idata->client)));
446 return FALSE;
450 while (records) {
451 PurpleKeyValuePair *kvp = records->data;
452 lst = avahi_string_list_add_pair(lst, kvp->key, kvp->value);
453 records = records->next;
456 /* Publish the service */
457 switch (type) {
458 case PUBLISH_START:
459 publish_result = avahi_entry_group_add_service_strlst(
460 idata->group, AVAHI_IF_UNSPEC,
461 AVAHI_PROTO_UNSPEC, 0,
462 bonjour_get_jid(data->account),
463 LINK_LOCAL_RECORD_NAME, NULL, NULL, data->port_p2pj, lst);
464 break;
465 case PUBLISH_UPDATE:
466 publish_result = avahi_entry_group_update_service_txt_strlst(
467 idata->group, AVAHI_IF_UNSPEC,
468 AVAHI_PROTO_UNSPEC, 0,
469 bonjour_get_jid(data->account),
470 LINK_LOCAL_RECORD_NAME, NULL, lst);
471 break;
474 /* Free the memory used by temp data */
475 avahi_string_list_free(lst);
477 if (publish_result < 0) {
478 purple_debug_error("bonjour",
479 "Failed to add the " LINK_LOCAL_RECORD_NAME " service. Error: %s\n",
480 avahi_strerror(publish_result));
481 return FALSE;
484 if (type == PUBLISH_START
485 && (publish_result = avahi_entry_group_commit(idata->group)) < 0) {
486 purple_debug_error("bonjour",
487 "Failed to commit " LINK_LOCAL_RECORD_NAME " service. Error: %s\n",
488 avahi_strerror(publish_result));
489 return FALSE;
492 return TRUE;
495 gboolean _mdns_browse(BonjourDnsSd *data) {
496 AvahiSessionImplData *idata = data->mdns_impl_data;
498 g_return_val_if_fail(idata != NULL, FALSE);
500 idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, LINK_LOCAL_RECORD_NAME, NULL, 0, _browser_callback, data->account);
501 if (!idata->sb) {
503 purple_debug_error("bonjour",
504 "Unable to initialize service browser. Error: %s.\n",
505 avahi_strerror(avahi_client_errno(idata->client)));
506 return FALSE;
509 return TRUE;
512 gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
513 AvahiSessionImplData *idata = data->mdns_impl_data;
515 if (idata == NULL || idata->client == NULL)
516 return FALSE;
518 if (avatar_data != NULL) {
519 gboolean new_group = FALSE;
520 gchar *svc_name;
521 int ret;
522 AvahiPublishFlags flags = 0;
524 if (idata->buddy_icon_group == NULL) {
525 purple_debug_info("bonjour", "Setting new buddy icon.\n");
526 new_group = TRUE;
528 idata->buddy_icon_group = avahi_entry_group_new(idata->client,
529 _buddy_icon_group_cb, data);
530 } else {
531 purple_debug_info("bonjour", "Updating existing buddy icon.\n");
532 flags |= AVAHI_PUBLISH_UPDATE;
535 if (idata->buddy_icon_group == NULL) {
536 purple_debug_error("bonjour",
537 "Unable to initialize the buddy icon group (%s).\n",
538 avahi_strerror(avahi_client_errno(idata->client)));
539 return FALSE;
542 svc_name = g_strdup_printf("%s." LINK_LOCAL_RECORD_NAME "local",
543 bonjour_get_jid(data->account));
545 ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
546 AVAHI_PROTO_UNSPEC, flags, svc_name,
547 AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
549 g_free(svc_name);
551 if (ret < 0) {
552 purple_debug_error("bonjour",
553 "Failed to register buddy icon. Error: %s\n", avahi_strerror(ret));
554 if (new_group) {
555 avahi_entry_group_free(idata->buddy_icon_group);
556 idata->buddy_icon_group = NULL;
558 return FALSE;
561 if (new_group && (ret = avahi_entry_group_commit(idata->buddy_icon_group)) < 0) {
562 purple_debug_error("bonjour",
563 "Failed to commit buddy icon group. Error: %s\n", avahi_strerror(ret));
564 avahi_entry_group_free(idata->buddy_icon_group);
565 idata->buddy_icon_group = NULL;
566 return FALSE;
568 } else if (idata->buddy_icon_group != NULL) {
569 purple_debug_info("bonjour", "Removing existing buddy icon.\n");
570 avahi_entry_group_free(idata->buddy_icon_group);
571 idata->buddy_icon_group = NULL;
574 return TRUE;
577 void _mdns_stop(BonjourDnsSd *data) {
578 AvahiSessionImplData *idata = data->mdns_impl_data;
580 if (idata == NULL || idata->client == NULL)
581 return;
583 if (idata->sb != NULL)
584 avahi_service_browser_free(idata->sb);
586 avahi_client_free(idata->client);
587 avahi_glib_poll_free(idata->glib_poll);
589 g_free(idata);
591 data->mdns_impl_data = NULL;
594 void _mdns_init_buddy(BonjourBuddy *buddy) {
595 buddy->mdns_impl_data = g_new0(AvahiBuddyImplData, 1);
598 void _mdns_delete_buddy(BonjourBuddy *buddy) {
599 AvahiBuddyImplData *idata = buddy->mdns_impl_data;
601 g_return_if_fail(idata != NULL);
603 if (idata->buddy_icon_rec_browser != NULL)
604 avahi_record_browser_free(idata->buddy_icon_rec_browser);
606 g_slist_free_full(idata->resolvers, (GDestroyNotify)_cleanup_resolver_data);
608 g_free(idata);
610 buddy->mdns_impl_data = NULL;
613 void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
614 PurpleConnection *conn = purple_account_get_connection(buddy->account);
615 BonjourData *bd = purple_connection_get_protocol_data(conn);
616 AvahiSessionImplData *session_idata = bd->dns_sd_data->mdns_impl_data;
617 AvahiBuddyImplData *idata = buddy->mdns_impl_data;
618 gchar *name;
620 g_return_if_fail(idata != NULL);
622 if (idata->buddy_icon_rec_browser != NULL)
623 avahi_record_browser_free(idata->buddy_icon_rec_browser);
625 purple_debug_info("bonjour", "Retrieving buddy icon for '%s'.\n", buddy->name);
627 name = g_strdup_printf("%s." LINK_LOCAL_RECORD_NAME "local", buddy->name);
628 idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
629 AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
630 _buddy_icon_record_cb, buddy);
631 g_free(name);
633 if (!idata->buddy_icon_rec_browser) {
634 purple_debug_error("bonjour",
635 "Unable to initialize buddy icon record browser. Error: %s.\n",
636 avahi_strerror(avahi_client_errno(session_idata->client)));