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.
20 #include "mdns_interface.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
41 /* data used by avahi bonjour implementation */
44 AvahiGLibPoll
*glib_poll
;
45 AvahiServiceBrowser
*sb
;
46 AvahiEntryGroup
*group
;
47 AvahiEntryGroup
*buddy_icon_group
;
48 } AvahiSessionImplData
;
51 AvahiServiceResolver
*resolver
;
52 AvahiIfIndex interface
;
53 AvahiProtocol protocol
;
57 /* This is a reference to the entry in BonjourBuddy->ips */
59 } AvahiSvcResolverData
;
63 AvahiRecordBrowser
*buddy_icon_rec_browser
;
67 _find_resolver_data(gconstpointer a
, gconstpointer b
) {
68 const AvahiSvcResolverData
*rd_a
= a
;
69 const AvahiSvcResolverData
*rd_b
= b
;
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
)) {
84 _find_resolver_data_by_resolver(gconstpointer a
, gconstpointer b
) {
85 const AvahiSvcResolverData
*rd_a
= a
;
86 const AvahiServiceResolver
*resolver
= b
;
89 if(rd_a
->resolver
== resolver
)
96 _cleanup_resolver_data(AvahiSvcResolverData
*rd
) {
98 avahi_service_resolver_free(rd
->resolver
);
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
) {
114 PurpleAccount
*account
= userdata
;
118 char ip
[AVAHI_ADDRESS_STR_MAX
];
119 AvahiBuddyImplData
*b_impl
;
120 AvahiSvcResolverData
*rd
;
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
;
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
);
135 b_impl
= bb
->mdns_impl_data
;
136 res
= g_slist_find_custom(b_impl
->resolvers
, r
, _find_resolver_data_by_resolver
);
139 b_impl
->resolvers
= g_slist_delete_link(b_impl
->resolvers
, res
);
141 /* We've already freed the resolver */
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
);
151 case AVAHI_RESOLVER_FOUND
:
153 purple_debug_info("bonjour", "_resolve_callback - name:%s account:%p bb:%p\n",
156 /* create a buddy record */
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
);
166 rd
= g_new0(AvahiSvcResolverData
, 1);
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 */
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",
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
);
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)
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 */
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
) {
224 bonjour_buddy_signed_off(pb
);
226 bonjour_buddy_delete(bb
);
229 /* Add or update the buddy in our buddy list */
230 bonjour_buddy_add_to_purple(bb
, pb
);
234 purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event
);
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
;
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. */
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
))));
267 case AVAHI_BROWSER_REMOVE
:
268 purple_debug_info("bonjour", "_browser_callback - Remove service\n");
269 pb
= purple_blist_find_buddy(account
, name
);
271 BonjourBuddy
*bb
= purple_buddy_get_protocol_data(pb
);
272 AvahiBuddyImplData
*b_impl
;
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
);
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
);
309 case AVAHI_BROWSER_ALL_FOR_NOW
:
310 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
313 purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event
);
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
);
325 case AVAHI_ENTRY_GROUP_ESTABLISHED
:
326 purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
328 case AVAHI_ENTRY_GROUP_COLLISION
:
329 purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
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
))));
335 case AVAHI_ENTRY_GROUP_UNCOMMITED
:
336 case AVAHI_ENTRY_GROUP_REGISTERING
:
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
);
349 case AVAHI_ENTRY_GROUP_ESTABLISHED
:
350 purple_debug_info("bonjour", "Successfully registered service.\n");
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")*/
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.*/
361 case AVAHI_ENTRY_GROUP_UNCOMMITED
:
362 case AVAHI_ENTRY_GROUP_REGISTERING
:
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
;
376 case AVAHI_BROWSER_CACHE_EXHAUSTED
:
377 case AVAHI_BROWSER_ALL_FOR_NOW
:
378 /* Ignore these "meta" informational events */
380 case AVAHI_BROWSER_NEW
:
381 bonjour_buddy_got_buddy_icon(buddy
, rdata
, size
);
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
))));
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
;
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
);
425 data
->mdns_impl_data
= idata
;
427 bonjour_dns_sd_set_jid(data
->account
, avahi_client_get_host_name(idata
->client
));
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
);
440 idata
->group
= avahi_entry_group_new(idata
->client
,
441 _entry_group_cb
, idata
);
443 purple_debug_error("bonjour",
444 "Unable to initialize the data for the mDNS (%s).\n",
445 avahi_strerror(avahi_client_errno(idata
->client
)));
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 */
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
);
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
);
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
));
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
));
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
);
503 purple_debug_error("bonjour",
504 "Unable to initialize service browser. Error: %s.\n",
505 avahi_strerror(avahi_client_errno(idata
->client
)));
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
)
518 if (avatar_data
!= NULL
) {
519 gboolean new_group
= FALSE
;
522 AvahiPublishFlags flags
= 0;
524 if (idata
->buddy_icon_group
== NULL
) {
525 purple_debug_info("bonjour", "Setting new buddy icon.\n");
528 idata
->buddy_icon_group
= avahi_entry_group_new(idata
->client
,
529 _buddy_icon_group_cb
, data
);
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
)));
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
);
552 purple_debug_error("bonjour",
553 "Failed to register buddy icon. Error: %s\n", avahi_strerror(ret
));
555 avahi_entry_group_free(idata
->buddy_icon_group
);
556 idata
->buddy_icon_group
= NULL
;
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
;
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
;
577 void _mdns_stop(BonjourDnsSd
*data
) {
578 AvahiSessionImplData
*idata
= data
->mdns_impl_data
;
580 if (idata
== NULL
|| idata
->client
== NULL
)
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
);
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
);
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
;
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
);
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
)));