commit 1ae36dab2fa166d0371407239521d60b91d692b3 Author: Eitan Isaacson Date: Thu Apr 29 16:27:06 2010 -0700 Added WockyAuthRegistry. It selects the authentication mechanism based on it's available handlers, and arbitrates challenge/responses between the handler and the connector (via Wocky[Sasl|Jabber]Auth). diff --git a/examples/connect.c b/examples/connect.c index 540c15f..1f1a7aa 100644 --- a/examples/connect.c +++ b/examples/connect.c @@ -68,6 +68,7 @@ ssl_features_received_cb (GObject *source, { WockyStanza *stanza; WockyNode *node; + WockyAuthRegistry *auth_registry; stanza = wocky_xmpp_connection_recv_stanza_finish (conn, result, NULL); @@ -83,7 +84,10 @@ ssl_features_received_cb (GObject *source, return; } - sasl = wocky_sasl_auth_new (server, username, password, conn); + auth_registry = wocky_auth_registry_new (); + sasl = wocky_sasl_auth_new (server, username, password, conn, auth_registry); + + g_object_unref (auth_registry); wocky_sasl_auth_authenticate_async (sasl, stanza, TRUE, NULL, auth_done_cb, NULL); diff --git a/tests/wocky-test-sasl-auth.c b/tests/wocky-test-sasl-auth.c index 8c0c14b..1ed8e05 100644 --- a/tests/wocky-test-sasl-auth.c +++ b/tests/wocky-test-sasl-auth.c @@ -7,6 +7,7 @@ #include #include +#include typedef struct { gchar *description; @@ -137,6 +138,7 @@ feature_stanza_received (GObject *source, { WockyStanza *stanza; WockySaslHandler *test_handler; + WockyAuthRegistry *auth_registry; test_t *test = (test_t *) user_data; stanza = wocky_xmpp_connection_recv_stanza_finish ( @@ -145,14 +147,20 @@ feature_stanza_received (GObject *source, g_assert (stanza != NULL); g_assert (sasl == NULL); + + auth_registry = wocky_auth_registry_new (); + sasl = wocky_sasl_auth_new (test->servername, - test->wrong_username ? "wrong" : test->username, - test->wrong_password ? "wrong" : test->password, - WOCKY_XMPP_CONNECTION (source)); + test->wrong_username ? "wrong" : test->username, + test->wrong_password ? "wrong" : test->password, + WOCKY_XMPP_CONNECTION (source), + auth_registry); test_handler = WOCKY_SASL_HANDLER (wocky_test_sasl_handler_new ()); - wocky_sasl_auth_add_handler (sasl, test_handler); + wocky_auth_registry_add_handler (auth_registry, test_handler); + g_object_unref (test_handler); + g_object_unref (auth_registry); wocky_sasl_auth_authenticate_async (sasl, stanza, diff --git a/tests/wocky-test-sasl-handler.c b/tests/wocky-test-sasl-handler.c index 7b3b8e5..cca6b86 100644 --- a/tests/wocky-test-sasl-handler.c +++ b/tests/wocky-test-sasl-handler.c @@ -22,7 +22,7 @@ wocky_test_sasl_handler_init (WockyTestSaslHandler *self) static gboolean test_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error); static void @@ -43,9 +43,9 @@ wocky_test_sasl_handler_new (void) static gboolean test_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error) { - *initial_data = g_base64_encode ((guchar *) "open sesame", 11); + *initial_data = g_string_new ("open sesame"); return TRUE; } diff --git a/wocky/Makefile.am b/wocky/Makefile.am index 4c3c5f0..3c95af3 100644 --- a/wocky/Makefile.am +++ b/wocky/Makefile.am @@ -94,7 +94,9 @@ HANDWRITTEN_SOURCES = \ wocky-pubsub-service-protected.h \ wocky-pubsub-node.c \ wocky-pubsub-node.h \ - wocky-pubsub-node-protected.h + wocky-pubsub-node-protected.h \ + wocky-auth-registry.c \ + wocky-auth-registry.h if USING_OPENSSL HANDWRITTEN_SOURCES += $(OPENSSL_SRC) diff --git a/wocky/wocky-auth-registry.c b/wocky/wocky-auth-registry.c new file mode 100644 index 0000000..f912282 --- /dev/null +++ b/wocky/wocky-auth-registry.c @@ -0,0 +1,374 @@ +/* wocky-auth-registry.c */ + +#include "wocky-auth-registry.h" +#include "wocky-sasl-digest-md5.h" +#include "wocky-sasl-handler.h" +#include "wocky-sasl-plain.h" + +#define DEBUG_FLAG DEBUG_SASL +#include "wocky-debug.h" + +G_DEFINE_TYPE (WockyAuthRegistry, wocky_auth_registry, G_TYPE_OBJECT) + +/* private structure */ +struct _WockyAuthRegistryPrivate +{ + gboolean dispose_has_run; + WockySaslHandler *handler; + GSList *handlers; +}; + +GQuark +wocky_auth_error_quark (void) { + static GQuark quark = 0; + + if (!quark) + quark = g_quark_from_static_string ("wocky_auth_error"); + + return quark; +} + +static void +wocky_auth_registry_constructed (GObject *object) +{ +} + +static void +wocky_auth_registry_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +wocky_auth_registry_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +wocky_auth_registry_dispose (GObject *object) +{ + WockyAuthRegistry *self = WOCKY_AUTH_REGISTRY (object); + WockyAuthRegistryPrivate *priv = self->priv; + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* release any references held by the object here */ + if (priv->handler != NULL) + { + g_object_unref (priv->handler); + } + + if (priv->handlers != NULL) + { + g_slist_foreach (priv->handlers, (GFunc) g_object_unref, NULL); + g_slist_free (priv->handlers); + } + + G_OBJECT_CLASS (wocky_auth_registry_parent_class)->dispose (object); +} + +static void +wocky_auth_registry_finalize (GObject *object) +{ + G_OBJECT_CLASS (wocky_auth_registry_parent_class)->finalize (object); +} + +static void +wocky_auth_registry_class_init (WockyAuthRegistryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (WockyAuthRegistryPrivate)); + + object_class->constructed = wocky_auth_registry_constructed; + object_class->get_property = wocky_auth_registry_get_property; + object_class->set_property = wocky_auth_registry_set_property; + object_class->dispose = wocky_auth_registry_dispose; + object_class->finalize = wocky_auth_registry_finalize; +} + +static void +wocky_auth_registry_init (WockyAuthRegistry *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WOCKY_TYPE_AUTH_REGISTRY, + WockyAuthRegistryPrivate); +} + +WockyAuthRegistry * +wocky_auth_registry_new (void) +{ + return g_object_new (WOCKY_TYPE_AUTH_REGISTRY, NULL); +} + +static gboolean +wocky_auth_registry_has_mechanism (const GSList *list, const gchar *mech) { + GSList *t; + + t = g_slist_find_custom ((GSList *) list, mech, (GCompareFunc) g_strcmp0); + + return (t != NULL); +} + +static void +wocky_auth_registry_free_response (GString *response) +{ + if (response != NULL) + g_string_free (response, TRUE); +} + +static WockySaslHandler * +wocky_auth_registry_select_handler (WockyAuthRegistry *self, + gboolean allow_plain, const GSList *mechanisms) +{ + WockyAuthRegistryPrivate *priv = self->priv; + GSList *k; + + for (k = priv->handlers; k != NULL; k = k->next) + { + WockySaslHandler *handler = k->data; + const gchar *handler_mech = wocky_sasl_handler_get_mechanism (handler); + + if (wocky_sasl_handler_is_plain (handler) && !allow_plain) + continue; + + if (wocky_auth_registry_has_mechanism (mechanisms, handler_mech)) + { + g_object_ref (handler); + return handler; + } + } + + return NULL; +} + +void +wocky_auth_registry_start_auth_async (WockyAuthRegistry *self, + const GSList *mechanisms, + gboolean allow_plain, + gboolean is_secure_channel, + const gchar *username, + const gchar *password, + const gchar *server, + GAsyncReadyCallback callback, + gpointer user_data) +{ + WockyAuthRegistryPrivate *priv = self->priv; + GSimpleAsyncResult *result; + + g_assert (priv->handler == NULL); + + result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + wocky_auth_registry_start_auth_finish); + + priv->handler = wocky_auth_registry_select_handler (self, allow_plain, + mechanisms); + + if (priv->handler == NULL) + { + if (wocky_auth_registry_has_mechanism (mechanisms, "DIGEST-MD5")) + { + /* XXX: check for username and password here? */ + DEBUG ("Choosing DIGEST-MD5 as auth mechanism"); + priv->handler = WOCKY_SASL_HANDLER (wocky_sasl_digest_md5_new ( + server, username, password)); + } + else if (allow_plain && + wocky_auth_registry_has_mechanism (mechanisms, "PLAIN")) + { + /* XXX: check for username and password here? */ + DEBUG ("Choosing PLAIN as auth mechanism"); + priv->handler = WOCKY_SASL_HANDLER (wocky_sasl_plain_new ( + username, password)); + } + } + + if (priv->handler == NULL) + { + g_simple_async_result_set_error (result, WOCKY_AUTH_ERROR, + WOCKY_AUTH_ERROR_NO_SUPPORTED_MECHANISMS, + "No supported mechanisms found"); + } + else + { + GString *initial_data; + GError *error; + + if (!wocky_sasl_handler_get_initial_response (priv->handler, + &initial_data, &error)) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } + else + { + g_simple_async_result_set_op_res_gpointer (result, initial_data, + (GDestroyNotify) wocky_auth_registry_free_response); + } + } + + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +gboolean +wocky_auth_registry_start_auth_finish (WockyAuthRegistry *self, + GAsyncResult *res, + gchar **mechanism, + GString **initial_data, + GError **error) +{ + WockyAuthRegistryPrivate *priv = self->priv; + GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (res); + GString *response_data; + g_assert (initial_data != NULL); + g_assert (mechanism != NULL); + + g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (self), + wocky_auth_registry_start_auth_finish), FALSE); + + if (g_simple_async_result_propagate_error ( + G_SIMPLE_ASYNC_RESULT (result), error)) + return FALSE; + + g_assert (priv->handler != NULL); + + *mechanism = g_strdup (wocky_sasl_handler_get_mechanism (priv->handler)); + + response_data = g_simple_async_result_get_op_res_gpointer (result); + + if (response_data != NULL) + *initial_data = g_string_new_len (response_data->str, + response_data->len); + + return TRUE; +} + +void +wocky_auth_registry_challenge_async (WockyAuthRegistry *self, + const GString *challenge_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + WockyAuthRegistryPrivate *priv = self->priv; + GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), + callback, user_data, wocky_auth_registry_challenge_finish); + GString *response = NULL; + GError *error; + + g_assert (priv->handler != NULL); + + if (!wocky_sasl_handler_handle_auth_data (priv->handler, challenge_data, + &response, &error)) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } + else + { + g_simple_async_result_set_op_res_gpointer (result, response, + (GDestroyNotify) wocky_auth_registry_free_response); + } + + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +gboolean +wocky_auth_registry_challenge_finish (WockyAuthRegistry *self, + GAsyncResult *res, + GString **response, + GError **error) +{ + GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (res); + GString *response_data; + + g_assert (response != NULL); + + g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (self), + wocky_auth_registry_challenge_finish), FALSE); + + if (g_simple_async_result_propagate_error ( + G_SIMPLE_ASYNC_RESULT (result), error)) + return FALSE; + + response_data = g_simple_async_result_get_op_res_gpointer (result); + + if (response_data != NULL) + *response = g_string_new_len (response_data->str, response_data->len); + else + *response = NULL; + + return TRUE; +} + +void +wocky_auth_registry_success_async (WockyAuthRegistry *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + WockyAuthRegistryPrivate *priv = self->priv; + GSimpleAsyncResult *result = g_simple_async_result_new (G_OBJECT (self), + callback, user_data, wocky_auth_registry_success_finish); + GError *error; + + g_assert (priv->handler != NULL); + + if (!wocky_sasl_handler_handle_success (priv->handler, &error)) + { + g_simple_async_result_set_from_error (result, error); + g_error_free (error); + } + + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +gboolean +wocky_auth_registry_success_finish (WockyAuthRegistry *self, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (res); + + g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (self), + wocky_auth_registry_success_finish), FALSE); + + if (g_simple_async_result_propagate_error ( + G_SIMPLE_ASYNC_RESULT (result), error)) + return FALSE; + + return TRUE; +} + +void wocky_auth_registry_failure_notify (WockyAuthRegistry *self, + GError *error) +{ +} + +void +wocky_auth_registry_add_handler (WockyAuthRegistry *self, + WockySaslHandler *handler) +{ + WockyAuthRegistryPrivate *priv = self->priv; + + g_object_ref (handler); + priv->handlers = g_slist_append (priv->handlers, handler); +} diff --git a/wocky/wocky-auth-registry.h b/wocky/wocky-auth-registry.h new file mode 100644 index 0000000..bc703ad --- /dev/null +++ b/wocky/wocky-auth-registry.h @@ -0,0 +1,122 @@ +/* wocky-auth-registry.h */ + +#ifndef _WOCKY_AUTH_REGISTRY_H +#define _WOCKY_AUTH_REGISTRY_H + +#include +#include +#include "wocky-sasl-handler.h" + +G_BEGIN_DECLS + +GQuark wocky_auth_error_quark (void); +#define WOCKY_AUTH_ERROR \ + wocky_auth_error_quark () + +typedef enum +{ + /* Failed to initialize our sasl support */ + WOCKY_AUTH_ERROR_INIT_FAILED, + /* Server doesn't support sasl (no mechanisms) */ + WOCKY_AUTH_ERROR_SASL_NOT_SUPPORTED, + /* Server doesn't support any mechanisms that we support */ + WOCKY_AUTH_ERROR_NO_SUPPORTED_MECHANISMS, + /* Couldn't send our stanzas to the server */ + WOCKY_AUTH_ERROR_NETWORK, + /* Server sent an invalid reply */ + WOCKY_AUTH_ERROR_INVALID_REPLY, + /* Failure to provide user credentials */ + WOCKY_AUTH_ERROR_NO_CREDENTIALS, + /* Server sent a failure */ + WOCKY_AUTH_ERROR_FAILURE, + /* disconnected */ + WOCKY_AUTH_ERROR_CONNRESET, + /* XMPP stream error while authing */ + WOCKY_AUTH_ERROR_STREAM, +} WockyAuthError; + +#define WOCKY_TYPE_AUTH_REGISTRY wocky_auth_registry_get_type() + +#define WOCKY_AUTH_REGISTRY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + WOCKY_TYPE_AUTH_REGISTRY, WockyAuthRegistry)) + +#define WOCKY_AUTH_REGISTRY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + WOCKY_TYPE_AUTH_REGISTRY, WockyAuthRegistryClass)) + +#define WOCKY_IS_AUTH_REGISTRY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + WOCKY_TYPE_AUTH_REGISTRY)) + +#define WOCKY_IS_AUTH_REGISTRY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + WOCKY_TYPE_AUTH_REGISTRY)) + +#define WOCKY_AUTH_REGISTRY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + WOCKY_TYPE_AUTH_REGISTRY, WockyAuthRegistryClass)) + +typedef struct _WockyAuthRegistry WockyAuthRegistry; +typedef struct _WockyAuthRegistryClass WockyAuthRegistryClass; +typedef struct _WockyAuthRegistryPrivate WockyAuthRegistryPrivate; + +struct _WockyAuthRegistry +{ + GObject parent; + + WockyAuthRegistryPrivate *priv; +}; + +struct _WockyAuthRegistryClass +{ + GObjectClass parent_class; +}; + +GType wocky_auth_registry_get_type (void) G_GNUC_CONST; + +WockyAuthRegistry *wocky_auth_registry_new (void); + +void wocky_auth_registry_start_auth_async (WockyAuthRegistry *self, + const GSList *mechanisms, + gboolean allow_plain, + gboolean is_secure_channel, + const gchar *username, + const gchar *password, + const gchar *server, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean wocky_auth_registry_start_auth_finish (WockyAuthRegistry *self, + GAsyncResult *res, + gchar **mechanism, + GString **initial_response, + GError **error); + +void wocky_auth_registry_challenge_async (WockyAuthRegistry *self, + const GString *challenge_data, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean wocky_auth_registry_challenge_finish (WockyAuthRegistry *self, + GAsyncResult *res, + GString **response, + GError **error); + +void wocky_auth_registry_success_async (WockyAuthRegistry *self, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean wocky_auth_registry_success_finish (WockyAuthRegistry *self, + GAsyncResult *res, + GError **error); + +void wocky_auth_registry_failure_notify (WockyAuthRegistry *self, + GError *error); + +void wocky_auth_registry_add_handler (WockyAuthRegistry *self, + WockySaslHandler *handler); + +G_END_DECLS + +#endif /* _WOCKY_AUTH_REGISTRY_H */ diff --git a/wocky/wocky-connector.c b/wocky/wocky-connector.c index be717c2..a24048e 100644 --- a/wocky/wocky-connector.c +++ b/wocky/wocky-connector.c @@ -243,6 +243,7 @@ enum PROP_LEGACY_SSL, PROP_SESSION_ID, PROP_EMAIL, + PROP_AUTH_REGISTRY, }; /* this tracks which XEP 0077 operation (register account, cancel account) * @@ -314,6 +315,8 @@ struct _WockyConnectorPrivate WockyTLSSession *tls_sess; WockyTLSConnection *tls; WockyXmppConnection *conn; + + WockyAuthRegistry *auth_registry; }; /* choose an appropriate chunk of text describing our state for debug/error */ @@ -503,6 +506,9 @@ wocky_connector_set_property (GObject *object, g_free (priv->session_id); priv->session_id = g_value_dup_string (value); break; + case PROP_AUTH_REGISTRY: + g_object_unref (priv->auth_registry); + priv->auth_registry = g_value_dup_object (value); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -565,6 +571,9 @@ wocky_connector_get_property (GObject *object, case PROP_SESSION_ID: g_value_set_string (value, priv->session_id); break; + case PROP_AUTH_REGISTRY: + g_value_set_object (value, priv->auth_registry); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -751,6 +760,18 @@ wocky_connector_class_init (WockyConnectorClass *klass) "XMPP Session ID", NULL, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (oclass, PROP_SESSION_ID, spec); + + /** + * WockyConnector:auth-registry + * + * An authentication registry that holds handlers for different + * authentication mechanisms, arbitrates mechanism selection and relays + * challenges and responses between the handlers and the connection. + */ + spec = g_param_spec_object ("auth-registry", "Authentication Registry", + "Authentication Registry", WOCKY_TYPE_AUTH_REGISTRY, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (oclass, PROP_AUTH_REGISTRY, spec); } #define UNREF_AND_FORGET(x) if (x != NULL) { g_object_unref (x); x = NULL; } @@ -1667,10 +1688,15 @@ request_auth (WockyConnector *object, { WockyConnector *self = WOCKY_CONNECTOR (object); WockyConnectorPrivate *priv = self->priv; - WockySaslAuth *s = - wocky_sasl_auth_new (priv->domain, priv->user, priv->pass, priv->conn); + WockySaslAuth *s; gboolean clear = FALSE; + if (priv->auth_registry == NULL) + priv->auth_registry = wocky_auth_registry_new (); + + s = wocky_sasl_auth_new (priv->domain, priv->user, priv->pass, priv->conn, + priv->auth_registry); + if (priv->auth_insecure_ok || (priv->encrypted && priv->encrypted_plain_auth_ok)) clear = TRUE; @@ -2802,3 +2828,8 @@ wocky_connector_new (const gchar *jid, NULL); } +void wocky_connector_set_auth_registry (WockyConnector *self, + WockyAuthRegistry *registry) +{ + g_object_set (self, "auth-registry", registry, NULL); +} diff --git a/wocky/wocky-connector.h b/wocky/wocky-connector.h index baf9cec..049aee1 100644 --- a/wocky/wocky-connector.h +++ b/wocky/wocky-connector.h @@ -183,6 +183,9 @@ gboolean wocky_connector_add_crl (WockyConnector *self, gboolean wocky_connector_add_ca (WockyConnector *self, const gchar *path); +void wocky_connector_set_auth_registry (WockyConnector *self, + WockyAuthRegistry *registry); + G_END_DECLS #endif /* #ifndef __WOCKY_CONNECTOR_H__*/ diff --git a/wocky/wocky-sasl-auth.c b/wocky/wocky-sasl-auth.c index d0f9788..358b02d 100644 --- a/wocky/wocky-sasl-auth.c +++ b/wocky/wocky-sasl-auth.c @@ -25,9 +25,6 @@ #include #include "wocky-sasl-auth.h" -#include "wocky-sasl-digest-md5.h" -#include "wocky-sasl-handler.h" -#include "wocky-sasl-plain.h" #include "wocky-signals-marshal.h" #include "wocky-namespaces.h" #include "wocky-utils.h" @@ -43,6 +40,7 @@ enum PROP_USERNAME, PROP_PASSWORD, PROP_CONNECTION, + PROP_AUTH_REGISTRY, }; /* private structure */ @@ -53,12 +51,14 @@ struct _WockySaslAuthPrivate gchar *username; gchar *password; gchar *server; - WockySaslHandler *handler; GCancellable *cancel; GSimpleAsyncResult *result; - GSList *handlers; + WockyAuthRegistry *auth_registry; }; +static void sasl_auth_stanza_received (GObject *source, GAsyncResult *res, + gpointer user_data); + GQuark wocky_sasl_auth_error_quark (void) { static GQuark quark = 0; @@ -105,6 +105,9 @@ wocky_sasl_auth_set_property (GObject *object, case PROP_CONNECTION: priv->connection = g_value_dup_object (value); break; + case PROP_AUTH_REGISTRY: + priv->auth_registry = g_value_dup_object (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -128,6 +131,9 @@ wocky_sasl_auth_get_property (GObject *object, case PROP_CONNECTION: g_value_set_object (value, priv->connection); break; + case PROP_AUTH_REGISTRY: + g_value_set_object (value, priv->auth_registry); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -174,6 +180,13 @@ wocky_sasl_auth_class_init (WockySaslAuthClass *wocky_sasl_auth_class) G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_CONNECTION, spec); + spec = g_param_spec_object ("auth-registry", + "Authentication Registry", + "Authentication Registry", + WOCKY_TYPE_AUTH_REGISTRY, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_AUTH_REGISTRY, spec); + object_class->dispose = wocky_sasl_auth_dispose; object_class->finalize = wocky_sasl_auth_finalize; @@ -185,23 +198,6 @@ wocky_sasl_auth_dispose (GObject *object) WockySaslAuth *self = WOCKY_SASL_AUTH (object); WockySaslAuthPrivate *priv = self->priv; - if (priv->dispose_has_run) - return; - - priv->dispose_has_run = TRUE; - - /* release any references held by the object here */ - if (priv->handler != NULL) - { - g_object_unref (priv->handler); - } - - if (priv->handlers != NULL) - { - g_slist_foreach (priv->handlers, (GFunc) g_object_unref, NULL); - g_slist_free (priv->handlers); - } - if (priv->connection != NULL) { g_object_unref (priv->connection); @@ -233,12 +229,6 @@ auth_reset (WockySaslAuth *sasl) g_free (priv->server); priv->server = NULL; - if (priv->handler != NULL) - { - g_object_unref (priv->handler); - priv->handler = NULL; - } - if (priv->connection != NULL) { g_object_unref (priv->connection); @@ -346,13 +336,15 @@ WockySaslAuth * wocky_sasl_auth_new (const gchar *server, const gchar *username, const gchar *password, - WockyXmppConnection *connection) + WockyXmppConnection *connection, + WockyAuthRegistry *auth_registry) { return g_object_new (WOCKY_TYPE_SASL_AUTH, "server", server, "username", username, "password", password, "connection", connection, + "auth-registry", auth_registry, NULL); } @@ -380,17 +372,6 @@ wocky_sasl_auth_mechanisms_to_list (WockyNode *mechanisms) return result; } -static gboolean -wocky_sasl_auth_has_mechanism (GSList *list, const gchar *mech) { - GSList *t; - for (t = list ; t != NULL ; t = g_slist_next (t)) { - if (!wocky_strdiff ((gchar *) t->data, mech)) { - return TRUE; - } - } - return FALSE; -} - static void sasl_auth_got_failure (WockySaslAuth *sasl, WockyStanza *stanza, @@ -415,6 +396,125 @@ sasl_auth_got_failure (WockySaslAuth *sasl, reason == NULL ? "Unknown reason" : reason->name); } +static GString * +wocky_sasl_auth_decode_challenge (const gchar *challenge) +{ + gchar *challenge_str; + GString *challenge_data; + gsize len; + + g_return_val_if_fail (challenge != NULL, NULL); + + challenge_str = (gchar *) g_base64_decode (challenge, &len); + + challenge_data = g_string_new_len (challenge_str, len); + + g_free (challenge_str); + + return challenge_data; +} + +static gchar * +wocky_sasl_auth_encode_response (const GString *response_data) +{ + if (response_data != NULL) + return g_base64_encode ((guchar *) response_data->str, + response_data->len); + + return NULL; +} + +static void +wocky_sasl_auth_success_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + WockySaslAuth *self = (WockySaslAuth *) user_data; + WockySaslAuthPrivate *priv = self->priv; + GError *error = NULL; + + if (!wocky_auth_registry_success_finish (priv->auth_registry, res, &error)) + { + auth_failed (self, error->code, error->message); + g_error_free (error); + } + else + { + auth_succeeded (self); + } +} + +static void +wocky_sasl_auth_response_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + WockySaslAuth *self = (WockySaslAuth *) user_data; + WockySaslAuthPrivate *priv = self->priv; + WockyStanza *response_stanza; + GString *response_data = NULL; + gchar *response; + GError *error = NULL; + + if (!wocky_auth_registry_challenge_finish (priv->auth_registry, res, + &response_data, &error)) + { + auth_failed (self, error->code, error->message); + g_error_free (error); + return; + } + + response = wocky_sasl_auth_encode_response (response_data); + + response_stanza = wocky_stanza_new ("response", WOCKY_XMPP_NS_SASL_AUTH); + wocky_node_set_content (wocky_stanza_get_top_node (response_stanza), + response); + + /* FIXME handle send error */ + wocky_xmpp_connection_send_stanza_async ( + priv->connection, response_stanza, NULL, NULL, NULL); + + wocky_xmpp_connection_recv_stanza_async (priv->connection, + NULL, sasl_auth_stanza_received, self); + + if (response_data != NULL) + g_string_free (response_data, TRUE); + g_free (response); + g_object_unref (response_stanza); +} + +static void +wocky_sasl_auth_success_response_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + WockySaslAuth *self = (WockySaslAuth *) user_data; + WockySaslAuthPrivate *priv = self->priv; + GError *error = NULL; + GString *response_data; + + if (!wocky_auth_registry_challenge_finish (priv->auth_registry, res, + &response_data, &error)) + { + auth_failed (self, error->code, error->message); + g_error_free (error); + return; + } + + if (response_data != NULL) + { + auth_failed (self, WOCKY_SASL_AUTH_ERROR_INVALID_REPLY, + "Got success from the server while we still had more data to " + "send"); + + g_string_free (response_data, TRUE); + return; + } + + wocky_auth_registry_success_async (priv->auth_registry, + wocky_sasl_auth_success_cb, self); +} + static void sasl_auth_stanza_received (GObject *source, GAsyncResult *res, @@ -450,54 +550,42 @@ sasl_auth_stanza_received (GObject *source, if (!wocky_strdiff (wocky_stanza_get_top_node (stanza)->name, "challenge")) { - WockyStanza *response_stanza; - gchar *response = NULL; - - if (!wocky_sasl_handler_handle_auth_data (priv->handler, - wocky_stanza_get_top_node (stanza)->content, &response, &error)) - goto failure; + GString *challenge; - response_stanza = wocky_stanza_new ("response", WOCKY_XMPP_NS_SASL_AUTH); - wocky_node_set_content (wocky_stanza_get_top_node (response_stanza), - response); + challenge = wocky_sasl_auth_decode_challenge ( + wocky_stanza_get_top_node (stanza)->content); - /* FIXME handle send error */ - wocky_xmpp_connection_send_stanza_async ( - priv->connection, response_stanza, NULL, NULL, NULL); - g_object_unref (response_stanza); - g_free (response); + wocky_auth_registry_challenge_async (priv->auth_registry, challenge, + wocky_sasl_auth_response_cb, sasl); - wocky_xmpp_connection_recv_stanza_async (priv->connection, - NULL, sasl_auth_stanza_received, sasl); + g_string_free (challenge, TRUE); } else if (!wocky_strdiff (wocky_stanza_get_top_node (stanza)->name, "success")) { if (wocky_stanza_get_top_node (stanza)->content != NULL) { - gchar *response = NULL; - - if (!wocky_sasl_handler_handle_auth_data (priv->handler, - wocky_stanza_get_top_node (stanza)->content, &response, &error)) - goto failure; - - if (response != NULL) - { - auth_failed (sasl, WOCKY_SASL_AUTH_ERROR_INVALID_REPLY, - "Got success from the server while we still had more data to " - "send"); - goto out; - } - } + GString *challenge; + + challenge = wocky_sasl_auth_decode_challenge ( + wocky_stanza_get_top_node (stanza)->content); - if (!wocky_sasl_handler_handle_success (priv->handler, &error)) - goto failure; + wocky_auth_registry_challenge_async (priv->auth_registry, challenge, + wocky_sasl_auth_success_response_cb, sasl); + + g_string_free (challenge, TRUE); + } else - auth_succeeded (sasl); + { + wocky_auth_registry_success_async (priv->auth_registry, + wocky_sasl_auth_success_cb, sasl); + } } else if (!wocky_strdiff (wocky_stanza_get_top_node (stanza)->name, "failure")) { sasl_auth_got_failure (sasl, stanza, &error); - goto failure; + g_assert (error != NULL); + auth_failed (sasl, error->code, error->message); + g_error_free (error); } else { @@ -506,29 +594,45 @@ sasl_auth_stanza_received (GObject *source, wocky_stanza_get_top_node (stanza)->name); } -out: g_object_unref (sasl); g_object_unref (stanza); return; +} + +gboolean +wocky_sasl_auth_authenticate_finish (WockySaslAuth *sasl, + GAsyncResult *result, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), + error)) + return FALSE; -failure: - g_assert (error != NULL); - auth_failed (sasl, error->code, error->message); - g_error_free (error); - goto out; + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (sasl), wocky_sasl_auth_authenticate_finish), FALSE); + + return TRUE; } -static gboolean -wocky_sasl_auth_start_mechanism (WockySaslAuth *sasl, - WockySaslHandler *handler) +static void +wocky_sasl_auth_start_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { + WockySaslAuth *self = (WockySaslAuth *) user_data; + WockySaslAuthPrivate *priv = self->priv; WockyStanza *stanza; - WockySaslAuthPrivate *priv = sasl->priv; - gboolean ret = TRUE; - gchar *initial_response = NULL; GError *error = NULL; + GString *initial_response = NULL; + gchar *mechanism = NULL; - priv->handler = handler; + if (!wocky_auth_registry_start_auth_finish (priv->auth_registry, res, + &mechanism, &initial_response, &error)) + { + auth_failed (self, error->code, error->message); + g_error_free (error); + return; + } stanza = wocky_stanza_new ("auth", WOCKY_XMPP_NS_SASL_AUTH); @@ -536,97 +640,28 @@ wocky_sasl_auth_start_mechanism (WockySaslAuth *sasl, wocky_node_set_attribute_ns (wocky_stanza_get_top_node (stanza), "client-uses-full-bind-result", "true", WOCKY_GOOGLE_NS_AUTH); - if (!wocky_sasl_handler_get_initial_response (priv->handler, - &initial_response, &error)) - { - auth_failed (sasl, error->domain, error->message); - goto out; - } - if (initial_response != NULL) { + gchar *initial_response_str = wocky_sasl_auth_encode_response ( + initial_response); + wocky_node_set_content ( wocky_stanza_get_top_node (stanza), - initial_response); - g_free (initial_response); + initial_response_str); + + g_free (initial_response_str); + g_string_free (initial_response, TRUE); } /* FIXME handle send error */ wocky_node_set_attribute (wocky_stanza_get_top_node (stanza), - "mechanism", - wocky_sasl_handler_get_mechanism (priv->handler)); + "mechanism", mechanism); wocky_xmpp_connection_send_stanza_async (priv->connection, stanza, NULL, NULL, NULL); wocky_xmpp_connection_recv_stanza_async (priv->connection, - NULL, sasl_auth_stanza_received, sasl); - -out: - g_object_unref (stanza); - - return ret; -} - -gboolean -wocky_sasl_auth_authenticate_finish (WockySaslAuth *sasl, - GAsyncResult *result, - GError **error) -{ - if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), - error)) - return FALSE; + NULL, sasl_auth_stanza_received, self); - g_return_val_if_fail (g_simple_async_result_is_valid (result, - G_OBJECT (sasl), wocky_sasl_auth_authenticate_finish), FALSE); - - return TRUE; -} - -static WockySaslHandler * -wocky_sasl_auth_select_handler ( - WockySaslAuth *sasl, gboolean allow_plain, GSList *mechanisms) -{ - WockySaslAuthPrivate *priv = sasl->priv; - GSList *i, *k; - - for (k = priv->handlers; k != NULL; k = k->next) - { - WockySaslHandler *handler = k->data; - const gchar *handler_mech = wocky_sasl_handler_get_mechanism (handler); - - if (wocky_sasl_handler_is_plain (handler) && !allow_plain) - continue; - - for (i = mechanisms; i != NULL; i = i->next) - { - const gchar *mechanism = i->data; - - if (!wocky_strdiff (handler_mech, mechanism)) - { - g_object_ref (handler); - return handler; - } - } - } - - if (wocky_sasl_auth_has_mechanism (mechanisms, "DIGEST-MD5")) - { - /* XXX: check for username and password here? */ - DEBUG ("Choosing DIGEST-MD5 as auth mechanism"); - return WOCKY_SASL_HANDLER (wocky_sasl_digest_md5_new ( - priv->server, priv->username, priv->password)); - } - else if (allow_plain && - wocky_sasl_auth_has_mechanism (mechanisms, "PLAIN")) - { - /* XXX: check for username and password here? */ - DEBUG ("Choosing PLAIN as auth mechanism"); - return WOCKY_SASL_HANDLER (wocky_sasl_plain_new ( - priv->username, priv->password)); - } - else - { - return NULL; - } + g_free (mechanism); } /* Initiate sasl auth. features should contain the stream features stanza as @@ -641,7 +676,6 @@ wocky_sasl_auth_authenticate_async (WockySaslAuth *sasl, WockySaslAuthPrivate *priv = sasl->priv; WockyNode *mech_node; GSList *mechanisms, *t; - WockySaslHandler *handler = NULL; g_assert (sasl != NULL); g_assert (features != NULL); @@ -663,25 +697,10 @@ wocky_sasl_auth_authenticate_async (WockySaslAuth *sasl, priv->result = g_simple_async_result_new (G_OBJECT (sasl), callback, user_data, wocky_sasl_auth_authenticate_finish); - handler = wocky_sasl_auth_select_handler (sasl, allow_plain, mechanisms); - if (handler == NULL) - { - DEBUG ("No supported mechanisms found"); - - g_object_unref (priv->result); - priv->result = NULL; - - g_simple_async_report_error_in_idle (G_OBJECT (sasl), - callback, user_data, - WOCKY_SASL_AUTH_ERROR, - WOCKY_SASL_AUTH_ERROR_NO_SUPPORTED_MECHANISMS, - "No supported mechanisms found"); - - goto out; - } - - wocky_sasl_auth_start_mechanism (sasl, handler); + wocky_auth_registry_start_auth_async (priv->auth_registry, mechanisms, + allow_plain, TRUE, priv->username, priv->password, priv->server, + wocky_sasl_auth_start_cb, sasl); out: for (t = mechanisms ; t != NULL; t = g_slist_next (t)) @@ -691,19 +710,3 @@ out: g_slist_free (mechanisms); } - -/** - * wocky_sasl_auth_add_handler: - * - * Provide an external SASL handler to be used during authentication. Handlers - * (and therefore SASL mechanisms) are prioritised in the order they are - * added (handlers added earlier take precedence over those added later). - **/ -void -wocky_sasl_auth_add_handler (WockySaslAuth *auth, WockySaslHandler *handler) -{ - WockySaslAuthPrivate *priv = auth->priv; - - g_object_ref (handler); - priv->handlers = g_slist_append (priv->handlers, handler); -} diff --git a/wocky/wocky-sasl-auth.h b/wocky/wocky-sasl-auth.h index 2833d28..a92ff12 100644 --- a/wocky/wocky-sasl-auth.h +++ b/wocky/wocky-sasl-auth.h @@ -23,9 +23,9 @@ #include #include -#include "wocky-sasl-handler.h" #include "wocky-stanza.h" #include "wocky-xmpp-connection.h" +#include "wocky-auth-registry.h" G_BEGIN_DECLS @@ -89,7 +89,8 @@ GType wocky_sasl_auth_get_type (void); WockySaslAuth *wocky_sasl_auth_new (const gchar *server, const gchar *username, const gchar *password, - WockyXmppConnection *connection); + WockyXmppConnection *connection, + WockyAuthRegistry *auth_registry); void wocky_sasl_auth_add_handler (WockySaslAuth *sasl, WockySaslHandler *handler); diff --git a/wocky/wocky-sasl-digest-md5.c b/wocky/wocky-sasl-digest-md5.c index e6f6c26..8c72eb5 100644 --- a/wocky/wocky-sasl-digest-md5.c +++ b/wocky/wocky-sasl-digest-md5.c @@ -136,7 +136,7 @@ wocky_sasl_digest_md5_class_init ( static gboolean digest_md5_handle_auth_data (WockySaslHandler *handler, - const gchar *data, gchar **response, GError **error); + const GString *data, GString **response, GError **error); static gboolean digest_md5_handle_success (WockySaslHandler *handler, @@ -191,12 +191,12 @@ strndup_unescaped (const gchar *str, gsize len) } static GHashTable * -digest_md5_challenge_to_hash (const gchar * challenge) +digest_md5_challenge_to_hash (const GString * challenge) { GHashTable *result = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); const gchar *keystart, *keyend, *valstart; - const gchar *c = challenge; + const gchar *c = challenge->str; gchar *key, *val; do { @@ -263,7 +263,7 @@ digest_md5_challenge_to_hash (const gchar * challenge) return result; error: - DEBUG ("Failed to parse challenge: %s", challenge); + DEBUG ("Failed to parse challenge: %s", challenge->str); g_hash_table_destroy (result); return NULL; } @@ -311,7 +311,7 @@ digest_md5_generate_cnonce (void) return g_base64_encode ((guchar *) n, sizeof (n)); } -static gchar * +static GString * md5_prepare_response (WockySaslDigestMd5Private *priv, GHashTable *challenge, GError **error) { @@ -401,7 +401,7 @@ md5_prepare_response (WockySaslDigestMd5Private *priv, GHashTable *challenge, out: g_free (cnonce); - return response != NULL ? g_string_free (response, FALSE) : NULL; + return response; error: g_string_free (response, TRUE); @@ -414,20 +414,17 @@ static gboolean digest_md5_make_initial_response ( WockySaslDigestMd5Private *priv, GHashTable *challenge, - gchar **response, + GString **response, GError **error) { - gchar *raw_response; - raw_response = md5_prepare_response (priv, challenge, error); - if (raw_response == NULL) - return FALSE; + *response = md5_prepare_response (priv, challenge, error); - DEBUG ("Prepared response: %s", raw_response); + if (response == NULL) + return FALSE; - *response = g_base64_encode ((guchar *) raw_response, strlen (raw_response)); + DEBUG ("Prepared response: %s", (*response)->str); - g_free (raw_response); priv->state = WOCKY_SASL_DIGEST_MD5_STATE_SENT_AUTH_RESPONSE; return TRUE; @@ -463,16 +460,12 @@ digest_md5_check_server_response ( } static GHashTable * -auth_data_to_hash (const gchar *data, GError **error) +auth_data_to_hash (const GString *challenge, GError **error) { GHashTable *h = NULL; - gchar *challenge = NULL; - gsize len; - challenge = (gchar *) g_base64_decode (data, &len); - DEBUG("Got digest-md5 challenge: %s", challenge); + DEBUG ("Got digest-md5 challenge: %s", challenge->str); h = digest_md5_challenge_to_hash (challenge); - g_free (challenge); if (h == NULL) g_set_error (error, WOCKY_SASL_AUTH_ERROR, @@ -484,8 +477,8 @@ auth_data_to_hash (const gchar *data, GError **error) static gboolean digest_md5_handle_auth_data (WockySaslHandler *handler, - const gchar *data, - gchar **response, + const GString *data, + GString **response, GError **error) { WockySaslDigestMd5 *self = WOCKY_SASL_DIGEST_MD5 (handler); diff --git a/wocky/wocky-sasl-handler.c b/wocky/wocky-sasl-handler.c index 0778aa7..762164a 100644 --- a/wocky/wocky-sasl-handler.c +++ b/wocky/wocky-sasl-handler.c @@ -46,7 +46,7 @@ wocky_sasl_handler_is_plain (WockySaslHandler *handler) gboolean wocky_sasl_handler_get_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error) { WockySaslInitialResponseFunc func = @@ -64,8 +64,8 @@ wocky_sasl_handler_get_initial_response (WockySaslHandler *handler, gboolean wocky_sasl_handler_handle_auth_data ( WockySaslHandler *handler, - const gchar *data, - gchar **response, + const GString *data, + GString **response, GError **error) { WockySaslAuthDataFunc func = diff --git a/wocky/wocky-sasl-handler.h b/wocky/wocky-sasl-handler.h index 33fdb2e..f5daeec 100644 --- a/wocky/wocky-sasl-handler.h +++ b/wocky/wocky-sasl-handler.h @@ -30,7 +30,7 @@ typedef struct _WockySaslHandler WockySaslHandler; * to send. On error it should return FALSE and set the error **/ typedef gboolean (*WockySaslInitialResponseFunc) (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error); /** @@ -46,8 +46,8 @@ typedef gboolean (*WockySaslInitialResponseFunc) (WockySaslHandler *handler, **/ typedef gboolean (*WockySaslAuthDataFunc) ( WockySaslHandler *handler, - const gchar *data, - gchar **response, + const GString *data, + GString **response, GError **error); /** @@ -76,14 +76,14 @@ wocky_sasl_handler_is_plain (WockySaslHandler *handler); gboolean wocky_sasl_handler_get_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error); gboolean wocky_sasl_handler_handle_auth_data ( WockySaslHandler *handler, - const gchar *data, - gchar **response, + const GString *data, + GString **response, GError **error); gboolean diff --git a/wocky/wocky-sasl-plain.c b/wocky/wocky-sasl-plain.c index 6accf4a..2a5a893 100644 --- a/wocky/wocky-sasl-plain.c +++ b/wocky/wocky-sasl-plain.c @@ -105,7 +105,7 @@ wocky_sasl_plain_class_init (WockySaslPlainClass *klass) static gboolean plain_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error); static void @@ -134,24 +134,21 @@ wocky_sasl_plain_new (const gchar *username, const gchar *password) NULL); } -static gchar * +static GString * plain_generate_initial_response (const gchar *username, const gchar *password) { GString *str = g_string_new (""); - gchar *cstr; g_string_append_c (str, '\0'); g_string_append (str, username); g_string_append_c (str, '\0'); g_string_append (str, password); - cstr = g_base64_encode ((guchar *) str->str, str->len); - g_string_free (str, TRUE); - return cstr; + return str; } static gboolean plain_initial_response (WockySaslHandler *handler, - gchar **initial_data, + GString **initial_data, GError **error) { WockySaslPlain *self = WOCKY_SASL_PLAIN (handler);