/* * gabble-connection.c - Source for GabbleConnection * Copyright (C) 2005-2010 Collabora Ltd. * Copyright (C) 2005-2010 Nokia Corporation * * 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 "connection.h" #include "gabble.h" #include #include #include #include #include #include #include #include #include "extensions/extensions.h" #define DEBUG_FLAG GABBLE_DEBUG_CONNECTION #include #include "bytestream-factory.h" #include "gabble/capabilities.h" #include "gabble/caps-channel-manager.h" #include "gabble/plugin-connection.h" #include "caps-hash.h" #include "auth-manager.h" #include "conn-aliasing.h" #include "conn-avatars.h" #include "conn-client-types.h" #include "conn-contact-info.h" #include "conn-location.h" #include "conn-presence.h" #include "conn-sidecars.h" #include "conn-mail-notif.h" #include "conn-olpc.h" #include "conn-power-saving.h" #include "debug.h" #include "disco.h" #include "im-factory.h" #include "legacy-caps.h" #include "muc-factory.h" #include "namespaces.h" #include "presence-cache.h" #include "presence.h" #include "request-pipeline.h" #include "roomlist-manager.h" #include "roster.h" #include "search-manager.h" #include "server-tls-channel.h" #include "server-tls-manager.h" #include "plugin-loader.h" #include "private-tubes-factory.h" #include "util.h" #include "vcard-manager.h" #include "conn-util.h" #include "conn-addressing.h" #ifdef ENABLE_VOIP #include "media-channel.h" #include "media-factory.h" #endif static guint disco_reply_timeout = 5; #define DISCONNECT_TIMEOUT 5 static void capabilities_service_iface_init (gpointer, gpointer); static void gabble_conn_contact_caps_iface_init (gpointer, gpointer); static void conn_capabilities_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash); static void conn_contact_capabilities_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash); static void gabble_plugin_connection_iface_init ( GabblePluginConnectionInterface *iface, gpointer conn); static gchar *_gabble_plugin_connection_get_full_jid ( GabblePluginConnection *conn); static TpBaseContactList *_gabble_plugin_connection_get_contact_list ( GabblePluginConnection *conn); G_DEFINE_TYPE_WITH_CODE(GabbleConnection, gabble_connection, TP_TYPE_BASE_CONNECTION, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, conn_aliasing_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS, conn_avatars_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_INFO, conn_contact_info_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CAPABILITIES, capabilities_service_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, tp_contacts_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST, tp_base_contact_list_mixin_list_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS, tp_base_contact_list_mixin_groups_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_BLOCKING, tp_base_contact_list_mixin_blocking_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, tp_presence_mixin_simple_presence_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CONNECTION_INTERFACE_GABBLE_DECLOAK, conn_decloak_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_LOCATION, location_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_OLPC_BUDDY_INFO, olpc_buddy_info_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_OLPC_ACTIVITY_PROPERTIES, olpc_activity_properties_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, gabble_conn_contact_caps_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CONNECTION_FUTURE, conn_future_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_MAIL_NOTIFICATION, conn_mail_notif_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CLIENT_TYPES, conn_client_types_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_POWER_SAVING, conn_power_saving_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_SVC_CONNECTION_INTERFACE_ADDRESSING, conn_addressing_iface_init); G_IMPLEMENT_INTERFACE (GABBLE_TYPE_PLUGIN_CONNECTION, gabble_plugin_connection_iface_init); ) /* properties */ enum { PROP_CONNECT_SERVER = 1, PROP_EXPLICIT_SERVER, PROP_PORT, PROP_OLD_SSL, PROP_REQUIRE_ENCRYPTION, PROP_REGISTER, PROP_LOW_BANDWIDTH, PROP_STREAM_SERVER, PROP_USERNAME, PROP_PASSWORD, PROP_RESOURCE, PROP_PRIORITY, PROP_HTTPS_PROXY_SERVER, PROP_HTTPS_PROXY_PORT, PROP_FALLBACK_CONFERENCE_SERVER, PROP_STUN_SERVER, PROP_STUN_PORT, PROP_FALLBACK_STUN_SERVER, PROP_FALLBACK_STUN_PORT, PROP_IGNORE_SSL_ERRORS, PROP_ALIAS, PROP_FALLBACK_SOCKS5_PROXIES, PROP_KEEPALIVE_INTERVAL, PROP_DECLOAK_AUTOMATICALLY, PROP_FALLBACK_SERVERS, PROP_EXTRA_CERTIFICATE_IDENTITIES, PROP_POWER_SAVING, PROP_DOWNLOAD_AT_CONNECTION, LAST_PROPERTY }; /* private structure */ struct _GabbleConnectionPrivate { WockyConnector *connector; WockyPorter *porter; WockyPing *pinger; GCancellable *cancellable; /* connection properties */ gchar *connect_server; gchar *explicit_server; guint port; gboolean old_ssl; gboolean require_encryption; gboolean ignore_ssl_errors; TpConnectionStatusReason ssl_error; gboolean do_register; gboolean low_bandwidth; guint keepalive_interval; gchar *https_proxy_server; guint16 https_proxy_port; gchar *stun_server; guint16 stun_port; gchar *fallback_stun_server; guint16 fallback_stun_port; gchar *fallback_conference_server; GStrv fallback_socks5_proxies; gboolean decloak_automatically; GStrv fallback_servers; guint fallback_server_index; GStrv extra_certificate_identities; gboolean power_saving; /* authentication properties */ gchar *stream_server; gchar *username; gchar *password; gchar *resource; gint8 priority; gchar *alias; /* reference to conference server name */ const gchar *conference_server; /* serial number of current advertised caps */ guint caps_serial; /* Last activity time for XEP-0012 purposes, where "activity" is defined to * mean "sending a message". */ time_t last_activity_time; /* capabilities from various sources: */ /* subscriptions on behalf of the Connection, like PEP "+notify" * namespaces (this one is add-only) */ GabbleCapabilitySet *notify_caps; /* caps provided by Capabilities.AdvertiseCapabilities (tp-spec 0.16) */ GabbleCapabilitySet *legacy_caps; /* additional caps that we advertise until the first call to * AdvertiseCapabilities or UpdateCapabilities, for vague historical * reasons */ GabbleCapabilitySet *bonus_caps; /* sidecar caps set by gabble_connection_update_sidecar_capabilities */ GabbleCapabilitySet *sidecar_caps; /* caps provided via ContactCapabilities.UpdateCapabilities () * gchar * (client name) => GabbleCapabilitySet * */ GHashTable *client_caps; /* the union of the above */ GabbleCapabilitySet *all_caps; /* data forms provided via UpdateCapabilities() * gchar * (client name) => GPtrArray */ GHashTable *client_data_forms; /* auth manager */ GabbleAuthManager *auth_manager; /* server TLS manager */ GabbleServerTLSManager *server_tls_manager; /* IM factory; we need this to add text caps for everyone (even * offline contacts) which is done through the IM factory's * GabbleCapsChannelManagerInterface->get_contact_caps function. */ GabbleImFactory *im_factory; /* stream id returned by the connector */ gchar *stream_id; /* timer used when trying to properly disconnect */ guint disconnect_timer; /* Number of things we are waiting for before changing the connection status * to connected */ guint waiting_connected; /* Used to cancel pending calls to _gabble_connection_send_with_reply(). It * should not be necessary because by the time we get to cancelling this (in * our dispose()) the porter should be long dead and have called back for all * outstanding requests. However... call this paranoia, and a desire to * delete the Loudmouth compatibility layer without spending any more hours * untangling even more old code. */ GCancellable *iq_reply_cancellable; gboolean closing; /* gobject housekeeping */ gboolean dispose_has_run; }; static void connection_capabilities_update_cb (GabblePresenceCache *cache, TpHandle handle, const GabbleCapabilitySet *old_cap_set, const GabbleCapabilitySet *new_cap_set, gpointer user_data); static gboolean gabble_connection_refresh_capabilities (GabbleConnection *self, GabbleCapabilitySet **old_out); static void add_to_array (gpointer data, gpointer user_data) { g_ptr_array_add (user_data, data); } static GPtrArray * _gabble_connection_create_channel_managers (TpBaseConnection *conn) { GabbleConnection *self = GABBLE_CONNECTION (conn); GabblePluginConnection *plugin_connection = GABBLE_PLUGIN_CONNECTION (self); GPtrArray *channel_managers = g_ptr_array_sized_new (10); GabblePluginLoader *loader; GPtrArray *tmp; self->roster = gabble_roster_new (self); g_signal_connect (self->roster, "nicknames-update", G_CALLBACK (gabble_conn_aliasing_nicknames_updated), self); g_ptr_array_add (channel_managers, self->roster); self->priv->im_factory = g_object_new (GABBLE_TYPE_IM_FACTORY, "connection", self, NULL); g_ptr_array_add (channel_managers, self->priv->im_factory); g_ptr_array_add (channel_managers, g_object_new (GABBLE_TYPE_ROOMLIST_MANAGER, "connection", self, NULL)); g_ptr_array_add (channel_managers, g_object_new (GABBLE_TYPE_SEARCH_MANAGER, "connection", self, NULL)); self->priv->auth_manager = g_object_new (GABBLE_TYPE_AUTH_MANAGER, "connection", self, NULL); g_ptr_array_add (channel_managers, self->priv->auth_manager); self->priv->server_tls_manager = g_object_new (GABBLE_TYPE_SERVER_TLS_MANAGER, "connection", self, NULL); g_ptr_array_add (channel_managers, self->priv->server_tls_manager); self->muc_factory = g_object_new (GABBLE_TYPE_MUC_FACTORY, "connection", self, NULL); g_ptr_array_add (channel_managers, self->muc_factory); self->private_tubes_factory = gabble_private_tubes_factory_new (self); g_ptr_array_add (channel_managers, self->private_tubes_factory); #ifdef ENABLE_VOIP self->jingle_mint = gabble_jingle_mint_new (self); g_ptr_array_add (channel_managers, g_object_new (GABBLE_TYPE_MEDIA_FACTORY, "connection", self, NULL)); #endif #ifdef ENABLE_FILE_TRANSFER self->ft_manager = gabble_ft_manager_new (self); g_ptr_array_add (channel_managers, self->ft_manager); #endif /* plugin channel managers */ loader = gabble_plugin_loader_dup (); tmp = gabble_plugin_loader_create_channel_managers (loader, plugin_connection); g_object_unref (loader); g_ptr_array_foreach (tmp, add_to_array, channel_managers); g_ptr_array_unref (tmp); return channel_managers; } static void gabble_plugin_connection_iface_init ( GabblePluginConnectionInterface *iface, gpointer conn) { iface->add_sidecar_own_caps = gabble_connection_add_sidecar_own_caps; iface->add_sidecar_own_caps_full= gabble_connection_add_sidecar_own_caps_full; iface->get_session = gabble_connection_get_session; iface->get_full_jid = _gabble_plugin_connection_get_full_jid; iface->get_jid_for_caps = gabble_connection_get_jid_for_caps; iface->pick_best_resource_for_caps = gabble_connection_pick_best_resource_for_caps; iface->get_contact_list = _gabble_plugin_connection_get_contact_list; iface->get_caps = gabble_connection_get_caps; } static GObject * gabble_connection_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GabbleConnection *self = GABBLE_CONNECTION ( G_OBJECT_CLASS (gabble_connection_parent_class)->constructor ( type, n_construct_properties, construct_params)); GabbleConnectionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CONNECTION, GabbleConnectionPrivate); TpBaseConnection *base = TP_BASE_CONNECTION (self); DEBUG("Post-construction: (GabbleConnection *)%p", self); tp_base_connection_add_possible_client_interest (base, TP_IFACE_QUARK_CONNECTION_INTERFACE_MAIL_NOTIFICATION); self->req_pipeline = gabble_request_pipeline_new (self); self->disco = gabble_disco_new (self); self->vcard_manager = gabble_vcard_manager_new (self); g_signal_connect (self->vcard_manager, "nickname-update", G_CALLBACK (gabble_conn_aliasing_nickname_updated), self); self->presence_cache = gabble_presence_cache_new (self); g_signal_connect (self->presence_cache, "nickname-update", G_CALLBACK (gabble_conn_aliasing_nickname_updated), self); g_signal_connect (self->presence_cache, "capabilities-update", G_CALLBACK (connection_capabilities_update_cb), self); tp_contacts_mixin_init (G_OBJECT (self), G_STRUCT_OFFSET (GabbleConnection, contacts)); tp_base_connection_register_with_contacts_mixin (base); tp_base_contact_list_mixin_register_with_contacts_mixin (base); conn_aliasing_init (self); conn_avatars_init (self); conn_contact_info_init (self); conn_presence_init (self); conn_olpc_activity_properties_init (self); conn_location_init (self); conn_sidecars_init (self); conn_mail_notif_init (self); conn_client_types_init (self); conn_addressing_init (self); tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self), TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES, conn_capabilities_fill_contact_attributes); tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self), TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, conn_contact_capabilities_fill_contact_attributes); self->bytestream_factory = gabble_bytestream_factory_new (self); self->avatar_requests = g_hash_table_new (NULL, NULL); self->vcard_requests = g_hash_table_new (NULL, NULL); if (priv->fallback_socks5_proxies == NULL) { /* No proxies have been defined, set the default ones */ gchar *default_socks5_proxies[] = GABBLE_PARAMS_DEFAULT_SOCKS5_PROXIES; g_object_set (self, "fallback-socks5-proxies", default_socks5_proxies, NULL); } priv->all_caps = gabble_capability_set_new (); priv->notify_caps = gabble_capability_set_new (); priv->legacy_caps = gabble_capability_set_new (); priv->sidecar_caps = gabble_capability_set_new (); priv->client_caps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gabble_capability_set_free); priv->client_data_forms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); /* Historically, the optional Jingle transports were in our initial * presence, but could be removed by AdvertiseCapabilities(). Emulate * that here for now. */ priv->bonus_caps = gabble_capability_set_new (); #ifdef ENABLE_VOIP gabble_capability_set_add (priv->bonus_caps, NS_GOOGLE_TRANSPORT_P2P); gabble_capability_set_add (priv->bonus_caps, NS_JINGLE_TRANSPORT_ICEUDP); #endif return (GObject *) self; } static gchar * dup_default_resource (void) { /* This is a once-per-process leak. */ static gchar *default_resource = NULL; if (G_UNLIKELY (default_resource == NULL)) { char *local_machine_id = dbus_get_local_machine_id (); if (local_machine_id == NULL) g_error ("Out of memory getting local machine ID"); default_resource = sha1_hex (local_machine_id, strlen (local_machine_id)); /* Let's keep the resource a maneagable length. */ default_resource[8] = '\0'; dbus_free (local_machine_id); } return g_strdup (default_resource); } static void gabble_connection_constructed (GObject *object) { GabbleConnection *self = GABBLE_CONNECTION (object); GabbleConnectionPrivate *priv = self->priv; void (*chain_up)(GObject *) = G_OBJECT_CLASS (gabble_connection_parent_class)->constructed; if (chain_up != NULL) chain_up (object); if (priv->resource == NULL) { priv->resource = dup_default_resource (); DEBUG ("defaulted resource to %s", priv->resource); } /* set initial presence */ self->self_presence = gabble_presence_new (); g_assert (priv->resource); gabble_presence_update (self->self_presence, priv->resource, GABBLE_PRESENCE_AVAILABLE, NULL, priv->priority, NULL, time (NULL)); } static void gabble_connection_init (GabbleConnection *self) { GError *error = NULL; GabbleConnectionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_CONNECTION, GabbleConnectionPrivate); DEBUG("Initializing (GabbleConnection *)%p", self); self->daemon = tp_dbus_daemon_dup (&error); if (self->daemon == NULL) { g_error ("Failed to connect to dbus daemon: %s", error->message); } self->priv = priv; priv->iq_reply_cancellable = g_cancellable_new (); priv->caps_serial = 1; priv->last_activity_time = time (NULL); priv->port = 5222; gabble_capabilities_init (self); } static void gabble_connection_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GabbleConnection *self = (GabbleConnection *) object; GabbleConnectionPrivate *priv = self->priv; switch (property_id) { case PROP_CONNECT_SERVER: g_value_set_string (value, priv->connect_server); break; case PROP_EXPLICIT_SERVER: g_value_set_string (value, priv->explicit_server); break; case PROP_STREAM_SERVER: g_value_set_string (value, priv->stream_server); break; case PROP_PORT: g_value_set_uint (value, priv->port); break; case PROP_OLD_SSL: g_value_set_boolean (value, priv->old_ssl); break; case PROP_REQUIRE_ENCRYPTION: g_value_set_boolean (value, priv->require_encryption); break; case PROP_REGISTER: g_value_set_boolean (value, priv->do_register); break; case PROP_LOW_BANDWIDTH: g_value_set_boolean (value, priv->low_bandwidth); break; case PROP_USERNAME: g_value_set_string (value, priv->username); break; case PROP_PASSWORD: g_value_set_string (value, priv->password); break; case PROP_RESOURCE: g_value_set_string (value, priv->resource); break; case PROP_PRIORITY: g_value_set_schar (value, priv->priority); break; case PROP_HTTPS_PROXY_SERVER: g_value_set_string (value, priv->https_proxy_server); break; case PROP_HTTPS_PROXY_PORT: g_value_set_uint (value, priv->https_proxy_port); break; case PROP_FALLBACK_CONFERENCE_SERVER: g_value_set_string (value, priv->fallback_conference_server); break; case PROP_IGNORE_SSL_ERRORS: g_value_set_boolean (value, priv->ignore_ssl_errors); break; case PROP_ALIAS: g_value_set_string (value, priv->alias); break; case PROP_STUN_SERVER: g_value_set_string (value, priv->stun_server); break; case PROP_STUN_PORT: g_value_set_uint (value, priv->stun_port); break; case PROP_FALLBACK_STUN_SERVER: g_value_set_string (value, priv->fallback_stun_server); break; case PROP_FALLBACK_STUN_PORT: g_value_set_uint (value, priv->fallback_stun_port); break; case PROP_FALLBACK_SOCKS5_PROXIES: g_value_set_boxed (value, priv->fallback_socks5_proxies); break; case PROP_KEEPALIVE_INTERVAL: g_value_set_uint (value, priv->keepalive_interval); break; case PROP_DECLOAK_AUTOMATICALLY: g_value_set_boolean (value, priv->decloak_automatically); break; case PROP_FALLBACK_SERVERS: g_value_set_boxed (value, priv->fallback_servers); break; case PROP_EXTRA_CERTIFICATE_IDENTITIES: g_value_set_boxed (value, priv->extra_certificate_identities); break; case PROP_POWER_SAVING: g_value_set_boolean (value, priv->power_saving); break; case PROP_DOWNLOAD_AT_CONNECTION: { gboolean download_at_connection = TRUE; if (self->roster != NULL) { g_object_get (self->roster, "download-at-connection", &download_at_connection, NULL); } else { g_warn_if_reached (); } g_value_set_boolean (value, download_at_connection); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_connection_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleConnection *self = (GabbleConnection *) object; GabbleConnectionPrivate *priv = self->priv; switch (property_id) { case PROP_EXPLICIT_SERVER: g_free (priv->explicit_server); priv->explicit_server = g_value_dup_string (value); if (priv->connect_server == NULL) priv->connect_server = g_value_dup_string (value); break; case PROP_PORT: priv->port = g_value_get_uint (value); break; case PROP_OLD_SSL: priv->old_ssl = g_value_get_boolean (value); break; case PROP_REQUIRE_ENCRYPTION: priv->require_encryption = g_value_get_boolean (value); break; case PROP_REGISTER: priv->do_register = g_value_get_boolean (value); break; case PROP_LOW_BANDWIDTH: priv->low_bandwidth = g_value_get_boolean (value); break; case PROP_STREAM_SERVER: g_free (priv->stream_server); priv->stream_server = g_value_dup_string (value); break; case PROP_USERNAME: g_free (priv->username); priv->username = g_value_dup_string (value); break; case PROP_PASSWORD: g_free (priv->password); priv->password = g_value_dup_string (value); break; case PROP_RESOURCE: if (tp_strdiff (priv->resource, g_value_get_string (value))) { gchar *old_resource = priv->resource; gchar *new_resource = g_value_dup_string (value); time_t now = time (NULL); priv->resource = new_resource; /* Add self presence for new resource... */ gabble_presence_update (self->self_presence, new_resource, self->self_presence->status, self->self_presence->status_message, priv->priority, NULL, now); /* ...and remove it for the old one. */ gabble_presence_update (self->self_presence, old_resource, GABBLE_PRESENCE_OFFLINE, NULL, 0, NULL, now); g_free (old_resource); } break; case PROP_PRIORITY: priv->priority = g_value_get_schar (value); break; case PROP_HTTPS_PROXY_SERVER: g_free (priv->https_proxy_server); priv->https_proxy_server = g_value_dup_string (value); break; case PROP_HTTPS_PROXY_PORT: priv->https_proxy_port = g_value_get_uint (value); break; case PROP_FALLBACK_CONFERENCE_SERVER: g_free (priv->fallback_conference_server); priv->fallback_conference_server = g_value_dup_string (value); break; case PROP_IGNORE_SSL_ERRORS: priv->ignore_ssl_errors = g_value_get_boolean (value); break; case PROP_ALIAS: g_free (priv->alias); priv->alias = g_value_dup_string (value); break; case PROP_STUN_SERVER: g_free (priv->stun_server); priv->stun_server = g_value_dup_string (value); break; case PROP_STUN_PORT: priv->stun_port = g_value_get_uint (value); break; case PROP_FALLBACK_STUN_SERVER: g_free (priv->fallback_stun_server); priv->fallback_stun_server = g_value_dup_string (value); break; case PROP_FALLBACK_STUN_PORT: priv->fallback_stun_port = g_value_get_uint (value); break; case PROP_FALLBACK_SOCKS5_PROXIES: if (priv->fallback_socks5_proxies != NULL) g_strfreev (priv->fallback_socks5_proxies); priv->fallback_socks5_proxies = g_value_dup_boxed (value); break; case PROP_KEEPALIVE_INTERVAL: priv->keepalive_interval = g_value_get_uint (value); if (priv->pinger != NULL) g_object_set (priv->pinger, "ping-interval", priv->keepalive_interval, NULL); break; case PROP_DECLOAK_AUTOMATICALLY: priv->decloak_automatically = g_value_get_boolean (value); break; case PROP_FALLBACK_SERVERS: if (priv->fallback_servers != NULL) g_strfreev (priv->fallback_servers); priv->fallback_servers = g_value_dup_boxed (value); priv->fallback_server_index = 0; break; case PROP_EXTRA_CERTIFICATE_IDENTITIES: if (priv->extra_certificate_identities != NULL) g_strfreev (priv->extra_certificate_identities); priv->extra_certificate_identities = g_value_dup_boxed (value); break; case PROP_POWER_SAVING: priv->power_saving = g_value_get_boolean (value); break; case PROP_DOWNLOAD_AT_CONNECTION: /* Connection parameters are set by TpBaseProtocolClass->new_connection * after the channel managers are created. So at this step self->roster * is not NULL and we pass the property. */ if (self->roster != NULL) g_object_set (self->roster, "download-at-connection", g_value_get_boolean (value), NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_connection_dispose (GObject *object); static void gabble_connection_finalize (GObject *object); static void connection_shut_down (TpBaseConnection *base); static gboolean _gabble_connection_connect (TpBaseConnection *base, GError **error); static gchar * gabble_connection_get_unique_name (TpBaseConnection *self) { GabbleConnectionPrivate *priv = GABBLE_CONNECTION (self)->priv; gchar *unique_name = gabble_encode_jid (priv->username, priv->stream_server, priv->resource); if (priv->username == NULL) { gchar *tmp = unique_name; unique_name = g_strdup_printf ("%s_%p", tmp, self); g_free (tmp); } return unique_name; } /* For the benefit of the unit tests, this will allow the connection to * be NULL */ void _gabble_connection_create_handle_repos (TpBaseConnection *conn, TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) { repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, gabble_normalize_contact, GUINT_TO_POINTER (GABBLE_JID_ANY)); repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_ROOM, gabble_normalize_room, conn); } #define TWICE(x) (x), (x) static const gchar *implemented_interfaces[] = { /* conditionally present interfaces */ TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, GABBLE_IFACE_OLPC_ACTIVITY_PROPERTIES, GABBLE_IFACE_OLPC_BUDDY_INFO, /* always present interfaces */ TP_IFACE_CONNECTION_INTERFACE_POWER_SAVING, TP_IFACE_CONNECTION_INTERFACE_ALIASING, TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES, TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_AVATARS, TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, TP_IFACE_CONNECTION_INTERFACE_CONTACTS, TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST, TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS, TP_IFACE_CONNECTION_INTERFACE_REQUESTS, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, TP_IFACE_CONNECTION_INTERFACE_LOCATION, GABBLE_IFACE_CONNECTION_INTERFACE_GABBLE_DECLOAK, GABBLE_IFACE_CONNECTION_FUTURE, TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES, GABBLE_IFACE_CONNECTION_INTERFACE_ADDRESSING, NULL }; static const gchar **interfaces_always_present = implemented_interfaces + 3; const gchar ** gabble_connection_get_implemented_interfaces (void) { return implemented_interfaces; } const gchar ** gabble_connection_get_guaranteed_interfaces (void) { return interfaces_always_present; } static GPtrArray * _gabble_connection_get_interfaces_always_present (TpBaseConnection *base) { GPtrArray *interfaces; const gchar **iter; interfaces = TP_BASE_CONNECTION_CLASS ( gabble_connection_parent_class)->get_interfaces_always_present (base); for (iter = interfaces_always_present; *iter != NULL; iter++) g_ptr_array_add (interfaces, (gchar *) *iter); return interfaces; } static void gabble_connection_class_init (GabbleConnectionClass *gabble_connection_class) { GObjectClass *object_class = G_OBJECT_CLASS (gabble_connection_class); TpBaseConnectionClass *parent_class = TP_BASE_CONNECTION_CLASS ( gabble_connection_class); static TpDBusPropertiesMixinPropImpl location_props[] = { { "LocationAccessControlTypes", NULL, NULL }, { "LocationAccessControl", NULL, NULL }, { "SupportedLocationFeatures", NULL, NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl decloak_props[] = { { "DecloakAutomatically", TWICE ("decloak-automatically") }, { NULL } }; static TpDBusPropertiesMixinPropImpl mail_notif_props[] = { { "MailNotificationFlags", NULL, NULL }, { "UnreadMailCount", NULL, NULL }, { "UnreadMails", NULL, NULL }, { "MailAddress", NULL, NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl power_saving_props[] = { { "PowerSavingActive", "power-saving", NULL }, { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { /* 0 */ { TP_IFACE_CONNECTION_INTERFACE_LOCATION, conn_location_properties_getter, conn_location_properties_setter, location_props, }, /* 1 */ { TP_IFACE_CONNECTION_INTERFACE_AVATARS, conn_avatars_properties_getter, NULL, NULL, }, /* 2 */ { TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, conn_contact_info_properties_getter, NULL, NULL, }, /* 3 */ { GABBLE_IFACE_CONNECTION_INTERFACE_GABBLE_DECLOAK, tp_dbus_properties_mixin_getter_gobject_properties, tp_dbus_properties_mixin_setter_gobject_properties, decloak_props, }, { TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, conn_mail_notif_properties_getter, NULL, mail_notif_props, }, { TP_IFACE_CONNECTION_INTERFACE_POWER_SAVING, tp_dbus_properties_mixin_getter_gobject_properties, NULL, power_saving_props, }, { NULL } }; prop_interfaces[1].props = conn_avatars_properties; prop_interfaces[2].props = conn_contact_info_properties; DEBUG("Initializing (GabbleConnectionClass *)%p", gabble_connection_class); object_class->get_property = gabble_connection_get_property; object_class->set_property = gabble_connection_set_property; object_class->constructor = gabble_connection_constructor; object_class->constructed = gabble_connection_constructed; parent_class->create_handle_repos = _gabble_connection_create_handle_repos; parent_class->get_unique_connection_name = gabble_connection_get_unique_name; parent_class->create_channel_factories = NULL; parent_class->create_channel_managers = _gabble_connection_create_channel_managers; parent_class->shut_down = connection_shut_down; parent_class->start_connecting = _gabble_connection_connect; parent_class->get_interfaces_always_present = _gabble_connection_get_interfaces_always_present; g_type_class_add_private (gabble_connection_class, sizeof (GabbleConnectionPrivate)); object_class->dispose = gabble_connection_dispose; object_class->finalize = gabble_connection_finalize; g_object_class_install_property (object_class, PROP_CONNECT_SERVER, g_param_spec_string ( "connect-server", "Hostname or IP of Jabber server", "The server used when establishing a connection.", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /* * The explicit-server property can be used for verification of a * server certificate. It's important that it comes from the user * and is not the result of any other outside lookup, unlike the * connect-server property. */ g_object_class_install_property (object_class, PROP_EXPLICIT_SERVER, g_param_spec_string ( "explicit-server", "Explicit Hostname or IP of Jabber server", "Server explicitly specified by the user to connect to.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PORT, g_param_spec_uint ( "port", "Jabber server port", "The port used when establishing a connection.", 1, G_MAXUINT16, 5222, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_OLD_SSL, g_param_spec_boolean ( "old-ssl", "Old-style SSL tunneled connection", "Establish the entire connection to the server within an " "SSL-encrypted tunnel. Note that this is not the same as connecting " "with TLS, which is not yet supported.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_REQUIRE_ENCRYPTION, g_param_spec_boolean ( "require-encryption", "Require encryption", "Require the connection to be encrypted, either via old-style SSL, " "or StartTLS mechanisms.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_REGISTER, g_param_spec_boolean ( "register", "Register account on server", "Register a new account on server.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_LOW_BANDWIDTH, g_param_spec_boolean ( "low-bandwidth", "Low bandwidth mode", "Determines whether we are in low bandwidth mode. This influences " "polling behaviour.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STREAM_SERVER, g_param_spec_string ( "stream-server", "The server name used to initialise the stream.", "The server name used when initialising the stream, which is " "usually the part after the @ in the user's JID.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_USERNAME, g_param_spec_string ( "username", "Jabber username", "The username used when authenticating.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PASSWORD, g_param_spec_string ( "password", "Jabber password", "The password used when authenticating.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_RESOURCE, g_param_spec_string ( "resource", "Jabber resource", "The Jabber resource used when authenticating.", "Telepathy", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_PRIORITY, g_param_spec_char ( "priority", "Jabber presence priority", "The default priority used when reporting our presence.", G_MININT8, G_MAXINT8, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_HTTPS_PROXY_SERVER, g_param_spec_string ( "https-proxy-server", "The server name used as an HTTPS proxy server", "The server name used as an HTTPS proxy server.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_HTTPS_PROXY_PORT, g_param_spec_uint ( "https-proxy-port", "The HTTP proxy server port", "The HTTP proxy server port.", 0, G_MAXUINT16, GABBLE_PARAMS_DEFAULT_HTTPS_PROXY_PORT, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FALLBACK_CONFERENCE_SERVER, g_param_spec_string ( "fallback-conference-server", "The conference server used as fallback", "The conference server used as fallback when everything else fails.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STUN_SERVER, g_param_spec_string ( "stun-server", "STUN server", "STUN server.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_STUN_PORT, g_param_spec_uint ( "stun-port", "STUN port", "STUN port.", 0, G_MAXUINT16, GABBLE_PARAMS_DEFAULT_STUN_PORT, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FALLBACK_STUN_SERVER, g_param_spec_string ( "fallback-stun-server", "fallback STUN server", "Fallback STUN server.", GABBLE_PARAMS_DEFAULT_FALLBACK_STUN_SERVER, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FALLBACK_STUN_PORT, g_param_spec_uint ( "fallback-stun-port", "fallback STUN port", "Fallback STUN port.", 0, G_MAXUINT16, GABBLE_PARAMS_DEFAULT_STUN_PORT, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_IGNORE_SSL_ERRORS, g_param_spec_boolean ( "ignore-ssl-errors", "Ignore SSL errors", "Continue connecting even if the server's " "SSL certificate is invalid or missing.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_ALIAS, g_param_spec_string ( "alias", "Alias/nick for local user", "Alias/nick for local user", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FALLBACK_SOCKS5_PROXIES, g_param_spec_boxed ( "fallback-socks5-proxies", "fallback SOCKS5 proxies", "Fallback SOCKS5 proxies.", G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_KEEPALIVE_INTERVAL, g_param_spec_uint ( "keepalive-interval", "keepalive interval", "Seconds between keepalive packets, or 0 to disable", 0, G_MAXUINT, 30, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_DECLOAK_AUTOMATICALLY, g_param_spec_boolean ( "decloak-automatically", "Decloak automatically?", "Leak presence and capabilities when requested", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_DOWNLOAD_AT_CONNECTION, g_param_spec_boolean ( "download-roster-at-connection", "Download the contact list at connection?", "Download the contact list at connection?", TRUE, G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_FALLBACK_SERVERS, g_param_spec_boxed ( "fallback-servers", "Fallback servers", "List of servers to fallback to (syntax server[:port][,oldssl]", G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_EXTRA_CERTIFICATE_IDENTITIES, g_param_spec_boxed ( "extra-certificate-identities", "Extra Certificate Reference Identities", "Extra identities to check certificate against. These are present as a " "result of a user choice or configuration.", G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_POWER_SAVING, g_param_spec_boolean ( "power-saving", "Power saving active?", "Queue remote presence updates server-side for less network chatter", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gabble_connection_class->properties_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleConnectionClass, properties_class)); tp_contacts_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleConnectionClass, contacts_class)); conn_presence_class_init (gabble_connection_class); conn_contact_info_class_init (gabble_connection_class); tp_base_contact_list_mixin_class_init (parent_class); } static void gabble_connection_dispose (GObject *object) { GabbleConnection *self = GABBLE_CONNECTION (object); TpBaseConnection *base = (TpBaseConnection *) self; GabbleConnectionPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; DEBUG ("called"); g_assert ((tp_base_connection_get_status (base) == TP_CONNECTION_STATUS_DISCONNECTED) || (tp_base_connection_get_status (base) == TP_INTERNAL_CONNECTION_STATUS_NEW)); tp_clear_object (&self->bytestream_factory); tp_clear_object (&self->disco); tp_clear_object (&self->req_pipeline); tp_clear_object (&self->vcard_manager); #ifdef ENABLE_VOIP tp_clear_object (&self->jingle_mint); #endif /* remove borrowed references before TpBaseConnection unrefs the channel * factories */ self->roster = NULL; self->muc_factory = NULL; self->private_tubes_factory = NULL; priv->auth_manager = NULL; tp_clear_object (&self->self_presence); tp_clear_object (&self->presence_cache); conn_olpc_activity_properties_dispose (self); g_hash_table_unref (self->avatar_requests); g_hash_table_unref (self->vcard_requests); conn_presence_dispose (self); conn_mail_notif_dispose (self); tp_clear_object (&priv->connector); tp_clear_object (&self->session); /* The porter was borrowed from the session. */ priv->porter = NULL; /* Cancel any outstanding _gabble_connection_send_with_reply() requests. */ g_cancellable_cancel (priv->iq_reply_cancellable); tp_clear_object (&priv->iq_reply_cancellable); g_hash_table_unref (priv->client_caps); gabble_capability_set_free (priv->all_caps); gabble_capability_set_free (priv->notify_caps); gabble_capability_set_free (priv->legacy_caps); gabble_capability_set_free (priv->sidecar_caps); gabble_capability_set_free (priv->bonus_caps); g_hash_table_unref (priv->client_data_forms); if (priv->disconnect_timer != 0) { g_source_remove (priv->disconnect_timer); priv->disconnect_timer = 0; } tp_clear_object (&self->pep_location); tp_clear_object (&self->pep_nick); tp_clear_object (&self->pep_olpc_buddy_props); tp_clear_object (&self->pep_olpc_activities); tp_clear_object (&self->pep_olpc_current_act); tp_clear_object (&self->pep_olpc_act_props); conn_sidecars_dispose (self); tp_clear_object (&self->daemon); if (G_OBJECT_CLASS (gabble_connection_parent_class)->dispose) G_OBJECT_CLASS (gabble_connection_parent_class)->dispose (object); } static void gabble_connection_finalize (GObject *object) { GabbleConnection *self = GABBLE_CONNECTION (object); GabbleConnectionPrivate *priv = self->priv; DEBUG ("called with %p", object); g_free (priv->connect_server); g_free (priv->explicit_server); g_free (priv->stream_server); g_free (priv->username); g_free (priv->password); g_free (priv->resource); g_free (priv->https_proxy_server); g_free (priv->stun_server); g_free (priv->fallback_stun_server); g_free (priv->fallback_conference_server); g_strfreev (priv->fallback_socks5_proxies); g_strfreev (priv->fallback_servers); g_strfreev (priv->extra_certificate_identities); g_free (priv->alias); g_free (priv->stream_id); tp_contacts_mixin_finalize (G_OBJECT(self)); conn_presence_finalize (self); conn_contact_info_finalize (self); gabble_capabilities_finalize (self); G_OBJECT_CLASS (gabble_connection_parent_class)->finalize (object); } /** * _gabble_connection_set_properties_from_account * * Parses an account string which may be one of the following forms: * username@server * username@server/resource * and sets the properties for username, stream server and resource * appropriately. Also sets the connect server to the stream server if one has * not yet been specified. */ gboolean _gabble_connection_set_properties_from_account (GabbleConnection *conn, const gchar *account, GError **error) { char *username, *server, *resource; gboolean result; g_assert (GABBLE_IS_CONNECTION (conn)); g_assert (account != NULL); username = server = resource = NULL; result = TRUE; if (!wocky_decode_jid (account, &username, &server, &resource)) { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "unable to extract JID from account name"); result = FALSE; goto OUT; } g_object_set (G_OBJECT (conn), "username", username, "stream-server", server, NULL); /* only override the default resource if we actually got one */ if (resource) g_object_set (G_OBJECT (conn), "resource", resource, NULL); OUT: g_free (username); g_free (server); g_free (resource); return result; } /** * gabble_connection_get_full_jid: * * Returns: the full jid (including resource) of this connection, which must be * freed by the caller. */ gchar * gabble_connection_get_full_jid (GabbleConnection *conn) { const gchar *bare_jid = conn_util_get_bare_self_jid (conn); return g_strconcat (bare_jid, "/", conn->priv->resource, NULL); } static gchar * _gabble_plugin_connection_get_full_jid (GabblePluginConnection *plugin_conn) { GabbleConnection *conn = GABBLE_CONNECTION (plugin_conn); return gabble_connection_get_full_jid (conn); } /** * gabble_connection_dup_porter: * * Returns: (transfer full): the #WockyPorter instance driving this connection. */ WockyPorter *gabble_connection_dup_porter (GabbleConnection *conn) { GabbleConnectionPrivate *priv; g_assert (GABBLE_IS_CONNECTION (conn)); priv = conn->priv; if (priv->porter != NULL) return g_object_ref (priv->porter); return NULL; } WockySession * gabble_connection_get_session (GabblePluginConnection *plugin_connection) { GabbleConnection *connection = GABBLE_CONNECTION (plugin_connection); g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); return connection->session; } /** * _gabble_connection_send * * Send an WockyStanza and trap network errors appropriately. */ gboolean _gabble_connection_send (GabbleConnection *conn, WockyStanza *msg, GError **error) { g_assert (GABBLE_IS_CONNECTION (conn)); if (conn->priv->porter == NULL) { g_set_error_literal (error, TP_ERROR, TP_ERROR_NETWORK_ERROR, "connection is disconnected"); return FALSE; } wocky_porter_send (conn->priv->porter, msg); return TRUE; } void gabble_connection_update_last_use (GabbleConnection *conn) { conn->priv->last_activity_time = time (NULL); } static gdouble gabble_connection_get_last_use (GabbleConnection *conn) { return difftime (time (NULL), conn->priv->last_activity_time); } typedef struct { GabbleConnectionMsgReplyFunc reply_func; GabbleConnection *conn; WockyStanza *sent_msg; gpointer user_data; GObject *object; gboolean object_alive; } GabbleMsgHandlerData; static void message_send_object_destroy_notify_cb (gpointer data, GObject *where_the_object_was) { GabbleMsgHandlerData *handler_data = data; handler_data->object = NULL; handler_data->object_alive = FALSE; } static void msg_handler_data_free (GabbleMsgHandlerData *handler_data) { g_object_unref (handler_data->sent_msg); if (handler_data->object != NULL) { g_object_weak_unref (handler_data->object, message_send_object_destroy_notify_cb, handler_data); } g_slice_free (GabbleMsgHandlerData, handler_data); } static void message_send_reply_cb ( GObject *source, GAsyncResult *result, gpointer user_data) { GabbleMsgHandlerData *handler_data = user_data; if (handler_data->object_alive && handler_data->reply_func != NULL) { WockyPorter *porter = WOCKY_PORTER (source); GError *error = NULL; WockyStanza *stanza = wocky_porter_send_iq_finish (porter, result, &error); if (stanza != NULL) { handler_data->reply_func (handler_data->conn, handler_data->sent_msg, stanza, handler_data->object, handler_data->user_data); g_object_unref (stanza); } else { DEBUG ("send_iq_async failed: %s", error->message); g_error_free (error); } } msg_handler_data_free (handler_data); } /** * _gabble_connection_send_with_reply * * Send a tracked WockyStanza and trap network errors appropriately. * * If object is non-NULL the handler will follow the lifetime of that object, * which means that if the object is destroyed the callback will not be invoked. * * if reply_func is NULL the reply will be ignored. */ gboolean _gabble_connection_send_with_reply (GabbleConnection *conn, WockyStanza *msg, GabbleConnectionMsgReplyFunc reply_func, GObject *object, gpointer user_data, GError **error) { GabbleConnectionPrivate *priv; GabbleMsgHandlerData *handler_data; g_assert (GABBLE_IS_CONNECTION (conn)); priv = conn->priv; if (priv->porter == NULL) { g_set_error_literal (error, TP_ERROR, TP_ERROR_NETWORK_ERROR, "connection is disconnected"); return FALSE; } g_object_ref (msg); handler_data = g_slice_new (GabbleMsgHandlerData); handler_data->reply_func = reply_func; handler_data->conn = conn; handler_data->sent_msg = msg; handler_data->user_data = user_data; handler_data->object = object; handler_data->object_alive = TRUE; if (object != NULL) { g_object_weak_ref (object, message_send_object_destroy_notify_cb, handler_data); } wocky_porter_send_iq_async (priv->porter, msg, priv->iq_reply_cancellable, message_send_reply_cb, handler_data); return TRUE; } static void connect_iq_callbacks (GabbleConnection *conn); static gboolean iq_disco_cb (WockyPorter *, WockyStanza *, gpointer); static gboolean iq_version_cb (WockyPorter *, WockyStanza *, gpointer); static void connection_disco_cb (GabbleDisco *, GabbleDiscoRequest *, const gchar *, const gchar *, WockyNode *, GError *, gpointer); static void decrement_waiting_connected (GabbleConnection *connection); static void connection_initial_presence_cb (GObject *, GAsyncResult *, gpointer); static void gabble_connection_disconnect_with_tp_error (GabbleConnection *self, const GError *tp_error, TpConnectionStatusReason reason) { TpBaseConnection *base = (TpBaseConnection *) self; GHashTable *details = tp_asv_new ( "debug-message", G_TYPE_STRING, tp_error->message, NULL); g_assert (tp_error->domain == TP_ERROR); tp_base_connection_disconnect_with_dbus_error (base, tp_error_get_dbus_name (tp_error->code), details, reason); g_hash_table_unref (details); } static void remote_closed_cb (WockyPorter *porter, GabbleConnection *self) { TpBaseConnection *base = TP_BASE_CONNECTION (self); GError e = { TP_ERROR, TP_ERROR_CONNECTION_LOST, "server closed its XMPP stream" }; if (tp_base_connection_get_status (base) == TP_CONNECTION_STATUS_DISCONNECTED) /* Ignore if we are already disconnecting/disconnected */ return; DEBUG ("server closed its XMPP stream; close ours"); /* Changing the state to Disconnect will call connection_shut_down which * will properly close the porter. */ gabble_connection_disconnect_with_tp_error (self, &e, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); } static void force_close_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); TpBaseConnection *base = TP_BASE_CONNECTION (self); GError *error = NULL; if (!wocky_porter_force_close_finish (WOCKY_PORTER (source), res, &error)) { DEBUG ("force close failed: %s", error->message); g_error_free (error); } else { DEBUG ("connection properly closed (forced)"); } tp_base_connection_finish_shutdown (base); } static void remote_error_cb (WockyPorter *porter, GQuark domain, gint code, gchar *msg, GabbleConnection *self) { TpConnectionStatusReason reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED; TpBaseConnection *base = (TpBaseConnection *) self; GabbleConnectionPrivate *priv = self->priv; GError e = { domain, code, msg }; GError *error = NULL; if (tp_base_connection_get_status (base) == TP_CONNECTION_STATUS_DISCONNECTED) /* Ignore if we are already disconnecting/disconnected */ return; gabble_set_tp_conn_error_from_wocky (&e, tp_base_connection_get_status (base), &reason, &error); g_assert (error->domain == TP_ERROR); DEBUG ("Force closing of the connection %p", self); priv->closing = TRUE; wocky_porter_force_close_async (priv->porter, NULL, force_close_cb, self); gabble_connection_disconnect_with_tp_error (self, error, reason); g_error_free (error); } static void connector_error_disconnect (GabbleConnection *self, GError *error) { GError *tp_error = NULL; TpBaseConnection *base = (TpBaseConnection *) self; TpConnectionStatusReason reason = TP_CONNECTION_STATUS_REASON_NETWORK_ERROR; gchar *dbus_error = NULL; GHashTable *details = NULL; if (error->domain == GABBLE_SERVER_TLS_ERROR) { gabble_server_tls_manager_get_rejection_details ( self->priv->server_tls_manager, &dbus_error, &details, &reason); /* new-style interactive TLS verification error */ DEBUG ("New-style TLS verification error, reason %u, dbus error %s", reason, dbus_error); tp_base_connection_disconnect_with_dbus_error (base, dbus_error, details, reason); tp_clear_pointer (&details, g_hash_table_unref); g_free (dbus_error); return; } if (error->domain == WOCKY_AUTH_ERROR && gabble_auth_manager_get_failure_details (self->priv->auth_manager, &dbus_error, &details, &reason)) { DEBUG ("Interactive authentication error, reason %u, dbus error %s", reason, dbus_error); tp_base_connection_disconnect_with_dbus_error (base, dbus_error, details, reason); tp_clear_pointer (&details, g_hash_table_unref); g_free (dbus_error); return; } gabble_set_tp_conn_error_from_wocky (error, tp_base_connection_get_status (base), &reason, &tp_error); DEBUG ("connection failed: %s", tp_error->message); g_assert (tp_error->domain == TP_ERROR); gabble_connection_disconnect_with_tp_error (self, tp_error, reason); g_error_free (tp_error); } static void bare_jid_disco_cb (GabbleDisco *disco, GabbleDiscoRequest *request, const gchar *jid, const gchar *node, WockyNode *result, GError *disco_error, gpointer user_data) { GabbleConnection *conn = user_data; if (disco_error != NULL) { DEBUG ("Got disco error on bare jid: %s", disco_error->message); } else { WockyNodeIter i; WockyNode *child; wocky_node_iter_init (&i, result, "identity", NULL); while (wocky_node_iter_next (&i, &child)) { const gchar *category = wocky_node_get_attribute (child, "category"); const gchar *type = wocky_node_get_attribute (child, "type"); if (!tp_strdiff (category, "pubsub") && !tp_strdiff (type, "pep")) { DEBUG ("Server advertises PEP support in our jid features"); conn->features |= GABBLE_CONNECTION_FEATURES_PEP; } } } decrement_waiting_connected (conn); } /** * next_fallback_server * * Launch connection using next fallback server if any, return * FALSE if they all have been tried or the error is not * network related (e.g. login failed). */ static gboolean next_fallback_server (GabbleConnection *self, const GError *error) { GabbleConnectionPrivate *priv = self->priv; const gchar *server; gchar **split; guint port = 5222; gboolean old_ssl = FALSE; GNetworkAddress *addr; GError *tmp_error = NULL; /* Only retry on connection failures */ if (error->domain != G_IO_ERROR) return FALSE; if (priv->fallback_servers == NULL || priv->fallback_servers[priv->fallback_server_index] == NULL) return FALSE; server = priv->fallback_servers[priv->fallback_server_index++]; split = g_strsplit (server, ",", 2); if (split[0] == NULL) { g_strfreev (split); return FALSE; } else if (split[1] != NULL && !tp_strdiff (split[1], "oldssl")) { old_ssl = TRUE; port = 5223; } addr = G_NETWORK_ADDRESS ( g_network_address_parse (split[0], port, &tmp_error)); if (!addr) { g_warning ("%s", tmp_error->message); g_error_free (tmp_error); return FALSE; } g_free (priv->connect_server); priv->connect_server = g_strdup (g_network_address_get_hostname (addr)); priv->port = g_network_address_get_port (addr); priv->old_ssl = old_ssl; g_object_notify (G_OBJECT (self), "connect-server"); g_object_notify (G_OBJECT (self), "port"); g_object_notify (G_OBJECT (self), "old-ssl"); g_object_unref (addr); _gabble_connection_connect (TP_BASE_CONNECTION (self), NULL); g_strfreev (split); return TRUE; } /** * connector_connected * * Stage 2 of connecting, this function is called once the connect operation * has finished. It checks if the connection succeeded, creates and starts * the WockyPorter, then sends two discovery requests to find the * server's features (one to the server and one to our bare jid). */ static void connector_connected (GabbleConnection *self, WockyXmppConnection *conn, const gchar *jid, GError *error) { GabbleConnectionPrivate *priv = self->priv; TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); /* cleanup the cancellable */ tp_clear_object (&priv->cancellable); /* We went to closing while we were connecting... drop the connection and * finish the shutdown */ if (priv->closing) { if (conn != NULL) g_object_unref (conn); else g_error_free (error); tp_base_connection_finish_shutdown (base); return; } /* We don't need the connector any more */ tp_clear_object (&priv->connector); if (conn == NULL) { if (!next_fallback_server (self, error)) connector_error_disconnect (self, error); g_error_free (error); return; } DEBUG ("connected (jid: %s)", jid); self->session = wocky_session_new_with_connection (conn, jid); priv->porter = wocky_session_get_porter (self->session); g_assert (WOCKY_IS_C2S_PORTER (priv->porter)); priv->pinger = wocky_ping_new (WOCKY_C2S_PORTER (priv->porter), priv->keepalive_interval); g_signal_connect (priv->porter, "remote-closed", G_CALLBACK (remote_closed_cb), self); g_signal_connect (priv->porter, "remote-error", G_CALLBACK (remote_error_cb), self); g_signal_emit_by_name (self, "porter-available", priv->porter); connect_iq_callbacks (self); wocky_pep_service_start (self->pep_location, self->session); wocky_pep_service_start (self->pep_nick, self->session); wocky_pep_service_start (self->pep_olpc_buddy_props, self->session); wocky_pep_service_start (self->pep_olpc_activities, self->session); wocky_pep_service_start (self->pep_olpc_current_act, self->session); wocky_pep_service_start (self->pep_olpc_act_props, self->session); /* Don't use wocky_session_start as we don't want to start all the * components (Roster, presence-manager, etc) for now */ wocky_porter_start (priv->porter); tp_base_connection_set_self_handle (base, tp_handle_ensure (contact_handles, jid, NULL, &error)); if (tp_base_connection_get_self_handle (base) == 0) { DEBUG ("couldn't get our self handle: %s", error->message); if (error->domain != TP_ERROR) { error->domain = TP_ERROR; error->code = TP_ERROR_INVALID_HANDLE; } gabble_connection_disconnect_with_tp_error (self, error, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); g_error_free (error); return; } /* update priv->resource and priv->stream_server from the server's JID */ if (!_gabble_connection_set_properties_from_account (self, jid, &error)) { DEBUG ("couldn't parse our own JID: %s", error->message); if (error->domain != TP_ERROR) { error->domain = TP_ERROR; error->code = TP_ERROR_INVALID_ARGUMENT; } gabble_connection_disconnect_with_tp_error (self, error, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); g_error_free (error); return; } DEBUG ("Created self handle %d, our JID is %s", tp_base_connection_get_self_handle (base), jid); /* set initial capabilities */ gabble_connection_refresh_capabilities (self, NULL); /* Disco server features */ if (!gabble_disco_request_with_timeout (self->disco, GABBLE_DISCO_TYPE_INFO, priv->stream_server, NULL, disco_reply_timeout, connection_disco_cb, self, G_OBJECT (self), &error)) { DEBUG ("sending disco request failed: %s", error->message); if (error->domain != TP_ERROR) { error->domain = TP_ERROR; error->code = TP_ERROR_NETWORK_ERROR; } gabble_connection_disconnect_with_tp_error (self, error, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); g_error_free (error); } /* Disco our own bare jid to check if PEP is supported */ if (!gabble_disco_request_with_timeout (self->disco, GABBLE_DISCO_TYPE_INFO, conn_util_get_bare_self_jid (self), NULL, disco_reply_timeout, bare_jid_disco_cb, self, G_OBJECT (self), &error)) { DEBUG ("Sending disco request to our own bare jid failed: %s", error->message); if (error->domain != TP_ERROR) { error->domain = TP_ERROR; error->code = TP_ERROR_NETWORK_ERROR; } gabble_connection_disconnect_with_tp_error (self, error, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); g_error_free (error); } self->priv->waiting_connected = 2; } static void connector_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); GabbleConnectionPrivate *priv = self->priv; WockyXmppConnection *conn; GError *error = NULL; gchar *jid = NULL; conn = wocky_connector_connect_finish (WOCKY_CONNECTOR (source), res, &jid, &(priv->stream_id), &error); connector_connected (self, conn, jid, error); g_free (jid); } static void connector_register_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); GabbleConnectionPrivate *priv = self->priv; WockyXmppConnection *conn; GError *error = NULL; gchar *jid = NULL; conn = wocky_connector_register_finish (WOCKY_CONNECTOR (source), res, &jid, &(priv->stream_id), &error); connector_connected (self, conn, jid, error); g_free (jid); } static gboolean connection_iq_last_cb ( WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); const gchar *from = wocky_stanza_get_from (stanza); /* Aside from 21 being an appropriate number, 2 ^ 64 is 20 digits long. */ char seconds[21]; /* Check if the peer, if any, is authorized to receive our presence. */ if (from != NULL) { TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) self, TP_HANDLE_TYPE_CONTACT); TpHandle handle = tp_handle_lookup (contact_repo, from, NULL, NULL); /* If there's no handle for them, they're certainly not on the roster. */ if (handle == 0 || !gabble_roster_handle_gets_presence_from_us (self->roster, handle)) { wocky_porter_send_iq_error (porter, stanza, WOCKY_XMPP_ERROR_FORBIDDEN, NULL); return TRUE; } } sprintf (seconds, "%.0f", gabble_connection_get_last_use (self)); wocky_porter_acknowledge_iq (porter, stanza, '(', "query", ':', NS_LAST, '@', "seconds", seconds, ')', NULL); return TRUE; } static void connect_iq_callbacks (GabbleConnection *conn) { GabbleConnectionPrivate *priv = conn->priv; wocky_porter_register_handler_from_anyone (priv->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, iq_disco_cb, conn, '(', "query", ':', NS_DISCO_INFO, ')', NULL); wocky_porter_register_handler_from_anyone (priv->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, iq_version_cb, conn, '(', "query", ':', NS_VERSION, ')', NULL); wocky_porter_register_handler_from_anyone (priv->porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, connection_iq_last_cb, conn, '(', "query", ':', NS_LAST, ')', NULL); } /** * _gabble_connection_connect * * Use the stored server & authentication details to commence * the stages for connecting to the server and authenticating. * Will create a WockyConnector. * * Stage 1 is _gabble_connection_connect calling wocky_connector_connect_async * Stage 2 is connector_connected initiating service discovery * Stage 3 is connection_disco_cb processing the server's features, and setting * initial presence * Stage 4 is set_status_to_connected setting the CONNECTED state. */ static gboolean _gabble_connection_connect (TpBaseConnection *base, GError **error) { GabbleConnection *conn = GABBLE_CONNECTION (base); GabbleConnectionPrivate *priv = conn->priv; WockyTLSHandler *tls_handler; char *jid; gboolean interactive_tls; gchar *user_certs_dir; g_assert (priv->connector == NULL); g_assert (priv->port <= G_MAXUINT16); g_assert (priv->stream_server != NULL); g_assert (priv->resource != NULL); jid = gabble_encode_jid (priv->username, priv->stream_server, NULL); tls_handler = WOCKY_TLS_HANDLER (priv->server_tls_manager); priv->connector = wocky_connector_new (jid, priv->password, priv->resource, WOCKY_AUTH_REGISTRY (priv->auth_manager), tls_handler); g_free (jid); #ifdef GTLS_SYSTEM_CA_CERTIFICATES /* system certs */ wocky_tls_handler_add_ca (tls_handler, GTLS_SYSTEM_CA_CERTIFICATES); #endif /* user certs */ user_certs_dir = g_build_filename (g_get_user_config_dir (), "telepathy", "certs", NULL); wocky_tls_handler_add_ca (tls_handler, user_certs_dir); g_free (user_certs_dir); /* If the UI explicitly specified a port or a server, pass them to Loudmouth * rather than letting it do an SRV lookup. * * If the port is 5222 (the default) then unless the UI also specified a * server or old-style SSL, we ignore it and do an SRV lookup anyway. This * means that UIs that blindly pass the default back to Gabble work * correctly. If the user really did mean 5222, then when the SRV lookup * fails we fall back to that anyway. */ if (priv->port != 5222 || priv->connect_server != NULL || priv->old_ssl) { gchar *server; if (priv->connect_server != NULL) server = priv->connect_server; else server = priv->stream_server; DEBUG ("disabling SRV because \"server\" or \"old-ssl\" was specified " "or port was not 5222, will connect to %s", server); g_object_set (priv->connector, "xmpp-server", server, "xmpp-port", priv->port, NULL); } else { DEBUG ("letting SRV lookup decide server and port"); } /* We want to enable interactive TLS verification also in * case encryption is not required, and we don't ignore SSL errors. */ interactive_tls = !conn->priv->ignore_ssl_errors; if (!conn->priv->require_encryption && !conn->priv->ignore_ssl_errors) { DEBUG ("require-encryption is False; flipping ignore_ssl_errors to True"); conn->priv->ignore_ssl_errors = TRUE; } g_object_set (priv->connector, "old-ssl", priv->old_ssl, /* We always wants to support old servers */ "legacy", TRUE, NULL); g_object_set (tls_handler, "interactive-tls", interactive_tls, "ignore-ssl-errors", priv->ignore_ssl_errors, NULL); if (priv->old_ssl) { g_object_set (priv->connector, "tls-required", FALSE, NULL); } else { g_object_set (priv->connector, "tls-required", priv->require_encryption, "plaintext-auth-allowed", !priv->require_encryption, NULL); } priv->cancellable = g_cancellable_new (); if (priv->do_register) { DEBUG ("Start registering"); wocky_connector_register_async (priv->connector, priv->cancellable, connector_register_cb, conn); } else { DEBUG ("Start connecting"); wocky_connector_connect_async (priv->connector, priv->cancellable, connector_connect_cb, conn); } return TRUE; } static void closed_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); GabbleConnectionPrivate *priv = self->priv; TpBaseConnection *base = TP_BASE_CONNECTION (self); GError *error = NULL; if (priv->disconnect_timer != 0) { /* stop the timer */ g_source_remove (priv->disconnect_timer); priv->disconnect_timer = 0; } if (!wocky_porter_close_finish (WOCKY_PORTER (source), res, &error)) { DEBUG ("close failed: %s", error->message); if (g_error_matches (error, WOCKY_PORTER_ERROR, WOCKY_PORTER_ERROR_FORCIBLY_CLOSED)) { /* Close operation has been aborted because a force_close operation * has been started. tp_base_connection_finish_shutdown will be * called once this force_close operation is completed so we don't * do it here. */ g_error_free (error); return; } g_error_free (error); } else { DEBUG ("connection properly closed"); } tp_base_connection_finish_shutdown (base); } static gboolean disconnect_timeout_cb (gpointer data) { GabbleConnection *self = GABBLE_CONNECTION (data); GabbleConnectionPrivate *priv = self->priv; DEBUG ("Close operation timed out. Force closing"); priv->disconnect_timer = 0; wocky_porter_force_close_async (priv->porter, NULL, force_close_cb, self); return FALSE; } static void connection_shut_down (TpBaseConnection *base) { GabbleConnection *self = GABBLE_CONNECTION (base); GabbleConnectionPrivate *priv = self->priv; tp_clear_object (&priv->pinger); if (priv->closing) return; priv->closing = TRUE; if (priv->porter != NULL) { DEBUG ("connection may still be open; closing it: %p", base); g_assert (priv->disconnect_timer == 0); priv->disconnect_timer = g_timeout_add_seconds (DISCONNECT_TIMEOUT, disconnect_timeout_cb, self); wocky_porter_close_async (priv->porter, NULL, closed_cb, self); return; } else if (priv->connector != NULL) { DEBUG ("Cancelling the porter connection"); g_cancellable_cancel (priv->cancellable); return; } DEBUG ("neither porter nor connector is alive: clean up the base connection"); tp_base_connection_finish_shutdown (base); } void gabble_connection_fill_in_caps (GabbleConnection *self, WockyStanza *presence_message) { GabblePresence *presence = self->self_presence; WockyNode *node = wocky_stanza_get_top_node (presence_message); gchar *caps_hash; gboolean share_v1, voice_v1, video_v1; GString *ext = g_string_new (""); /* XEP-0115 version 1.5 uses a verification string in the 'ver' attribute */ caps_hash = caps_hash_compute_from_self_presence (self); node = wocky_node_add_child_ns (node, "c", NS_CAPS); wocky_node_set_attributes ( node, "hash", "sha-1", "node", NS_GABBLE_CAPS, "ver", caps_hash, NULL); /* Ensure this set of capabilities is in the cache. */ gabble_presence_cache_add_own_caps (self->presence_cache, caps_hash, gabble_presence_peek_caps (presence), NULL, gabble_presence_peek_data_forms (presence)); /* XEP-0115 deprecates 'ext' feature bundles. But we still need * BUNDLE_VOICE_V1 it for backward-compatibility with Gabble 0.2 */ g_string_append (ext, BUNDLE_PMUC_V1); share_v1 = gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_SHARE); voice_v1 = gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_VOICE); video_v1 = gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_VIDEO); if (share_v1) g_string_append (ext, " " BUNDLE_SHARE_V1); if (voice_v1) g_string_append (ext, " " BUNDLE_VOICE_V1); if (video_v1) g_string_append (ext, " " BUNDLE_VIDEO_V1); if (gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_CAMERA)) g_string_append (ext, " " BUNDLE_CAMERA_V1); wocky_node_set_attribute (node, "ext", ext->str); g_string_free (ext, TRUE); g_free (caps_hash); } gboolean gabble_connection_send_capabilities (GabbleConnection *self, const gchar *recipient, GError **error) { TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *) self, TP_HANDLE_TYPE_CONTACT); WockyStanza *message; gboolean ret; TpHandle handle; /* if we don't have a handle allocated for the recipient, they clearly aren't * getting our presence... */ handle = tp_handle_lookup (contact_repo, recipient, NULL, NULL); if (handle != 0 && conn_presence_visible_to (self, handle)) { /* nothing to do, they should already have had our presence */ return TRUE; } /* We deliberately don't include anything except the caps here */ message = wocky_stanza_build ( WOCKY_STANZA_TYPE_PRESENCE, WOCKY_STANZA_SUB_TYPE_AVAILABLE, NULL, recipient, NULL); gabble_connection_fill_in_caps (self, message); ret = _gabble_connection_send (self, message, error); g_object_unref (message); return ret; } gboolean gabble_connection_request_decloak (GabbleConnection *self, const gchar *to, const gchar *reason, GError **error) { GabblePresence *presence = self->self_presence; WockyStanza *message = gabble_presence_as_message (presence, to); WockyNode *decloak; gboolean ret; gabble_connection_fill_in_caps (self, message); decloak = wocky_node_add_child_ns (wocky_stanza_get_top_node (message), "temppres", NS_TEMPPRES); if (reason != NULL && *reason != '\0') { wocky_node_set_attribute (decloak, "reason", reason); } ret = _gabble_connection_send (self, message, error); g_object_unref (message); return ret; } static gboolean gabble_connection_refresh_capabilities (GabbleConnection *self, GabbleCapabilitySet **old_out) { TpBaseConnection *base = (TpBaseConnection *) self; GError *error = NULL; GHashTableIter iter; gpointer k, v; GabbleCapabilitySet *save_set; GPtrArray *data_forms; save_set = self->priv->all_caps; self->priv->all_caps = gabble_capability_set_new (); data_forms = g_ptr_array_new (); gabble_capability_set_update (self->priv->all_caps, gabble_capabilities_get_fixed_caps ()); gabble_capability_set_update (self->priv->all_caps, self->priv->notify_caps); gabble_capability_set_update (self->priv->all_caps, self->priv->legacy_caps); gabble_capability_set_update (self->priv->all_caps, self->priv->sidecar_caps); gabble_capability_set_update (self->priv->all_caps, self->priv->bonus_caps); /* first, normal caps */ g_hash_table_iter_init (&iter, self->priv->client_caps); while (g_hash_table_iter_next (&iter, &k, &v)) { if (DEBUGGING) { gchar *s = gabble_capability_set_dump (v, " "); DEBUG ("incorporating caps for %s:\n%s", (const gchar *) k, s); g_free (s); } gabble_capability_set_update (self->priv->all_caps, v); } /* now data forms */ g_hash_table_iter_init (&iter, self->priv->client_data_forms); /* just borrow the ref, data_forms doesn't have a free func */ while (g_hash_table_iter_next (&iter, &k, &v)) tp_g_ptr_array_extend (data_forms, v); if (self->self_presence != NULL) gabble_presence_set_capabilities (self->self_presence, self->priv->resource, self->priv->all_caps, data_forms, self->priv->caps_serial++); if (gabble_capability_set_equals (self->priv->all_caps, save_set)) { gabble_capability_set_free (save_set); g_ptr_array_unref (data_forms); DEBUG ("nothing to do"); return FALSE; } /* don't signal presence unless we're already CONNECTED */ if (tp_base_connection_get_status (base) != TP_CONNECTION_STATUS_CONNECTED) { gabble_capability_set_free (save_set); g_ptr_array_unref (data_forms); DEBUG ("not emitting self-presence stanza: not connected yet"); return FALSE; } if (!conn_presence_signal_own_presence (self, NULL, &error)) { gabble_capability_set_free (save_set); g_ptr_array_unref (data_forms); DEBUG ("error sending presence: %s", error->message); g_error_free (error); return FALSE; } if (old_out == NULL) gabble_capability_set_free (save_set); else *old_out = save_set; g_ptr_array_unref (data_forms); return TRUE; } /** * _gabble_connection_send_iq_result * * Function used to acknowledge an IQ stanza. */ void _gabble_connection_acknowledge_set_iq (GabbleConnection *conn, WockyStanza *iq) { wocky_porter_acknowledge_iq (wocky_session_get_porter (conn->session), iq, NULL); } static void add_feature_node (gpointer namespace, gpointer result_query) { WockyNode *feature_node; feature_node = wocky_node_add_child_with_content (result_query, "feature", NULL); wocky_node_set_attribute (feature_node, "var", namespace); } static void add_identity_node (const GabbleDiscoIdentity *identity, gpointer result_query) { WockyNode *identity_node; identity_node = wocky_node_add_child_with_content (result_query, "identity", NULL); wocky_node_set_attribute (identity_node, "category", identity->category); wocky_node_set_attribute (identity_node, "type", identity->type); if (identity->lang) wocky_node_set_attribute (identity_node, "lang", identity->lang); if (identity->name) wocky_node_set_attribute (identity_node, "name", identity->name); } /** * iq_disco_cb * * Called by Wocky when we get an incoming with a * node. This handler handles disco-related IQs. */ static gboolean iq_disco_cb (WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (user_data); WockyStanza *result; WockyNode *query, *result_query; const gchar *node, *suffix; const GabbleCapabilityInfo *info = NULL; const GabbleCapabilitySet *features = NULL; const GPtrArray *identities = NULL; const GPtrArray *data_forms = NULL; /* query's existence is checked by WockyPorter before this function is called */ query = wocky_node_get_child (wocky_stanza_get_top_node (stanza), "query"); node = wocky_node_get_attribute (query, "node"); if (node && ( 0 != strncmp (node, NS_GABBLE_CAPS "#", strlen (NS_GABBLE_CAPS) + 1) || strlen (node) < strlen (NS_GABBLE_CAPS) + 2)) { STANZA_DEBUG (stanza, "got iq disco query with unexpected node attribute"); return FALSE; } if (node == NULL) suffix = NULL; else suffix = node + strlen (NS_GABBLE_CAPS) + 1; result = wocky_stanza_build_iq_result (stanza, '(', "query", ':', NS_DISCO_INFO, '*', &result_query, ')', NULL); /* If we get an IQ without an id='', there's not much we can do. */ if (result == NULL) return FALSE; if (node) wocky_node_set_attribute (result_query, "node", node); if (node == NULL) { features = gabble_presence_peek_caps (self->self_presence); data_forms = gabble_presence_peek_data_forms (self->self_presence); } else { /* If node is not NULL, it can be either a caps bundle as defined in the * legacy XEP-0115 version 1.3 or an hash as defined in XEP-0115 version * 1.5. Let's see if it's a verification string we've told the cache about. */ info = gabble_presence_cache_peek_own_caps (self->presence_cache, suffix); } if (info) { features = info->cap_set; identities = info->identities; data_forms = info->data_forms; } if (identities && identities->len != 0) { g_ptr_array_foreach ((GPtrArray *) identities, (GFunc) add_identity_node, result_query); } else { /* Every entity MUST have at least one identity (XEP-0030). Gabble publishes * one identity. If you change the identity here, you also need to change * caps_hash_compute_from_self_presence(). */ wocky_node_add_build (result_query, '(', "identity", '@', "category", "client", '@', "name", PACKAGE_STRING, '@', "type", CLIENT_TYPE, ')', NULL); } if (features == NULL) { /* Otherwise, is it one of the caps bundles we advertise? These are not * just shoved into the cache with gabble_presence_cache_add_own_caps() * because capabilities_get_features() always includes a few bonus * features... */ if (!tp_strdiff (suffix, BUNDLE_SHARE_V1)) features = gabble_capabilities_get_bundle_share_v1 (); if (!tp_strdiff (suffix, BUNDLE_VOICE_V1)) features = gabble_capabilities_get_bundle_voice_v1 (); if (!tp_strdiff (suffix, BUNDLE_VIDEO_V1)) features = gabble_capabilities_get_bundle_video_v1 (); if (!tp_strdiff (suffix, BUNDLE_CAMERA_V1)) features = gabble_capabilities_get_bundle_camera_v1 (); } if (data_forms != NULL) { guint i; for (i = 0; i < data_forms->len; i++) { WockyDataForm *form = g_ptr_array_index (data_forms, i); wocky_data_form_add_to_node (form, result_query); } } if (features == NULL && tp_strdiff (suffix, BUNDLE_PMUC_V1)) { wocky_porter_send_iq_error (porter, stanza, WOCKY_XMPP_ERROR_ITEM_NOT_FOUND, NULL); } else { /* Send an empty reply for a pmuc-v1 disco, matching Google's behaviour. */ if (features != NULL) { gabble_capability_set_foreach (features, add_feature_node, result_query); } wocky_porter_send (self->priv->porter, result); } g_object_unref (result); return TRUE; } static gboolean iq_version_cb (WockyPorter *porter, WockyStanza *stanza, gpointer user_data) { WockyStanza *result; result = wocky_stanza_build_iq_result (stanza, '(', "query", ':', NS_VERSION, '(', "name", '$', PACKAGE_NAME, ')', '(', "version", '$', PACKAGE_VERSION, ')', ')', NULL); if (result == NULL) return FALSE; wocky_porter_send (porter, result); g_object_unref ((GObject *) result); return TRUE; } /** * set_status_to_connected * * Stage 4 of connecting, this function is called once all the events we were * waiting for happened. * */ static void set_status_to_connected (GabbleConnection *conn) { TpBaseConnection *base = (TpBaseConnection *) conn; if (tp_base_connection_get_status (base) == TP_CONNECTION_STATUS_DISCONNECTED) { /* We already failed to connect, but at the time an async thing was * still pending, and now it has finished. Do nothing special. */ return; } if (conn->features & GABBLE_CONNECTION_FEATURES_PEP) { const gchar *ifaces[] = { GABBLE_IFACE_OLPC_BUDDY_INFO, GABBLE_IFACE_OLPC_ACTIVITY_PROPERTIES, NULL }; tp_base_connection_add_interfaces ((TpBaseConnection *) conn, ifaces); } if (conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_MAIL_NOTIFY) { const gchar *ifaces[] = { TP_IFACE_CONNECTION_INTERFACE_MAIL_NOTIFICATION, NULL }; tp_base_connection_add_interfaces ((TpBaseConnection *) conn, ifaces); } if (tp_base_contact_list_can_block (gabble_connection_get_contact_list (conn))) { const gchar *ifaces[] = { TP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING, NULL }; tp_base_connection_add_interfaces ((TpBaseConnection *) conn, ifaces); } /* go go gadget on-line */ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); } static void decrement_waiting_connected (GabbleConnection *conn) { conn->priv->waiting_connected--; if (conn->priv->waiting_connected == 0) set_status_to_connected (conn); } static void conn_wlm_jid_lookup_cb (GObject *source, GAsyncResult *result, gpointer user_data) { GabbleConnection *conn = (GabbleConnection *) source; GSimpleAsyncResult *my_result = user_data; WockyStanza *iq = NULL; WockyNode *node; const gchar *jid = NULL; GError *error = NULL; if (!conn_util_send_iq_finish (conn, result, &iq, &error)) { g_simple_async_result_take_error (my_result, error); goto out; } node = wocky_node_get_child_ns (wocky_stanza_get_top_node (iq), "getjid", NS_WLM_JID_LOOKUP); if (node != NULL) { jid = wocky_node_get_content_from_child (node, "jid"); } if (!tp_str_empty (jid)) { g_simple_async_result_set_op_res_gpointer (my_result, g_strdup (jid), g_free); } else { g_simple_async_result_set_error (my_result, TP_ERROR, TP_ERROR_INVALID_HANDLE, "jid not found in getjid reply"); } out: g_simple_async_result_complete (my_result); g_object_unref (my_result); } static void conn_wlm_jid_lookup_async (TpHandleRepoIface *repo, TpBaseConnection *base, const gchar *id, gpointer context, GAsyncReadyCallback callback, gpointer user_data) { GabbleConnection *self = GABBLE_CONNECTION (base); WockyStanza *iq; GSimpleAsyncResult *result; gchar *normal_id; GError *error = NULL; result = g_simple_async_result_new ((GObject *) repo, callback, user_data, conn_wlm_jid_lookup_async); /* First, do local normalization */ normal_id = gabble_normalize_contact (repo, id, context, &error); if (normal_id == NULL) { g_simple_async_result_take_error (result, error); g_simple_async_result_complete_in_idle (result); g_object_unref (result); return; } /* If it is already a WLM jid, return it */ if (g_str_has_suffix (normal_id, "@messenger.live.com")) { g_simple_async_result_set_op_res_gpointer (result, normal_id, g_free); g_simple_async_result_complete_in_idle (result); g_object_unref (result); return; } /* Ask the server to give a WLM jid */ iq = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, conn_util_get_bare_self_jid (self), normal_id, '(', "getjid", ':', NS_WLM_JID_LOOKUP, ')', NULL); conn_util_send_iq_async (self, iq, NULL, conn_wlm_jid_lookup_cb, result); g_object_unref (iq); g_free (normal_id); } static gchar * conn_wlm_jid_lookup_finish (TpHandleRepoIface *repo, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple = (GSimpleAsyncResult *) result; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (repo), conn_wlm_jid_lookup_async), NULL); if (g_simple_async_result_propagate_error (simple, error)) return NULL; return g_strdup (g_simple_async_result_get_op_res_gpointer (simple)); } static void connection_disco_cb (GabbleDisco *disco, GabbleDiscoRequest *request, const gchar *jid, const gchar *node, WockyNode *result, GError *disco_error, gpointer user_data) { GabbleConnection *conn = GABBLE_CONNECTION (user_data); TpBaseConnection *base = (TpBaseConnection *) conn; if (tp_base_connection_get_status (base) != TP_CONNECTION_STATUS_CONNECTING) { g_assert (tp_base_connection_get_status (base) == TP_CONNECTION_STATUS_DISCONNECTED); return; } if (disco_error) { DEBUG ("got disco error, setting no features: %s", disco_error->message); if (disco_error->code == GABBLE_DISCO_ERROR_TIMEOUT) { DEBUG ("didn't receive a response to our disco request: disconnect"); goto ERROR; } } else { WockyNodeIter i; WockyNode *child; NODE_DEBUG (result, "got"); wocky_node_iter_init (&i, result, NULL, NULL); while (wocky_node_iter_next (&i, &child)) { if (0 == strcmp (child->name, "identity")) { const gchar *category = wocky_node_get_attribute (child, "category"); const gchar *type = wocky_node_get_attribute (child, "type"); if (!tp_strdiff (category, "pubsub") && !tp_strdiff (type, "pep")) { DEBUG ("Server advertises PEP support in its features"); conn->features |= GABBLE_CONNECTION_FEATURES_PEP; } } else if (0 == strcmp (child->name, "feature")) { const gchar *var = wocky_node_get_attribute (child, "var"); if (var == NULL) continue; if (0 == strcmp (var, NS_GOOGLE_ROSTER)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER; #ifdef ENABLE_VOIP else if (0 == strcmp (var, NS_GOOGLE_JINGLE_INFO)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO; #endif else if (0 == strcmp (var, NS_PRESENCE_INVISIBLE)) conn->features |= GABBLE_CONNECTION_FEATURES_PRESENCE_INVISIBLE; else if (0 == strcmp (var, NS_PRIVACY)) conn->features |= GABBLE_CONNECTION_FEATURES_PRIVACY; else if (0 == strcmp (var, NS_INVISIBLE)) conn->features |= GABBLE_CONNECTION_FEATURES_INVISIBLE; else if (0 == strcmp (var, NS_GOOGLE_MAIL_NOTIFY)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_MAIL_NOTIFY; else if (0 == strcmp (var, NS_GOOGLE_SHARED_STATUS)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_SHARED_STATUS; else if (0 == strcmp (var, NS_GOOGLE_QUEUE)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_QUEUE; else if (0 == strcmp (var, NS_GOOGLE_SETTING)) conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_SETTING; else if (0 == strcmp (var, NS_WLM_JID_LOOKUP)) conn->features |= GABBLE_CONNECTION_FEATURES_WLM_JID_LOOKUP; } } DEBUG ("set features flags to %d", conn->features); } if ((conn->features & GABBLE_CONNECTION_FEATURES_WLM_JID_LOOKUP) != 0) { TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); tp_dynamic_handle_repo_set_normalize_async ( (TpDynamicHandleRepo *) contact_repo, conn_wlm_jid_lookup_async, conn_wlm_jid_lookup_finish); } conn_presence_set_initial_presence_async (conn, connection_initial_presence_cb, NULL); return; ERROR: tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); return; } /** * connection_initial_presence_cb * * Stage 4 of connecting, this function is called after our initial presence * has been set (or has failed unrecoverably). It marks the connection as * online (or failed, as appropriate); the former triggers the roster being * retrieved. */ static void connection_initial_presence_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GabbleConnection *self = (GabbleConnection *) source_object; TpBaseConnection *base = (TpBaseConnection *) self; GError *error = NULL; if (!conn_presence_set_initial_presence_finish (self, res, &error)) { DEBUG ("error setting up initial presence: %s", error->message); if (tp_base_connection_get_status (base) != TP_CONNECTION_STATUS_DISCONNECTED) tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); g_error_free (error); } else { decrement_waiting_connected (self); } } /**************************************************************************** * D-BUS EXPORTED METHODS * ****************************************************************************/ static void gabble_free_rcc_list (GPtrArray *rccs) { g_boxed_free (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs); } /** * gabble_connection_build_contact_caps: * @handle: a contact * @caps: @handle's XMPP capabilities * * Returns: an array containing the channel classes corresponding to @caps. */ static GPtrArray * gabble_connection_build_contact_caps ( GabbleConnection *self, TpHandle handle, const GabbleCapabilitySet *caps) { TpBaseConnection *base_conn = TP_BASE_CONNECTION (self); TpChannelManagerIter iter; TpChannelManager *manager; GPtrArray *ret = g_ptr_array_new (); tp_base_connection_channel_manager_iter_init (&iter, base_conn); while (tp_base_connection_channel_manager_iter_next (&iter, &manager)) { if (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager)) { gabble_caps_channel_manager_get_contact_capabilities ( GABBLE_CAPS_CHANNEL_MANAGER (manager), handle, caps, ret); } } return ret; } static void _emit_capabilities_changed (GabbleConnection *conn, TpHandle handle, const GabbleCapabilitySet *old_set, const GabbleCapabilitySet *new_set) { GPtrArray *caps_arr; const CapabilityConversionData *ccd; GHashTable *hash; guint i; if (gabble_capability_set_equals (old_set, new_set)) return; /* o.f.T.C.Capabilities */ caps_arr = g_ptr_array_new (); for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) { guint old_specific = ccd->c2tf_fn (old_set); guint new_specific = ccd->c2tf_fn (new_set); if (old_specific != 0 || new_specific != 0) { GValue caps_monster_struct = {0, }; guint old_generic = old_specific ? TP_CONNECTION_CAPABILITY_FLAG_CREATE | TP_CONNECTION_CAPABILITY_FLAG_INVITE : 0; guint new_generic = new_specific ? TP_CONNECTION_CAPABILITY_FLAG_CREATE | TP_CONNECTION_CAPABILITY_FLAG_INVITE : 0; if (0 == (old_specific ^ new_specific)) continue; g_value_init (&caps_monster_struct, TP_STRUCT_TYPE_CAPABILITY_CHANGE); g_value_take_boxed (&caps_monster_struct, dbus_g_type_specialized_construct (TP_STRUCT_TYPE_CAPABILITY_CHANGE)); dbus_g_type_struct_set (&caps_monster_struct, 0, handle, 1, ccd->iface, 2, old_generic, 3, new_generic, 4, old_specific, 5, new_specific, G_MAXUINT); g_ptr_array_add (caps_arr, g_value_get_boxed (&caps_monster_struct)); } } if (caps_arr->len) tp_svc_connection_interface_capabilities_emit_capabilities_changed ( conn, caps_arr); for (i = 0; i < caps_arr->len; i++) { g_boxed_free (TP_STRUCT_TYPE_CAPABILITY_CHANGE, g_ptr_array_index (caps_arr, i)); } g_ptr_array_unref (caps_arr); /* o.f.T.C.ContactCapabilities */ caps_arr = gabble_connection_build_contact_caps (conn, handle, new_set); hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) gabble_free_rcc_list); g_hash_table_insert (hash, GUINT_TO_POINTER (handle), caps_arr); tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed ( conn, hash); g_hash_table_unref (hash); } static const GabbleCapabilitySet * empty_caps_set (void) { static GabbleCapabilitySet *empty = NULL; if (G_UNLIKELY (empty == NULL)) empty = gabble_capability_set_new (); return empty; } /* * gabble_connection_get_handle_contact_capabilities: * * Returns: an array of channel classes representing @handle's capabilities */ static GPtrArray * gabble_connection_get_handle_contact_capabilities ( GabbleConnection *self, TpHandle handle) { TpBaseConnection *base = TP_BASE_CONNECTION (self); GabblePresence *p; const GabbleCapabilitySet *caps; if (handle == tp_base_connection_get_self_handle (base)) p = self->self_presence; else p = gabble_presence_cache_get (self->presence_cache, handle); if (p == NULL) caps = empty_caps_set (); else caps = gabble_presence_peek_caps (p); return gabble_connection_build_contact_caps (self, handle, caps); } static void connection_capabilities_update_cb (GabblePresenceCache *cache, TpHandle handle, const GabbleCapabilitySet *old_cap_set, const GabbleCapabilitySet *new_cap_set, gpointer user_data) { GabbleConnection *conn = GABBLE_CONNECTION (user_data); _emit_capabilities_changed (conn, handle, old_cap_set, new_cap_set); } /** * gabble_connection_advertise_capabilities * * Implements D-Bus method AdvertiseCapabilities * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities * * @error: Used to return a pointer to a GError detailing any error * that occurred, D-Bus will throw the error only if this * function returns FALSE. * * Returns: TRUE if successful, FALSE if an error was thrown. */ static void gabble_connection_advertise_capabilities (TpSvcConnectionInterfaceCapabilities *iface, const GPtrArray *add, const gchar **del, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; guint i; GabbleConnectionPrivate *priv = self->priv; const CapabilityConversionData *ccd; GPtrArray *ret; GabbleCapabilitySet *save_set; GabbleCapabilitySet *add_set, *remove_set; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); /* Now that someone has told us our *actual* capabilities, we can stop * advertising spurious caps in initial presence */ gabble_capability_set_clear (self->priv->bonus_caps); add_set = gabble_capability_set_new (); remove_set = gabble_capability_set_new (); for (i = 0; i < add->len; i++) { GValue iface_flags_pair = {0, }; gchar *channel_type; guint flags; g_value_init (&iface_flags_pair, TP_STRUCT_TYPE_CAPABILITY_PAIR); g_value_set_static_boxed (&iface_flags_pair, g_ptr_array_index (add, i)); dbus_g_type_struct_get (&iface_flags_pair, 0, &channel_type, 1, &flags, G_MAXUINT); for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) if (g_str_equal (channel_type, ccd->iface)) ccd->tf2c_fn (flags, add_set); g_free (channel_type); } for (i = 0; NULL != del[i]; i++) { for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) if (g_str_equal (del[i], ccd->iface)) ccd->tf2c_fn (~0, remove_set); } gabble_capability_set_update (priv->legacy_caps, add_set); gabble_capability_set_exclude (priv->legacy_caps, remove_set); if (DEBUGGING) { gchar *add_str = gabble_capability_set_dump (add_set, " "); gchar *remove_str = gabble_capability_set_dump (remove_set, " "); DEBUG ("caps to add:\n%s", add_str); DEBUG ("caps to remove:\n%s", remove_str); g_free (add_str); g_free (remove_str); } gabble_capability_set_free (add_set); gabble_capability_set_free (remove_set); if (gabble_connection_refresh_capabilities (self, &save_set)) { _emit_capabilities_changed (self, tp_base_connection_get_self_handle (base), save_set, priv->all_caps); gabble_capability_set_free (save_set); } ret = g_ptr_array_new (); for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) { guint tp_caps = ccd->c2tf_fn (self->priv->all_caps); if (tp_caps != 0) { GValue iface_flags_pair = {0, }; g_value_init (&iface_flags_pair, TP_STRUCT_TYPE_CAPABILITY_PAIR); g_value_take_boxed (&iface_flags_pair, dbus_g_type_specialized_construct ( TP_STRUCT_TYPE_CAPABILITY_PAIR)); dbus_g_type_struct_set (&iface_flags_pair, 0, ccd->iface, 1, tp_caps, G_MAXUINT); g_ptr_array_add (ret, g_value_get_boxed (&iface_flags_pair)); } } tp_svc_connection_interface_capabilities_return_from_advertise_capabilities ( context, ret); g_ptr_array_foreach (ret, (GFunc) g_value_array_free, NULL); g_ptr_array_unref (ret); } static const gchar * get_form_type (WockyDataForm *form) { WockyDataFormField *field; field = g_hash_table_lookup (form->fields, "FORM_TYPE"); g_assert (field != NULL); return field->raw_value_contents[0]; } static const gchar * check_form_is_unique (GabbleConnection *self, const gchar *form_type) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, self->priv->client_data_forms); while (g_hash_table_iter_next (&iter, &key, &value)) { const gchar *manager = key; GPtrArray *data_forms = value; guint i; if (data_forms == NULL || data_forms->len == 0) continue; for (i = 0; i < data_forms->len; i++) { WockyDataForm *form = g_ptr_array_index (data_forms, i); if (!tp_strdiff (get_form_type (form), form_type)) return manager; } } return NULL; } static gboolean check_data_form_in_list (GPtrArray *array, const gchar *form_type) { guint i; for (i = 0; i < array->len; i++) { WockyDataForm *form = g_ptr_array_index (array, i); if (!tp_strdiff (get_form_type (form), form_type)) return TRUE; } return FALSE; } static gboolean check_data_form_is_valid (GabbleConnection *self, WockyDataForm *form, GPtrArray *existing_forms) { WockyDataFormField *field; const gchar *form_type, *other_client; /* We want rid of forms with no FORM_TYPE quickly. */ field = g_hash_table_lookup (form->fields, "FORM_TYPE"); if (field == NULL || tp_str_empty (field->raw_value_contents[0])) { WARNING ("data form with no FORM_TYPE field; ignoring"); return FALSE; } form_type = field->raw_value_contents[0]; /* We'll get warnings (potentially bad) if two clients cause a * channel manager to create two data forms with the same FORM_TYPE, * or if multiple channel managers create two data forms with the * same FORM_TYPE, for the same client. This is probably not a * problem in practice given hardly anyone uses data forms in entity * capabilities anyway. */ /* We don't want the same data form from another caps channel * manager for this client either */ if (check_data_form_in_list (existing_forms, form_type)) { WARNING ("duplicate data form '%s' from another channel " "manager; ignoring", form_type); return FALSE; } /* And lastly we don't want a form we're already advertising. */ other_client = check_form_is_unique (self, form_type); if (other_client != NULL) { WARNING ("Data form '%s' already provided by client " "%s; ignoring", form_type, other_client); return FALSE; } return TRUE; } /** * gabble_connection_update_capabilities * * Implements D-Bus method UpdateCapabilities * on interface * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities */ static void gabble_connection_update_capabilities ( TpSvcConnectionInterfaceContactCapabilities *iface, const GPtrArray *clients, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; GabbleCapabilitySet *old_caps = NULL; guint i; /* Now that someone has told us our *actual* capabilities, we can stop * advertising spurious caps in initial presence */ gabble_capability_set_clear (self->priv->bonus_caps); DEBUG ("enter"); for (i = 0; i < clients->len; i++) { GValueArray *va = g_ptr_array_index (clients, i); const gchar *client_name = g_value_get_string (va->values + 0); const GPtrArray *filters = g_value_get_boxed (va->values + 1); const gchar * const * cap_tokens = g_value_get_boxed (va->values + 2); GabbleCapabilitySet *cap_set; GPtrArray *data_forms; TpChannelManagerIter iter; TpChannelManager *manager; g_hash_table_remove (self->priv->client_caps, client_name); g_hash_table_remove (self->priv->client_data_forms, client_name); if ((cap_tokens == NULL || cap_tokens[0] == NULL) && filters->len == 0) { /* no capabilities */ DEBUG ("client %s can't do anything", client_name); continue; } cap_set = gabble_capability_set_new (); data_forms = g_ptr_array_new_with_free_func ( (GDestroyNotify) g_object_unref); tp_base_connection_channel_manager_iter_init (&iter, base); while (tp_base_connection_channel_manager_iter_next (&iter, &manager)) { if (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager)) { GPtrArray *forms = g_ptr_array_new_with_free_func ( (GDestroyNotify) g_object_unref); guint j; /* First, represent the client... */ gabble_caps_channel_manager_represent_client ( GABBLE_CAPS_CHANNEL_MANAGER (manager), client_name, filters, cap_tokens, cap_set, forms); /* Now check the forms... */ for (j = 0; j < forms->len; j++) { WockyDataForm *form = g_ptr_array_index (forms, j); if (check_data_form_is_valid (self, form, data_forms)) g_ptr_array_add (data_forms, g_object_ref (form)); } g_ptr_array_unref (forms); } } /* first deal with normal caps */ if (gabble_capability_set_size (cap_set) > 0) { if (DEBUGGING) { gchar *s = gabble_capability_set_dump (cap_set, " "); DEBUG ("client %s contributes:\n%s", client_name, s); g_free (s); } g_hash_table_insert (self->priv->client_caps, g_strdup (client_name), cap_set); } else { DEBUG ("client %s has no interesting capabilities", client_name); gabble_capability_set_free (cap_set); } /* now data forms */ if (data_forms->len > 0) { guint j; /* now print out what forms we have here */ DEBUG ("client %s contributes %u data form%s:", client_name, data_forms->len, data_forms->len > 1 ? "s" : ""); for (j = 0; j < data_forms->len; j++) DEBUG (" - %s", get_form_type (g_ptr_array_index (data_forms, j))); g_hash_table_insert (self->priv->client_data_forms, g_strdup (client_name), data_forms); } else { DEBUG ("client %s has no interesting data forms", client_name); g_ptr_array_unref (data_forms); } } if (gabble_connection_refresh_capabilities (self, &old_caps)) { _emit_capabilities_changed (self, tp_base_connection_get_self_handle (base), old_caps, self->priv->all_caps); gabble_capability_set_free (old_caps); } tp_svc_connection_interface_contact_capabilities_return_from_update_capabilities ( context); } static const gchar *assumed_caps[] = { TP_IFACE_CHANNEL_TYPE_TEXT, NULL }; /** * gabble_connection_get_handle_capabilities * * Add capabilities of handle to the given GPtrArray */ static void gabble_connection_get_handle_capabilities (GabbleConnection *self, TpHandle handle, GPtrArray *arr) { TpBaseConnection *base = (TpBaseConnection *) self; GabblePresence *pres; const CapabilityConversionData *ccd; guint typeflags; const gchar **assumed; if (0 == handle) { /* obsolete request for the connection's capabilities, do nothing */ return; } if (handle == tp_base_connection_get_self_handle (base)) pres = self->self_presence; else pres = gabble_presence_cache_get (self->presence_cache, handle); if (NULL != pres) { const GabbleCapabilitySet *cap_set = gabble_presence_peek_caps (pres); for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) { typeflags = ccd->c2tf_fn (cap_set); if (typeflags) { GValue monster = {0, }; g_value_init (&monster, TP_STRUCT_TYPE_CONTACT_CAPABILITY); g_value_take_boxed (&monster, dbus_g_type_specialized_construct ( TP_STRUCT_TYPE_CONTACT_CAPABILITY)); dbus_g_type_struct_set (&monster, 0, handle, 1, ccd->iface, 2, TP_CONNECTION_CAPABILITY_FLAG_CREATE | TP_CONNECTION_CAPABILITY_FLAG_INVITE, 3, typeflags, G_MAXUINT); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } } } for (assumed = assumed_caps; NULL != *assumed; assumed++) { GValue monster = {0, }; g_value_init (&monster, TP_STRUCT_TYPE_CONTACT_CAPABILITY); g_value_take_boxed (&monster, dbus_g_type_specialized_construct ( TP_STRUCT_TYPE_CONTACT_CAPABILITY)); dbus_g_type_struct_set (&monster, 0, handle, 1, *assumed, 2, TP_CONNECTION_CAPABILITY_FLAG_CREATE | TP_CONNECTION_CAPABILITY_FLAG_INVITE, 3, 0, G_MAXUINT); g_ptr_array_add (arr, g_value_get_boxed (&monster)); } } static void conn_capabilities_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { GabbleConnection *self = GABBLE_CONNECTION (obj); guint i; GPtrArray *array = NULL; for (i = 0; i < contacts->len; i++) { TpHandle handle = g_array_index (contacts, TpHandle, i); if (array == NULL) array = g_ptr_array_new (); gabble_connection_get_handle_capabilities (self, handle, array); if (array->len > 0) { GValue *val = tp_g_value_slice_new ( TP_ARRAY_TYPE_CONTACT_CAPABILITY_LIST); g_value_take_boxed (val, array); tp_contacts_mixin_set_contact_attribute (attributes_hash, handle, TP_IFACE_CONNECTION_INTERFACE_CAPABILITIES"/caps", val); array = NULL; } } if (array != NULL) g_ptr_array_unref (array); } static void conn_contact_capabilities_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { GabbleConnection *self = GABBLE_CONNECTION (obj); guint i; for (i = 0; i < contacts->len; i++) { TpHandle handle = g_array_index (contacts, TpHandle, i); GValue *val = tp_g_value_slice_new_take_boxed ( TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, gabble_connection_get_handle_contact_capabilities (self, handle)); tp_contacts_mixin_set_contact_attribute (attributes_hash, handle, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES"/capabilities", val); } } /** * gabble_connection_get_capabilities * * Implements D-Bus method GetCapabilities * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities */ static void gabble_connection_get_capabilities (TpSvcConnectionInterfaceCapabilities *iface, const GArray *handles, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); guint i; GPtrArray *ret; GError *error = NULL; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); if (!tp_handles_are_valid (contact_handles, handles, TRUE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } ret = g_ptr_array_new (); for (i = 0; i < handles->len; i++) { TpHandle handle = g_array_index (handles, TpHandle, i); gabble_connection_get_handle_capabilities (self, handle, ret); } tp_svc_connection_interface_capabilities_return_from_get_capabilities ( context, ret); for (i = 0; i < ret->len; i++) { g_value_array_free (g_ptr_array_index (ret, i)); } g_ptr_array_unref (ret); } /** * gabble_connection_get_contact_capabilities * * Implements D-Bus method GetContactCapabilities * on interface * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities */ static void gabble_connection_get_contact_capabilities ( TpSvcConnectionInterfaceContactCapabilities *iface, const GArray *handles, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); guint i; GHashTable *ret; GError *error = NULL; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); if (!tp_handles_are_valid (contact_handles, handles, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } ret = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) gabble_free_rcc_list); for (i = 0; i < handles->len; i++) { TpHandle handle = g_array_index (handles, TpHandle, i); GPtrArray *arr; arr = gabble_connection_get_handle_contact_capabilities (self, handle); g_hash_table_insert (ret, GUINT_TO_POINTER (handle), arr); } tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities (context, ret); g_hash_table_unref (ret); } const char * _gabble_connection_find_conference_server (GabbleConnection *conn) { GabbleConnectionPrivate *priv; g_assert (GABBLE_IS_CONNECTION (conn)); priv = conn->priv; if (priv->conference_server == NULL) { /* Find first server that has NS_MUC feature */ const GabbleDiscoItem *item = gabble_disco_service_find (conn->disco, "conference", "text", NS_MUC); if (item != NULL) priv->conference_server = item->jid; } if (priv->conference_server == NULL) priv->conference_server = priv->fallback_conference_server; return priv->conference_server; } gchar * gabble_connection_get_canonical_room_name (GabbleConnection *conn, const gchar *name) { const gchar *server; g_assert (GABBLE_IS_CONNECTION (conn)); if (strchr (name, '@')) return g_strdup (name); server = _gabble_connection_find_conference_server (conn); if (server == NULL) return NULL; return gabble_encode_jid (name, server, NULL); } void gabble_connection_ensure_capabilities (GabbleConnection *self, const GabbleCapabilitySet *ensured) { gabble_capability_set_update (self->priv->notify_caps, ensured); gabble_connection_refresh_capabilities (self, NULL); } gboolean gabble_connection_send_presence (GabbleConnection *conn, WockyStanzaSubType sub_type, const gchar *contact, const gchar *status, GError **error) { WockyStanza *message; gboolean result; message = wocky_stanza_build (WOCKY_STANZA_TYPE_PRESENCE, sub_type, NULL, contact, NULL); if (WOCKY_STANZA_SUB_TYPE_SUBSCRIBE == sub_type) lm_message_node_add_own_nick ( wocky_stanza_get_top_node (message), conn); if (!tp_str_empty (status)) wocky_node_add_child_with_content ( wocky_stanza_get_top_node (message), "status", status); result = _gabble_connection_send (conn, message, error); g_object_unref (message); return result; } static void capabilities_service_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfaceCapabilitiesClass *klass = (TpSvcConnectionInterfaceCapabilitiesClass *) g_iface; #define IMPLEMENT(x) tp_svc_connection_interface_capabilities_implement_##x (\ klass, gabble_connection_##x) IMPLEMENT(advertise_capabilities); IMPLEMENT(get_capabilities); #undef IMPLEMENT } static void gabble_conn_contact_caps_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfaceContactCapabilitiesClass *klass = g_iface; #define IMPLEMENT(x) \ tp_svc_connection_interface_contact_capabilities_implement_##x (\ klass, gabble_connection_##x) IMPLEMENT(get_contact_capabilities); IMPLEMENT(update_capabilities); #undef IMPLEMENT } /* For unit tests only */ void gabble_connection_set_disco_reply_timeout (guint timeout) { disco_reply_timeout = timeout; } void gabble_connection_update_sidecar_capabilities (GabbleConnection *self, const GabbleCapabilitySet *add_set, const GabbleCapabilitySet *remove_set) { TpBaseConnection *base = TP_BASE_CONNECTION (self); GabbleConnectionPrivate *priv = self->priv; GabbleCapabilitySet *save_set; if (add_set == NULL && remove_set == NULL) return; if (add_set != NULL) gabble_capability_set_update (priv->sidecar_caps, add_set); if (remove_set != NULL) gabble_capability_set_exclude (priv->sidecar_caps, remove_set); if (DEBUGGING) { if (add_set != NULL) { gchar *add_str = gabble_capability_set_dump (add_set, " "); DEBUG ("sidecar caps to add:\n%s", add_str); g_free (add_str); } if (remove_set != NULL) { gchar *remove_str = gabble_capability_set_dump (remove_set, " "); DEBUG ("sidecar caps to remove:\n%s", remove_str); g_free (remove_str); } } if (gabble_connection_refresh_capabilities (self, &save_set)) { _emit_capabilities_changed (self, tp_base_connection_get_self_handle (base), save_set, priv->all_caps); gabble_capability_set_free (save_set); } } /* identities is actually a WockyDiscoIdentityArray */ gchar * gabble_connection_add_sidecar_own_caps_full (GabblePluginConnection *self, const GabbleCapabilitySet *cap_set, const GPtrArray *identities, GPtrArray *data_forms) { GabbleConnection *conn = GABBLE_CONNECTION (self); GPtrArray *identities_copy = ((identities == NULL) ? wocky_disco_identity_array_new () : wocky_disco_identity_array_copy (identities)); gchar *ver; /* XEP-0030 requires at least 1 identity. We don't need more. */ if (identities_copy->len == 0) g_ptr_array_add (identities_copy, wocky_disco_identity_new ("client", CLIENT_TYPE, NULL, PACKAGE_STRING)); ver = gabble_caps_hash_compute_full (cap_set, identities_copy, data_forms); gabble_presence_cache_add_own_caps (conn->presence_cache, ver, cap_set, identities_copy, data_forms); wocky_disco_identity_array_free (identities_copy); return ver; } gchar * gabble_connection_add_sidecar_own_caps (GabblePluginConnection *self, const GabbleCapabilitySet *cap_set, const GPtrArray *identities) { return gabble_connection_add_sidecar_own_caps_full (self, cap_set, identities, NULL); } const gchar * gabble_connection_get_jid_for_caps (GabblePluginConnection *plugin_conn, WockyXep0115Capabilities *caps) { TpHandle handle; TpBaseConnection *base; TpHandleRepoIface *contact_handles; GabbleConnection *conn = GABBLE_CONNECTION (plugin_conn); g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); g_return_val_if_fail (GABBLE_IS_PRESENCE (caps), NULL); base = (TpBaseConnection *) conn; if ((GabblePresence *) caps == conn->self_presence) { handle = tp_base_connection_get_self_handle (base); } else { handle = gabble_presence_cache_get_handle (conn->presence_cache, (GabblePresence *) caps); } contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); return tp_handle_inspect (contact_handles, handle); } const gchar * gabble_connection_pick_best_resource_for_caps ( GabblePluginConnection *plugin_connection, const gchar *jid, GabbleCapabilitySetPredicate predicate, gconstpointer user_data) { TpBaseConnection *base; TpHandleRepoIface *contact_handles; TpHandle handle; GabbleConnection *connection = GABBLE_CONNECTION (plugin_connection); GabblePresence *presence; g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); g_return_val_if_fail (!tp_str_empty (jid), NULL); base = (TpBaseConnection *) connection; contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); handle = tp_handle_ensure (contact_handles, jid, NULL, NULL); if (handle == 0) return NULL; presence = gabble_presence_cache_get (connection->presence_cache, handle); if (presence == NULL) return NULL; return gabble_presence_pick_resource_by_caps (presence, 0, predicate, user_data); } TpBaseContactList * gabble_connection_get_contact_list (GabbleConnection *connection) { g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); return (TpBaseContactList *) connection->roster; } static TpBaseContactList * _gabble_plugin_connection_get_contact_list ( GabblePluginConnection *plugin_connection) { GabbleConnection *connection = GABBLE_CONNECTION (plugin_connection); return gabble_connection_get_contact_list (connection); } WockyXep0115Capabilities * gabble_connection_get_caps (GabblePluginConnection *plugin_connection, TpHandle handle) { GabblePresence *presence; GabbleConnection *connection = GABBLE_CONNECTION (plugin_connection); g_return_val_if_fail (GABBLE_IS_CONNECTION (connection), NULL); g_return_val_if_fail (handle > 0, NULL); presence = gabble_presence_cache_get (connection->presence_cache, handle); return (WockyXep0115Capabilities *) presence; }