/* * avahi-contact.c - Source for SalutAvahiContact * Copyright (C) 2008 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "avahi-contact.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG DEBUG_MUC #include "debug.h" #define DEBUG_CONTACT(contact, format, ...) G_STMT_START { \ DEBUG ("Contact %s: " format, \ SALUT_CONTACT (contact)->name, ##__VA_ARGS__); \ } G_STMT_END #define DEBUG_RESOLVER(contact, resolver, format, ...) G_STMT_START { \ gchar *_name; \ gchar *_type; \ gint _interface; \ gint _protocol; \ \ g_object_get (G_OBJECT(resolver), \ "name", &_name, \ "type", &_type, \ "interface", &_interface, \ "protocol", &_protocol, \ NULL \ ); \ \ DEBUG_CONTACT (contact, "Resolver (%s %s intf: %d proto: %d): " format, \ _name, _type, _interface, _protocol, ##__VA_ARGS__); \ \ g_free (_name); \ g_free (_type); \ } G_STMT_END #define PRESENCE_TIMEOUT (1200 * 1000) G_DEFINE_TYPE (SalutAvahiContact, salut_avahi_contact, SALUT_TYPE_CONTACT); /* properties */ enum { PROP_CLIENT = 1, LAST_PROP }; /* private structure */ typedef struct _SalutAvahiContactPrivate SalutAvahiContactPrivate; struct _SalutAvahiContactPrivate { SalutAvahiDiscoveryClient *discovery_client; GSList *resolvers; guint presence_resolver_failed_timer; GaRecordBrowser *record_browser; gboolean dispose_has_run; }; static void salut_avahi_contact_init (SalutAvahiContact *self) { SalutAvahiContactPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SALUT_TYPE_AVAHI_CONTACT, SalutAvahiContactPrivate); self->priv = priv; priv->resolvers = NULL; } static void salut_avahi_contact_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object); SalutAvahiContactPrivate *priv = self->priv; switch (property_id) { case PROP_CLIENT: g_value_set_object (value, priv->discovery_client); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void salut_avahi_contact_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object); SalutAvahiContactPrivate *priv = self->priv; switch (property_id) { case PROP_CLIENT: priv->discovery_client = g_value_get_object (value); g_object_ref (priv->discovery_client); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void _avahi_address_to_sockaddr (AvahiAddress *address, guint16 port, AvahiIfIndex index_, struct sockaddr *sockaddr) { switch (address->proto) { case AVAHI_PROTO_INET: { struct sockaddr_in *sockaddr4 = (struct sockaddr_in *) sockaddr; sockaddr4->sin_family = AF_INET; sockaddr4->sin_port = htons (port); /* ->address is already in network byte order */ sockaddr4->sin_addr.s_addr = address->data.ipv4.address; break; } case AVAHI_PROTO_INET6: { struct sockaddr_in6 *sockaddr6 = (struct sockaddr_in6 *) sockaddr; sockaddr6->sin6_family = AF_INET6; sockaddr6->sin6_port = htons (port); memcpy (sockaddr6->sin6_addr.s6_addr, address->data.ipv6.address, 16); sockaddr6->sin6_flowinfo = 0; sockaddr6->sin6_scope_id = index_; break; } default: g_assert_not_reached (); } } static void _add_address (GaServiceResolver *resolver, GArray *addresses) { salut_contact_address_t s_address; AvahiAddress address; guint16 port; AvahiIfIndex ifindex; g_object_get (resolver, "interface", &ifindex, NULL); if (ga_service_resolver_get_address (resolver, &address, &port)) { _avahi_address_to_sockaddr (&address, port, ifindex, (struct sockaddr *) &s_address.address); g_array_append_val (addresses, s_address); } } static GArray * salut_avahi_contact_get_addresses (SalutContact *contact) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact); SalutAvahiContactPrivate *priv = self->priv; GArray *addresses; addresses = g_array_sized_new (TRUE, TRUE, sizeof (salut_contact_address_t), g_slist_length (priv->resolvers)); g_slist_foreach (priv->resolvers, (GFunc) _add_address, addresses); return addresses; } static gchar * salut_avahi_contact_dup_jid (WockyContact *contact) { SalutContact *self = SALUT_CONTACT (contact); return g_strdup (self->name); } static GList * salut_avahi_contact_ll_get_addresses (WockyLLContact *contact) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact); SalutAvahiContactPrivate *priv = self->priv; /* omg, GQueue! */ GQueue queue = G_QUEUE_INIT; GSList *l; for (l = priv->resolvers; l != NULL; l = l->next) { GaServiceResolver *resolver = l->data; AvahiAddress address; guint16 port; if (ga_service_resolver_get_address (resolver, &address, &port)) { GInetAddress *addr; GSocketAddress *socket_address; if (address.proto == AVAHI_PROTO_INET) { addr = g_inet_address_new_from_bytes ( (guint8 *) &(address.data.ipv4.address), G_SOCKET_FAMILY_IPV4); } else if (address.proto == AVAHI_PROTO_INET6) { addr = g_inet_address_new_from_bytes ( (guint8 *) &(address.data.ipv6.address), G_SOCKET_FAMILY_IPV6); } else g_assert_not_reached (); socket_address = g_inet_socket_address_new (addr, port); g_object_unref (addr); g_queue_push_tail (&queue, socket_address); } } return queue.head; } static gint _compare_address (GaServiceResolver *resolver, struct sockaddr *addr_b) { union { struct sockaddr_storage storage; struct sockaddr_in in; struct sockaddr_in6 in6; } addr_a; AvahiIfIndex ifindex; AvahiAddress address; uint16_t port; g_object_get (resolver, "interface", &ifindex, NULL); if (!ga_service_resolver_get_address (resolver, &address, &port)) return -1; _avahi_address_to_sockaddr (&address, port, ifindex, (struct sockaddr *) &addr_a); if (addr_a.storage.ss_family != addr_b->sa_family) return -1; switch (addr_a.storage.ss_family) { case AF_INET: { struct sockaddr_in *b4 = (struct sockaddr_in *) addr_b; return b4->sin_addr.s_addr - addr_a.in.sin_addr.s_addr; } case AF_INET6: { struct sockaddr_in6 *b6 = (struct sockaddr_in6 *) addr_b; /* FIXME should we compare the scope_id too ? */ return memcmp (addr_a.in6.sin6_addr.s6_addr, b6->sin6_addr.s6_addr, 16); } default: g_assert_not_reached (); } return 0; } static gboolean salut_avahi_contact_has_address (SalutContact *contact, struct sockaddr *address, guint size) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact); SalutAvahiContactPrivate *priv = self->priv; return (g_slist_find_custom (priv->resolvers, address, (GCompareFunc) _compare_address) != NULL); } static void salut_avahi_contact_avatar_request_flush (SalutAvahiContact *self, guint8 *data, gsize size) { SalutAvahiContactPrivate *priv = self->priv; if (priv->record_browser != NULL) g_object_unref (priv->record_browser); priv->record_browser = NULL; salut_contact_avatar_request_flush (SALUT_CONTACT (self), data, size); } static void salut_contact_avatar_all_for_now (GaRecordBrowser *browser, SalutAvahiContact *self) { DEBUG ("All for now for resolving %s's record", SALUT_CONTACT (self)->name); salut_avahi_contact_avatar_request_flush (self, NULL, 0); } static void salut_contact_avatar_failure (GaRecordBrowser *browser, GError *error, SalutAvahiContact *self) { DEBUG ("Resolving record for %s failed: %s", SALUT_CONTACT(self)->name, error->message); salut_avahi_contact_avatar_request_flush (self, NULL, 0); } static void salut_contact_avatar_found (GaRecordBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, gchar *name, guint16 clazz, guint16 type, guint8 *rdata, gsize rdata_size, AvahiLookupFlags flags, SalutAvahiContact *self) { DEBUG ("Found avatar for %s for size %" G_GSIZE_FORMAT, SALUT_CONTACT (self)->name, rdata_size); if (rdata_size <= 0) salut_avahi_contact_avatar_request_flush (self, NULL, 0); else salut_avahi_contact_avatar_request_flush (self, rdata, rdata_size); } static void salut_avahi_contact_retrieve_avatar (SalutContact *contact) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (contact); SalutAvahiContactPrivate *priv = self->priv; gchar *name; GError *error = NULL; const gchar *dnssd_name; if (priv->record_browser != NULL) { g_object_unref (priv->record_browser); priv->record_browser = NULL; } if (contact->avatar_token == NULL) { salut_avahi_contact_avatar_request_flush (self, NULL, 0); return; } dnssd_name = salut_avahi_discovery_client_get_dnssd_name ( priv->discovery_client); name = g_strdup_printf ("%s.%s.local", contact->name, dnssd_name); priv->record_browser = ga_record_browser_new (name, 0xA); g_free (name); g_signal_connect (priv->record_browser, "new-record", G_CALLBACK (salut_contact_avatar_found), contact); g_signal_connect (priv->record_browser, "all-for-now", G_CALLBACK (salut_contact_avatar_all_for_now), contact); g_signal_connect (priv->record_browser, "failure", G_CALLBACK (salut_contact_avatar_failure), contact); if (!ga_record_browser_attach (priv->record_browser, priv->discovery_client->avahi_client, &error)) { DEBUG ("browser attached failed: %s", error->message); g_error_free (error); salut_avahi_contact_avatar_request_flush (self, NULL, 0); } } static void salut_avahi_contact_dispose (GObject *object); static void salut_avahi_contact_class_init ( SalutAvahiContactClass *salut_avahi_contact_class) { GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_contact_class); SalutContactClass *contact_class = SALUT_CONTACT_CLASS ( salut_avahi_contact_class); WockyContactClass *w_contact_class = WOCKY_CONTACT_CLASS ( salut_avahi_contact_class); WockyLLContactClass *ll_contact_class = WOCKY_LL_CONTACT_CLASS ( salut_avahi_contact_class); GParamSpec *param_spec; g_type_class_add_private (salut_avahi_contact_class, sizeof (SalutAvahiContactPrivate)); object_class->get_property = salut_avahi_contact_get_property; object_class->set_property = salut_avahi_contact_set_property; object_class->dispose = salut_avahi_contact_dispose; contact_class->get_addresses = salut_avahi_contact_get_addresses; contact_class->has_address = salut_avahi_contact_has_address; contact_class->retrieve_avatar = salut_avahi_contact_retrieve_avatar; w_contact_class->dup_jid = salut_avahi_contact_dup_jid; ll_contact_class->get_addresses = salut_avahi_contact_ll_get_addresses; param_spec = g_param_spec_object ( "discovery-client", "SalutAvahiDiscoveryClient object", "The Salut Avahi Discovery client associated with this muc manager", SALUT_TYPE_AVAHI_DISCOVERY_CLIENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CLIENT, param_spec); } void salut_avahi_contact_dispose (GObject *object) { SalutAvahiContact *self = SALUT_AVAHI_CONTACT (object); SalutAvahiContactPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->presence_resolver_failed_timer > 0) { g_source_remove (priv->presence_resolver_failed_timer); priv->presence_resolver_failed_timer = 0; } g_slist_foreach (priv->resolvers, (GFunc) g_object_unref, NULL); g_slist_free (priv->resolvers); priv->resolvers = NULL; if (priv->discovery_client != NULL) { g_object_unref (priv->discovery_client); priv->discovery_client = NULL; } if (G_OBJECT_CLASS (salut_avahi_contact_parent_class)->dispose) G_OBJECT_CLASS (salut_avahi_contact_parent_class)->dispose (object); } /* public functions */ SalutAvahiContact * salut_avahi_contact_new (SalutConnection *connection, const gchar *name, SalutAvahiDiscoveryClient *discovery_client) { g_assert (connection != NULL); g_assert (name != NULL); g_assert (discovery_client != NULL); return g_object_new (SALUT_TYPE_AVAHI_CONTACT, "connection", connection, "name", name, "discovery-client", discovery_client, NULL); } static void contact_drop_resolver (SalutAvahiContact *self, GaServiceResolver *resolver) { SalutAvahiContactPrivate *priv = self->priv; SalutContact *contact = SALUT_CONTACT (self); gint resolvers_left; priv->resolvers = g_slist_remove (priv->resolvers, resolver); resolvers_left = g_slist_length (priv->resolvers); DEBUG_RESOLVER (self, resolver, "removed, %d left for %s", resolvers_left, SALUT_CONTACT (self)->name); g_object_unref (resolver); if (resolvers_left == 0) { salut_contact_lost (contact); } } struct resolverinfo { AvahiIfIndex interface; AvahiProtocol protocol; const gchar *name; const gchar *type; const gchar *domain; }; static gint compare_resolver (GaServiceResolver *a, struct resolverinfo *info) { AvahiIfIndex interface; AvahiProtocol protocol; gchar *name; gchar *type; gchar *domain; gint result; g_object_get (a, "interface", &interface, "protocol", &protocol, "name", &name, "type", &type, "domain", &domain, NULL); if (interface == info->interface && protocol == info->protocol && !tp_strdiff (name, info->name) && !tp_strdiff (type, info->type) && !tp_strdiff (domain, info->domain)) result = 0; else result = 1; g_free (name); g_free (type); g_free (domain); return result; } static GaServiceResolver * find_resolver (SalutAvahiContact *contact, AvahiIfIndex interface, AvahiProtocol protocol, const gchar *name, const gchar *type, const gchar *domain) { SalutAvahiContactPrivate *priv = contact->priv; struct resolverinfo info; GSList *ret; info.interface = interface; info.protocol = protocol; info.name = name; info.type = type; info.domain = domain; ret = g_slist_find_custom (priv->resolvers, &info, (GCompareFunc) compare_resolver); return ret ? GA_SERVICE_RESOLVER (ret->data) : NULL; } static void update_alias (SalutAvahiContact *self, const gchar *nick) { SalutContact *contact = SALUT_CONTACT (self); if (!tp_str_empty (nick)) { salut_contact_change_alias (contact, nick); return; } if (!tp_str_empty (contact->full_name)) { salut_contact_change_alias (contact, contact->full_name); return; } salut_contact_change_alias (contact, NULL); } /* Returned string needs to be freed with avahi_free ! */ static char * _avahi_txt_get_keyval_with_size (AvahiStringList *txt, const gchar *key, gsize *size) { AvahiStringList *t; gchar *s = NULL; if ((t = avahi_string_list_find (txt, key)) == NULL) return NULL; avahi_string_list_get_pair (t, NULL, &s, size); return s; } static char * _avahi_txt_get_keyval (AvahiStringList *txt, const gchar *key) { return _avahi_txt_get_keyval_with_size (txt, key, NULL); } static void contact_resolved_cb (GaServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, gchar *name, gchar *type, gchar *domain, gchar *host_name, AvahiAddress *address, gint port, AvahiStringList *txt, AvahiLookupResultFlags flags, SalutAvahiContact *self) { SalutAvahiContactPrivate *priv = self->priv; SalutContact *contact = SALUT_CONTACT (self); char *s; char *nick, *first, *last; /* node, hash and ver as defined by XEP-0115 */ char *node, *hash, *ver; #ifdef ENABLE_OLPC char *activity_id, *room_id; char *olpc_key_part; gsize size; #endif DEBUG_RESOLVER (self, resolver, "contact %s resolved", contact->name); if (priv->presence_resolver_failed_timer != 0) { DEBUG_CONTACT (self, "remove presence resolver timer"); g_source_remove (priv->presence_resolver_failed_timer); priv->presence_resolver_failed_timer = 0; } salut_contact_freeze (contact); /* status */ if ((s = _avahi_txt_get_keyval (txt, "status")) != NULL) { int i; for (i = 0; i < SALUT_PRESENCE_NR_PRESENCES ; i++) { if (!tp_strdiff (s, salut_presence_status_txt_names[i])) { salut_contact_change_status (contact, i); break; } } avahi_free (s); } /* status message */ s = _avahi_txt_get_keyval (txt, "msg"); salut_contact_change_status_message (contact, s); avahi_free (s); /* real name and nick */ nick = _avahi_txt_get_keyval (txt, "nick"); first = _avahi_txt_get_keyval (txt, "1st"); last = _avahi_txt_get_keyval (txt, "last"); salut_contact_change_real_name (contact, first, last); update_alias (self, nick); avahi_free (nick); avahi_free (first); avahi_free (last); /* capabilities */ hash = _avahi_txt_get_keyval (txt, "hash"); node = _avahi_txt_get_keyval (txt, "node"); ver = _avahi_txt_get_keyval (txt, "ver"); salut_contact_change_capabilities (contact, hash, node, ver); avahi_free (node); avahi_free (hash); avahi_free (ver); /* avatar token */ s = _avahi_txt_get_keyval (txt, "phsh"); salut_contact_change_avatar_token (contact, s); avahi_free (s); /* email */ s = _avahi_txt_get_keyval (txt, "email"); salut_contact_change_email (contact, s); avahi_free (s); /* jid */ s = _avahi_txt_get_keyval (txt, "jid"); salut_contact_change_jid (contact, s); avahi_free (s); #ifdef ENABLE_OLPC /* OLPC color */ s = _avahi_txt_get_keyval (txt, "olpc-color"); salut_contact_change_olpc_color (contact, s); avahi_free (s); /* current activity */ activity_id = _avahi_txt_get_keyval (txt, "olpc-current-activity"); room_id = _avahi_txt_get_keyval (txt, "olpc-current-activity-room"); salut_contact_change_current_activity (contact, room_id, activity_id); avahi_free (activity_id); avahi_free (room_id); /* OLPC key */ olpc_key_part = _avahi_txt_get_keyval_with_size (txt, "olpc-key-part0", &size); if (olpc_key_part != NULL) { guint i = 0; gchar *olpc_key_part_name = NULL; GArray *olpc_key; /* FIXME: how big are OLPC keys anyway? */ olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8), 512); do { g_array_append_vals (olpc_key, olpc_key_part, size); avahi_free (olpc_key_part); i++; olpc_key_part_name = g_strdup_printf ("olpc-key-part%u", i); olpc_key_part = _avahi_txt_get_keyval_with_size (txt, olpc_key_part_name, &size); g_free (olpc_key_part_name); } while (olpc_key_part != NULL); salut_contact_change_olpc_key (contact, olpc_key); g_array_unref (olpc_key); } /* address */ if (address != NULL) { gchar* saddr = g_malloc0 (AVAHI_ADDRESS_STR_MAX); if (avahi_address_snprint (saddr, AVAHI_ADDRESS_STR_MAX, address)) { switch (address->proto) { case AVAHI_PROTO_INET: salut_contact_change_ipv4_addr (contact, saddr); break; case AVAHI_PROTO_INET6: salut_contact_change_ipv6_addr (contact, saddr); break; default: break; } } g_free (saddr); } #endif salut_contact_found (contact); salut_contact_thaw (contact); } static gboolean presence_resolver_failed_timeout (SalutAvahiContact *self) { SalutAvahiContactPrivate *priv = self->priv; DEBUG_CONTACT (self, "presence resolver timer expired. Remove contact"); priv->presence_resolver_failed_timer = 0; salut_contact_lost (SALUT_CONTACT (self)); return FALSE; } static void contact_failed_cb (GaServiceResolver *resolver, GError *error, SalutAvahiContact *self) { SalutAvahiContactPrivate *priv = self->priv; if (priv->presence_resolver_failed_timer != 0) /* There is already a timer running */ return; DEBUG_RESOLVER (self, resolver, "failed: %s. Start presence resolver timer", error->message); priv->presence_resolver_failed_timer = g_timeout_add ( PRESENCE_TIMEOUT, (GSourceFunc) presence_resolver_failed_timeout, self); } gboolean salut_avahi_contact_add_service (SalutAvahiContact *self, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) { SalutAvahiContactPrivate *priv = self->priv; GaServiceResolver *resolver; GError *error = NULL; resolver = find_resolver (self, interface, protocol, name, type, domain); if (resolver != NULL) return TRUE; resolver = ga_service_resolver_new (interface, protocol, name, type, domain, protocol, 0); g_signal_connect (resolver, "found", G_CALLBACK (contact_resolved_cb), self); g_signal_connect (resolver, "failure", G_CALLBACK (contact_failed_cb), self); if (!ga_service_resolver_attach (resolver, priv->discovery_client->avahi_client, &error)) { DEBUG_CONTACT(self, "Failed to attach resolver: %s", error->message); g_error_free (error); return FALSE; } DEBUG_RESOLVER (self, resolver, "added"); priv->resolvers = g_slist_prepend (priv->resolvers, resolver); return TRUE; } void salut_avahi_contact_remove_service (SalutAvahiContact *self, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) { GaServiceResolver *resolver; resolver = find_resolver (self, interface, protocol, name, type, domain); if (resolver == NULL) return; DEBUG_RESOLVER (self, resolver, "remove requested"); contact_drop_resolver (self, resolver); } gboolean salut_avahi_contact_has_services (SalutAvahiContact *self) { SalutAvahiContactPrivate *priv = self->priv; return priv->resolvers != NULL; }