/* * server-sasl-channel.c - Source for GabbleServerSaslChannel * Copyright (C) 2010 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "server-sasl-channel.h" #include "gabble-signals-marshal.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #include #include #include #define DEBUG_FLAG GABBLE_DEBUG_AUTH #include #include "connection.h" #include "debug.h" #include "namespaces.h" #include "util.h" static void sasl_auth_iface_init (gpointer, gpointer); G_DEFINE_TYPE_WITH_CODE (GabbleServerSaslChannel, gabble_server_sasl_channel, TP_TYPE_BASE_CHANNEL, G_IMPLEMENT_INTERFACE ( TP_TYPE_SVC_CHANNEL_TYPE_SERVER_AUTHENTICATION, NULL); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_SECURABLE, NULL); G_IMPLEMENT_INTERFACE ( TP_TYPE_SVC_CHANNEL_INTERFACE_SASL_AUTHENTICATION, sasl_auth_iface_init)); enum { /* server authentication channel */ PROP_AUTH_METHOD = 1, /* sasl authentication channel */ PROP_AVAILABLE_MECHANISMS, PROP_HAS_INITIAL_DATA, PROP_CAN_TRY_AGAIN, PROP_SECURE, PROP_SASL_STATUS, PROP_SASL_ERROR, PROP_SASL_ERROR_DETAILS, PROP_AUTHORIZATION_IDENTITY, PROP_DEFAULT_USERNAME, PROP_DEFAULT_REALM, LAST_PROPERTY, }; /* private structure */ struct _GabbleServerSaslChannelPrivate { /* Immutable SASL properties */ GStrv available_mechanisms; gboolean secure; /* Mutable SASL properties */ TpSASLStatus sasl_status; gchar *sasl_error; GHashTable *sasl_error_details; /* Given to the Connection on request */ TpConnectionStatusReason disconnect_reason; GError *wocky_auth_error /* = NULL */; GSimpleAsyncResult *result; }; static GPtrArray * gabble_server_sasl_channel_get_interfaces (TpBaseChannel *base) { GPtrArray *interfaces; interfaces = TP_BASE_CHANNEL_CLASS ( gabble_server_sasl_channel_parent_class)->get_interfaces (base); g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION); g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_SECURABLE); return interfaces; } static void gabble_server_sasl_channel_init (GabbleServerSaslChannel *self) { GabbleServerSaslChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GABBLE_TYPE_SERVER_SASL_CHANNEL, GabbleServerSaslChannelPrivate); self->priv = priv; priv->sasl_status = TP_SASL_STATUS_NOT_STARTED; priv->sasl_error = NULL; priv->sasl_error_details = tp_asv_new (NULL, NULL); /* a safe assumption if we don't set anything else */ priv->disconnect_reason = TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED; } static void gabble_server_sasl_channel_fill_immutable_properties (TpBaseChannel *channel, GHashTable *properties) { TP_BASE_CHANNEL_CLASS (gabble_server_sasl_channel_parent_class) ->fill_immutable_properties (channel, properties); tp_dbus_properties_mixin_fill_properties_hash (G_OBJECT (channel), properties, TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION, "AuthenticationMethod", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "AvailableMechanisms", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "HasInitialData", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "CanTryAgain", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "AuthorizationIdentity", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "DefaultRealm", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, "DefaultUsername", TP_IFACE_CHANNEL_INTERFACE_SECURABLE, "Encrypted", TP_IFACE_CHANNEL_INTERFACE_SECURABLE, "Verified", NULL); } static gchar * gabble_server_sasl_channel_get_object_path_suffix (TpBaseChannel *channel) { return g_strdup ("ServerSASLChannel"); } static void gabble_server_sasl_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpBaseChannel *channel = TP_BASE_CHANNEL (object); GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (object); GabbleServerSaslChannelPrivate *priv = self->priv; switch (property_id) { case PROP_SASL_STATUS: g_value_set_uint (value, priv->sasl_status); break; case PROP_SASL_ERROR: g_value_set_string (value, priv->sasl_error); break; case PROP_SASL_ERROR_DETAILS: g_value_set_boxed (value, priv->sasl_error_details); break; case PROP_AUTH_METHOD: g_value_set_static_string (value, TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION); break; case PROP_AVAILABLE_MECHANISMS: g_value_set_boxed (value, priv->available_mechanisms); break; case PROP_SECURE: g_value_set_boolean (value, priv->secure); break; case PROP_CAN_TRY_AGAIN: /* Wocky can't retry SASL authentication (although XMPP can) */ g_value_set_boolean (value, FALSE); break; case PROP_HAS_INITIAL_DATA: /* Yes, XMPP has "initial data" in its SASL */ g_value_set_boolean (value, TRUE); break; case PROP_AUTHORIZATION_IDENTITY: /* As per RFC 3920, the authorization identity for c2s connections * is the desired JID. We can't use conn_util_get_bare_self_jid at * this stage of the connection process, because it hasn't been * initialized yet. */ { gchar *jid, *username, *stream_server; g_object_get (tp_base_channel_get_connection (channel), "username", &username, "stream-server", &stream_server, NULL); jid = g_strconcat (username, "@", stream_server, NULL); g_free (username); g_free (stream_server); g_value_take_string (value, jid); } break; case PROP_DEFAULT_REALM: /* Like WockySaslDigestMd5, we use the stream server as the default * realm, for interoperability with servers that fail to supply a * realm but expect us to have this default. */ g_object_get_property ( G_OBJECT (tp_base_channel_get_connection (channel)), "stream-server", value); break; case PROP_DEFAULT_USERNAME: /* In practice, XMPP servers normally want us to authenticate as the * local-part of the JID. */ g_object_get_property ( G_OBJECT (tp_base_channel_get_connection (channel)), "username", value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_server_sasl_channel_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GabbleServerSaslChannel *chan = GABBLE_SERVER_SASL_CHANNEL (object); GabbleServerSaslChannelPrivate *priv = chan->priv; switch (property_id) { case PROP_SECURE: priv->secure = g_value_get_boolean (value); break; case PROP_AVAILABLE_MECHANISMS: priv->available_mechanisms = g_value_dup_boxed (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gabble_server_sasl_channel_finalize (GObject *object) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (object); GabbleServerSaslChannelPrivate *priv = self->priv; /* a ref is held for the channel's lifetime */ g_assert (tp_base_channel_is_destroyed ((TpBaseChannel *) self)); g_assert (priv->result == NULL); g_strfreev (priv->available_mechanisms); g_free (priv->sasl_error); g_hash_table_unref (priv->sasl_error_details); g_clear_error (&priv->wocky_auth_error); if (G_OBJECT_CLASS (gabble_server_sasl_channel_parent_class)->finalize) G_OBJECT_CLASS (gabble_server_sasl_channel_parent_class)->finalize (object); } static void gabble_server_sasl_channel_close (TpBaseChannel *channel); static void gabble_server_sasl_channel_class_init (GabbleServerSaslChannelClass *klass) { static TpDBusPropertiesMixinPropImpl server_auth_props[] = { { "AuthenticationMethod", "auth-method", NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl sasl_auth_props[] = { { "AvailableMechanisms", "available-mechanisms", NULL }, { "HasInitialData", "has-initial-data", NULL }, { "CanTryAgain", "can-try-again", NULL }, { "SASLStatus", "sasl-status", NULL }, { "SASLError", "sasl-error", NULL }, { "SASLErrorDetails", "sasl-error-details", NULL }, { "AuthorizationIdentity", "authorization-identity", NULL }, { "DefaultRealm", "default-realm", NULL }, { "DefaultUsername", "default-username", NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl securable_props[] = { /* For the moment we only have a unified "secure" property, which * implies we're both encrypted and verified */ { "Encrypted", "secure", NULL }, { "Verified", "secure", NULL }, { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { { TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION, tp_dbus_properties_mixin_getter_gobject_properties, NULL, server_auth_props, }, { TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, tp_dbus_properties_mixin_getter_gobject_properties, NULL, sasl_auth_props, }, { TP_IFACE_CHANNEL_INTERFACE_SECURABLE, tp_dbus_properties_mixin_getter_gobject_properties, NULL, securable_props, }, { NULL } }; GObjectClass *object_class = G_OBJECT_CLASS (klass); TpBaseChannelClass *channel_class = TP_BASE_CHANNEL_CLASS (klass); GParamSpec *param_spec; g_type_class_add_private (klass, sizeof (GabbleServerSaslChannelPrivate)); object_class->get_property = gabble_server_sasl_channel_get_property; object_class->set_property = gabble_server_sasl_channel_set_property; object_class->finalize = gabble_server_sasl_channel_finalize; channel_class->channel_type = TP_IFACE_CHANNEL_TYPE_SERVER_AUTHENTICATION; channel_class->get_interfaces = gabble_server_sasl_channel_get_interfaces; channel_class->target_handle_type = TP_HANDLE_TYPE_NONE; channel_class->fill_immutable_properties = gabble_server_sasl_channel_fill_immutable_properties; channel_class->get_object_path_suffix = gabble_server_sasl_channel_get_object_path_suffix; channel_class->close = gabble_server_sasl_channel_close; param_spec = g_param_spec_string ("auth-method", "Authentication method", "Method of authentication (D-Bus interface)", TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_AUTH_METHOD, param_spec); param_spec = g_param_spec_string ("authorization-identity", "AuthorizationIdentity", "Identity for which we wish to be authorized", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_AUTHORIZATION_IDENTITY, param_spec); param_spec = g_param_spec_string ("default-realm", "DefaultRealm", "Default realm if the server does not supply one", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DEFAULT_REALM, param_spec); param_spec = g_param_spec_string ("default-username", "DefaultUsername", "Default simple username if the user does not supply one", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DEFAULT_USERNAME, param_spec); param_spec = g_param_spec_uint ("sasl-status", "SASLStatus", "Status of this channel", 0, NUM_TP_SASL_STATUSES, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SASL_STATUS, param_spec); param_spec = g_param_spec_string ("sasl-error", "SASLError", "D-Bus error name", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SASL_ERROR, param_spec); param_spec = g_param_spec_boxed ("sasl-error-details", "SASLErrorDetails", "Extra details of a SASL error", TP_HASH_TYPE_STRING_VARIANT_MAP, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SASL_ERROR_DETAILS, param_spec); param_spec = g_param_spec_boxed ("available-mechanisms", "Available authentication mechanisms", "The set of mechanisms the server advertised.", G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_AVAILABLE_MECHANISMS, param_spec); param_spec = g_param_spec_boolean ("can-try-again", "CanTryAgain", "True if failed SASL can be retried without reconnecting", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CAN_TRY_AGAIN, param_spec); param_spec = g_param_spec_boolean ("has-initial-data", "HasInitialData", "True if SASL has initial data", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_HAS_INITIAL_DATA, param_spec); param_spec = g_param_spec_boolean ("secure", "Is secure", "Is this channel secure (encrypted and verified)?", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SECURE, param_spec); klass->dbus_props_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (GabbleServerSaslChannelClass, dbus_props_class)); } static void set_errors ( GabbleServerSaslChannel *self, const gchar *dbus_error, const gchar *debug_message, const GError *error) { g_free (self->priv->sasl_error); self->priv->sasl_error = g_strdup (dbus_error); g_hash_table_remove_all (self->priv->sasl_error_details); if (debug_message != NULL) tp_asv_set_string (self->priv->sasl_error_details, "debug-message", debug_message); g_clear_error (&self->priv->wocky_auth_error); self->priv->wocky_auth_error = g_error_copy (error); } static void change_current_state (GabbleServerSaslChannel *self, TpSASLStatus status) { self->priv->sasl_status = status; tp_svc_channel_interface_sasl_authentication_emit_sasl_status_changed ( self, self->priv->sasl_status, self->priv->sasl_error, self->priv->sasl_error_details); } static void complete_operation ( GabbleServerSaslChannel *self, gboolean in_idle) { GabbleServerSaslChannelPrivate *priv = self->priv; GSimpleAsyncResult *r = priv->result; g_return_if_fail (priv->result != NULL); priv->result = NULL; if (in_idle) g_simple_async_result_complete_in_idle (r); else g_simple_async_result_complete (r); g_object_unref (r); } /** * SASL Authentication Channel Interface */ static void gabble_server_sasl_channel_raise ( DBusGMethodInvocation *context, TpError code, const gchar *message, ...) G_GNUC_PRINTF (3, 4); static void gabble_server_sasl_channel_raise (DBusGMethodInvocation *context, TpError code, const gchar *message, ...) { va_list ap; GError *error = NULL; va_start (ap, message); error = g_error_new_valist (TP_ERROR, code, message, ap); va_end (ap); dbus_g_method_return_error (context, error); g_error_free (error); } /* When called from start_mechanism, initial_data can be NULL. When called * from D-Bus as StartMechanismWithData, it can't. */ static void gabble_server_sasl_channel_start_mechanism_with_data ( TpSvcChannelInterfaceSASLAuthentication *iface, const gchar *in_Mechanism, const GArray *in_InitialData, DBusGMethodInvocation *context) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (iface); GabbleServerSaslChannelPrivate *priv = self->priv; WockyAuthRegistryStartData *start_data; GString *initial_data = NULL; if (self->priv->sasl_status != TP_SASL_STATUS_NOT_STARTED) { gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_AVAILABLE, "Mechanisms can only be started in state Not_Started, not %u", self->priv->sasl_status); DEBUG ("cannot start: state %u != Not_Started", self->priv->sasl_status); return; } /* NotStarted state is entered by creating the channel: the caller must * call start_auth_async immediately */ g_assert (priv->result != NULL); g_assert (g_simple_async_result_is_valid (G_ASYNC_RESULT (priv->result), G_OBJECT (self), gabble_server_sasl_channel_start_auth_async)); if (tp_strv_contains ((const gchar * const *) priv->available_mechanisms, in_Mechanism)) { if (in_InitialData != NULL) { /* The initial data might be secret (for PLAIN etc.), and also might * not be UTF-8 or even text, so we just output the length */ DEBUG ("Starting %s authentication with %u bytes of initial data", in_Mechanism, in_InitialData->len); initial_data = g_string_new_len (in_InitialData->data, in_InitialData->len); } else { DEBUG ("Starting %s authentication without initial data", in_Mechanism); } change_current_state (self, TP_SASL_STATUS_IN_PROGRESS); dbus_g_method_return (context); start_data = wocky_auth_registry_start_data_new (in_Mechanism, initial_data); g_simple_async_result_set_op_res_gpointer (priv->result, start_data, (GDestroyNotify) wocky_auth_registry_start_data_free); complete_operation (self, TRUE); if (initial_data != NULL) g_string_free (initial_data, TRUE); } else { DEBUG ("cannot start: %s is not a supported mechanism", in_Mechanism); gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_IMPLEMENTED, "Selected mechanism is not available."); } } static void gabble_server_sasl_channel_start_mechanism ( TpSvcChannelInterfaceSASLAuthentication *iface, const gchar *mech, DBusGMethodInvocation *context) { gabble_server_sasl_channel_start_mechanism_with_data (iface, mech, NULL, context); } static void gabble_server_sasl_channel_respond ( TpSvcChannelInterfaceSASLAuthentication *channel, const GArray *in_Response_Data, DBusGMethodInvocation *context) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (channel); GabbleServerSaslChannelPrivate *priv = self->priv; GString *response_data; if (self->priv->sasl_status != TP_SASL_STATUS_IN_PROGRESS) { gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_AVAILABLE, "You can only respond to challenges in state In_Progress, not %u", self->priv->sasl_status); DEBUG ("cannot respond: state %u != In_Progress", self->priv->sasl_status); return; } if (priv->result == NULL) { gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_AVAILABLE, "You already responded to the most recent challenge"); DEBUG ("cannot respond: already responded"); return; } g_assert (g_simple_async_result_is_valid (G_ASYNC_RESULT (priv->result), G_OBJECT (self), gabble_server_sasl_channel_challenge_async)); /* The response might be secret (for PLAIN etc.), and also might * not be UTF-8 or even text, so we just output the length */ DEBUG ("responding with %u bytes", in_Response_Data->len); if (in_Response_Data->len > 0) response_data = g_string_new_len (in_Response_Data->data, in_Response_Data->len); else response_data = NULL; g_simple_async_result_set_op_res_gpointer (priv->result, response_data, (GDestroyNotify) wocky_g_string_free); complete_operation (self, TRUE); tp_svc_channel_interface_sasl_authentication_return_from_respond ( context); } static void gabble_server_sasl_channel_accept_sasl ( TpSvcChannelInterfaceSASLAuthentication *channel, DBusGMethodInvocation *context) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (channel); GabbleServerSaslChannelPrivate *priv = self->priv; const gchar *message = NULL; switch (self->priv->sasl_status) { case TP_SASL_STATUS_NOT_STARTED: message = "Authentication has not yet begun (Not_Started)"; break; case TP_SASL_STATUS_IN_PROGRESS: /* In this state, the only valid time to call this method is in response * to a challenge, to indicate that, actually, that challenge was * additional data for a successful authentication. */ if (priv->result == NULL) { message = "In_Progress, but you already responded to the last " "challenge"; } else { DEBUG ("client says the last challenge was actually final data " "and has accepted it"); g_assert (g_simple_async_result_is_valid (G_ASYNC_RESULT (priv->result), G_OBJECT (self), gabble_server_sasl_channel_challenge_async)); change_current_state (self, TP_SASL_STATUS_CLIENT_ACCEPTED); } break; case TP_SASL_STATUS_SERVER_SUCCEEDED: /* The server has already said yes, and the caller is waiting for * success_async(), i.e. waiting for the UI to check whether it's * happy too. AcceptSASL means that it is. */ DEBUG ("client has accepted server's success"); g_assert (g_simple_async_result_is_valid (G_ASYNC_RESULT (priv->result), G_OBJECT (self), gabble_server_sasl_channel_success_async)); change_current_state (self, TP_SASL_STATUS_SUCCEEDED); break; case TP_SASL_STATUS_CLIENT_ACCEPTED: message = "Client already accepted authentication (Client_Accepted)"; break; case TP_SASL_STATUS_SUCCEEDED: message = "Authentication already succeeded (Succeeded)"; break; case TP_SASL_STATUS_SERVER_FAILED: message = "Authentication has already failed (Server_Failed)"; break; case TP_SASL_STATUS_CLIENT_FAILED: message = "Authentication has already been aborted (Client_Failed)"; break; default: g_assert_not_reached (); } if (message != NULL) { DEBUG ("cannot accept SASL: %s", message); gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_AVAILABLE, "%s", message); return; } if (priv->result != NULL) { /* This is a bit weird - this code is run for two different async * results. In the In_Progress case, this code results in * success with the GSimpleAsyncResult's op_res left as NULL, which * is what Wocky wants for an empty response. In the Server_Succeeded * response, the async result is just success or error - we succeed. */ /* We want want to complete not in an idle because if we do we * will hit fd.o#32278. This is safe because we're being called * from dbus-glib in the main loop. */ complete_operation (self, FALSE); } tp_svc_channel_interface_sasl_authentication_return_from_accept_sasl ( context); } static void gabble_server_sasl_channel_abort_sasl ( TpSvcChannelInterfaceSASLAuthentication *channel, guint in_Reason, const gchar *in_Debug_Message, DBusGMethodInvocation *context) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (channel); GabbleServerSaslChannelPrivate *priv = self->priv; const gchar *dbus_error; switch (self->priv->sasl_status) { case TP_SASL_STATUS_SERVER_FAILED: case TP_SASL_STATUS_CLIENT_FAILED: DEBUG ("ignoring attempt to abort: we already failed"); break; case TP_SASL_STATUS_SUCCEEDED: case TP_SASL_STATUS_CLIENT_ACCEPTED: DEBUG ("cannot abort: client already called AcceptSASL"); gabble_server_sasl_channel_raise (context, TP_ERROR_NOT_AVAILABLE, "Authentication has already succeeded - too late to abort"); return; case TP_SASL_STATUS_NOT_STARTED: case TP_SASL_STATUS_IN_PROGRESS: case TP_SASL_STATUS_SERVER_SUCCEEDED: { GError *error = NULL; switch (in_Reason) { case TP_SASL_ABORT_REASON_INVALID_CHALLENGE: g_set_error (&error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_INVALID_REPLY, "invalid challenge (%s)", in_Debug_Message); dbus_error = TP_ERROR_STR_SERVICE_CONFUSED; break; case TP_SASL_ABORT_REASON_USER_ABORT: g_set_error (&error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, "user aborted auth (%s)", in_Debug_Message); dbus_error = TP_ERROR_STR_CANCELLED; break; default: g_set_error (&error, WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, "unknown reason code %u, treating as User_Abort (%s)", in_Reason, in_Debug_Message); dbus_error = TP_ERROR_STR_CANCELLED; break; } DEBUG ("%s", error->message); set_errors (self, dbus_error, in_Debug_Message, error); change_current_state (self, TP_SASL_STATUS_CLIENT_FAILED); if (priv->result != NULL) { /* If Not_Started, we're returning failure from start_auth_async. * If In_Progress, we might be returning failure from * challenge_async, if one is outstanding. * If Server_Succeeded, we're returning failure from success_async. */ g_simple_async_result_set_from_error (priv->result, error); complete_operation (self, TRUE); } g_error_free (error); break; } default: g_assert_not_reached (); } tp_svc_channel_interface_sasl_authentication_return_from_abort_sasl ( context); } static void sasl_auth_iface_init (gpointer klass, gpointer unused G_GNUC_UNUSED) { #define IMPLEMENT(x) \ tp_svc_channel_interface_sasl_authentication_implement_##x ( \ klass, gabble_server_sasl_channel_##x) IMPLEMENT (start_mechanism); IMPLEMENT (start_mechanism_with_data); IMPLEMENT (respond); IMPLEMENT (accept_sasl); IMPLEMENT (abort_sasl); #undef IMPLEMENT } void gabble_server_sasl_channel_start_auth_async (GabbleServerSaslChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GabbleServerSaslChannelPrivate *priv = self->priv; g_assert (priv->result == NULL); g_assert (priv->sasl_status == TP_SASL_STATUS_NOT_STARTED); DEBUG ("Starting authentication"); priv->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gabble_server_sasl_channel_start_auth_async); tp_base_channel_register (TP_BASE_CHANNEL (self)); } gboolean gabble_server_sasl_channel_start_auth_finish (GabbleServerSaslChannel *self, GAsyncResult *result, WockyAuthRegistryStartData **start_data, GError **error) { wocky_implement_finish_copy_pointer (self, gabble_server_sasl_channel_start_auth_async, wocky_auth_registry_start_data_dup, start_data); } void gabble_server_sasl_channel_challenge_async (GabbleServerSaslChannel *self, const GString *challenge_data, GAsyncReadyCallback callback, gpointer user_data) { GabbleServerSaslChannelPrivate *priv = self->priv; GArray *challenge_ay; g_assert (!tp_base_channel_is_destroyed ((TpBaseChannel *) self)); g_assert (priv->result == NULL); /* it might be sensitive, and also might not be UTF-8 text, so just print * the length */ DEBUG ("New challenge, %" G_GSIZE_FORMAT " bytes", challenge_data->len); priv->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gabble_server_sasl_channel_challenge_async); switch (priv->sasl_status) { case TP_SASL_STATUS_IN_PROGRESS: challenge_ay = g_array_sized_new (FALSE, FALSE, sizeof (gchar), challenge_data->len); g_array_append_vals (challenge_ay, challenge_data->str, challenge_data->len); tp_svc_channel_interface_sasl_authentication_emit_new_challenge ( self, challenge_ay); break; case TP_SASL_STATUS_CLIENT_FAILED: g_return_if_fail (priv->wocky_auth_error != NULL); g_simple_async_result_set_from_error (priv->result, priv->wocky_auth_error); complete_operation (self, TRUE); return; default: g_assert_not_reached (); } } gboolean gabble_server_sasl_channel_challenge_finish (GabbleServerSaslChannel *self, GAsyncResult *result, GString **response, GError **error) { wocky_implement_finish_copy_pointer (self, gabble_server_sasl_channel_challenge_async, wocky_g_string_dup, response); } void gabble_server_sasl_channel_success_async (GabbleServerSaslChannel *self, GAsyncReadyCallback callback, gpointer user_data) { GabbleServerSaslChannelPrivate *priv = self->priv; g_assert (!tp_base_channel_is_destroyed ((TpBaseChannel *) self)); g_assert (priv->result == NULL); priv->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gabble_server_sasl_channel_success_async); DEBUG (""); if (self->priv->sasl_status != TP_SASL_STATUS_CLIENT_ACCEPTED) { change_current_state (self, TP_SASL_STATUS_SERVER_SUCCEEDED); } else { change_current_state (self, TP_SASL_STATUS_SUCCEEDED); complete_operation (self, TRUE); } } gboolean gabble_server_sasl_channel_success_finish (GabbleServerSaslChannel *self, GAsyncResult *result, GError **error) { wocky_implement_finish_void (self, gabble_server_sasl_channel_success_async); } void gabble_server_sasl_channel_fail (GabbleServerSaslChannel *self, const GError *error) { GError *tp_error = NULL; TpConnectionStatusReason conn_reason; if (self->priv->sasl_error != NULL) { DEBUG ("already failed, ignoring further error: %s", error->message); return; } gabble_set_tp_conn_error_from_wocky (error, TP_CONNECTION_STATUS_CONNECTING, &conn_reason, &tp_error); g_assert (tp_error->domain == TP_ERROR); DEBUG ("auth failed: %s", tp_error->message); set_errors (self, tp_error_get_dbus_name (tp_error->code), tp_error->message, error); change_current_state (self, TP_SASL_STATUS_SERVER_FAILED); self->priv->disconnect_reason = conn_reason; } /* * Public */ GabbleServerSaslChannel * gabble_server_sasl_channel_new (GabbleConnection *conn, GStrv available_mechanisms, gboolean secure, const gchar *session_id) { GabbleServerSaslChannel *obj; g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); obj = GABBLE_SERVER_SASL_CHANNEL ( g_object_new (GABBLE_TYPE_SERVER_SASL_CHANNEL, "connection", conn, "available-mechanisms", available_mechanisms, "secure", secure, NULL)); return obj; } static void gabble_server_sasl_channel_close (TpBaseChannel *channel) { GabbleServerSaslChannel *self = GABBLE_SERVER_SASL_CHANNEL (channel); GabbleServerSaslChannelPrivate *priv = self->priv; GError error = { WOCKY_AUTH_ERROR, WOCKY_AUTH_ERROR_FAILURE, "Client aborted authentication." }; DEBUG ("called on %p", self); switch (priv->sasl_status) { case TP_SASL_STATUS_NOT_STARTED: case TP_SASL_STATUS_IN_PROGRESS: case TP_SASL_STATUS_SERVER_SUCCEEDED: set_errors (self, TP_ERROR_STR_AUTHENTICATION_FAILED, "Close() called", &error); break; case TP_SASL_STATUS_CLIENT_ACCEPTED: case TP_SASL_STATUS_SUCCEEDED: /* Hooray! */ break; case TP_SASL_STATUS_SERVER_FAILED: case TP_SASL_STATUS_CLIENT_FAILED: g_warn_if_fail (priv->sasl_error != NULL); break; } if (priv->result != NULL) { DEBUG ("closed channel"); g_simple_async_result_set_from_error (priv->result, &error); complete_operation (self, TRUE); } tp_base_channel_destroyed (channel); } /** * @dbus_error: (out) (transfer full): the D-Bus error name * @details: (out) (transfer full) (element-type utf8 GObject.Value): the * error details * @reason: (out): the reason with which to disconnect * @error: (out): an error in a domain Wocky understands describing what went * wrong * * Returns: %TRUE if an error was copied; %FALSE leaving the 'out' parameters * untouched if there is no error */ gboolean gabble_server_sasl_channel_get_failure_details (GabbleServerSaslChannel *self, gchar **dbus_error, GHashTable **details, TpConnectionStatusReason *reason, GError **error) { if (self->priv->sasl_error != NULL) { if (dbus_error != NULL) *dbus_error = g_strdup (self->priv->sasl_error); if (details != NULL) *details = g_hash_table_ref (self->priv->sasl_error_details); if (reason != NULL) *reason = self->priv->disconnect_reason; if (error != NULL) *error = g_error_copy (self->priv->wocky_auth_error); return TRUE; } else { return FALSE; } }