# HG changeset patch # User Jelmer Vernooij # Date 1227082153 -3600 # Node ID 33ffbface1039f8f22dad9b271538e979ec10b71 # Parent 1ccc33f5f91fb0dab34ce968501fcbaf05b1163d Applied patch to add GSSAPI support to Loudmouth [#20] This patch adds optional suppor for SASL support through GSSAPI mechanism. Patch from Jelmer Vernooij. committer: Mikael Hallendal diff -r 1ccc33f5f91f -r 33ffbface103 loudmouth/lm-sasl.c --- a/loudmouth/lm-sasl.c Wed Nov 19 08:49:57 2008 +0100 +++ b/loudmouth/lm-sasl.c Wed Nov 19 09:09:13 2008 +0100 @@ -18,10 +18,16 @@ * Boston, MA 02111-1307, USA. */ +#include + #include #include #include +#ifdef HAVE_GSSAPI +#include +#endif + #include "lm-sock.h" #include "lm-debug.h" #include "lm-error.h" @@ -40,7 +46,8 @@ typedef enum { AUTH_TYPE_PLAIN = 1, - AUTH_TYPE_DIGEST = 2 + AUTH_TYPE_DIGEST = 2, + AUTH_TYPE_GSSAPI = 4, } AuthType; typedef enum { @@ -49,6 +56,9 @@ SASL_AUTH_STATE_DIGEST_MD5_STARTED, SASL_AUTH_STATE_DIGEST_MD5_SENT_AUTH_RESPONSE, SASL_AUTH_STATE_DIGEST_MD5_SENT_FINAL_RESPONSE, + SASL_AUTH_STATE_GSSAPI_STARTED, + SASL_AUTH_STATE_GSSAPI_SENT_AUTH_RESPONSE, + SASL_AUTH_STATE_GSSAPI_SENT_FINAL_RESPONSE, } SaslAuthState; struct _LmSASL { @@ -67,6 +77,11 @@ gboolean start_auth; LmSASLResultHandler handler; + +#ifdef HAVE_GSSAPI + gss_ctx_id_t gss_ctx; + gss_name_t gss_service; +#endif }; #define XMPP_NS_SASL_AUTH "urn:ietf:params:xml:ns:xmpp-sasl" @@ -92,6 +107,283 @@ gpointer user_data); +#ifdef HAVE_GSSAPI +static gboolean +sasl_gssapi_handle_challenge (LmSASL *sasl, LmMessageNode *node); + +static void +sasl_gssapi_fail (LmSASL *sasl, + const char *message, + guint32 major_status, + guint32 minor_status) +{ + guint32 err_major_status, err_minor_status; + guint32 msg_ctx = 0; + gss_buffer_desc major_status_string = GSS_C_EMPTY_BUFFER, + minor_status_string = GSS_C_EMPTY_BUFFER; + + err_major_status = gss_display_status (&err_minor_status, major_status, + GSS_C_GSS_CODE, GSS_C_NO_OID, + &msg_ctx, &major_status_string); + + if (!GSS_ERROR(err_major_status)) + err_major_status = gss_display_status (&err_minor_status, minor_status, + GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &minor_status_string); + + g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, "GSSAPI: %s: %s, %s", message, + (char *)major_status_string.value, + (char *)minor_status_string.value); + + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "GSSAPI failure"); + } + + gss_release_buffer(&err_minor_status, &major_status_string); + gss_release_buffer(&err_minor_status, &minor_status_string); +} + +static gss_name_t +sasl_gssapi_get_creds (LmSASL *sasl) +{ + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + gss_name_t service_name = GSS_C_NO_NAME; + guint32 major_status, minor_status; + + token.value = g_strdup_printf ("xmpp@%s", sasl->server); + token.length = strlen ((char *)token.value); + + if (token.value == NULL) { + return GSS_C_NO_NAME; + } + + major_status = gss_import_name (&minor_status, &token, + GSS_C_NT_HOSTBASED_SERVICE, + &service_name); + + if (GSS_ERROR(major_status)) + { + sasl_gssapi_fail (sasl, "while obtaining service principal", + major_status, minor_status); + return GSS_C_NO_NAME; + } + + return service_name; +} + +static gboolean +sasl_gssapi_start (LmSASL *sasl, LmMessageNode *node) +{ + gchar *response64; + gss_buffer_desc input_buffer_desc; + gss_buffer_t input_buffer; + gss_buffer_desc output_buffer_desc; + gss_buffer_t output_buffer; + guint32 major_status, minor_status; + + sasl->gss_ctx = GSS_C_NO_CONTEXT; + sasl->gss_service = sasl_gssapi_get_creds (sasl); + if (sasl->gss_service == GSS_C_NO_NAME) { + return FALSE; + } + + sasl->state = SASL_AUTH_STATE_GSSAPI_STARTED; + + input_buffer_desc.value = NULL; + input_buffer_desc.length = 0; + + input_buffer = &input_buffer_desc; + + output_buffer_desc.value = NULL; + output_buffer_desc.length = 0; + output_buffer = &output_buffer_desc; + + major_status = gss_init_sec_context (&minor_status, + GSS_C_NO_CREDENTIAL, + &sasl->gss_ctx, + sasl->gss_service, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG | + GSS_C_INTEG_FLAG | + GSS_C_CONF_FLAG, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + input_buffer, NULL, output_buffer, NULL, NULL); + + if (GSS_ERROR(major_status)) { + sasl_gssapi_fail (sasl, "during challenge/response", + major_status, minor_status); + return FALSE; + } + + if (major_status != GSS_S_CONTINUE_NEEDED) + sasl->state = SASL_AUTH_STATE_GSSAPI_SENT_AUTH_RESPONSE; + + response64 = g_base64_encode ((const guchar *) output_buffer_desc.value, + (gsize) output_buffer_desc.length); + + lm_message_node_set_value (node, response64); + + g_free (response64); + + return TRUE; +} + +static gboolean +sasl_gssapi_handle_challenge (LmSASL *sasl, LmMessageNode *node) +{ + const gchar *encoded; + gchar *response64; + gss_buffer_t input_buffer; + gss_buffer_desc input_buffer_desc; + gss_buffer_t output_buffer; + gss_buffer_desc output_buffer_desc; + guint32 major_status, minor_status; + gboolean result; + LmMessage *msg; + + encoded = lm_message_node_get_value (node); + if (encoded == NULL) { + input_buffer_desc.value = NULL; + input_buffer_desc.length = 0; + } else { + input_buffer_desc.value = base64_decode (encoded, + &input_buffer_desc.length); + } + input_buffer = &input_buffer_desc; + + output_buffer_desc.value = NULL; + output_buffer_desc.length = 0; + output_buffer = &output_buffer_desc; + + if (sasl->state == SASL_AUTH_STATE_GSSAPI_STARTED) { + major_status = gss_init_sec_context (&minor_status, + GSS_C_NO_CREDENTIAL, + &sasl->gss_ctx, + sasl->gss_service, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG | + GSS_C_INTEG_FLAG | + GSS_C_CONF_FLAG, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + input_buffer, NULL, output_buffer, NULL, NULL); + + if (GSS_ERROR(major_status)) { + sasl_gssapi_fail (sasl, "during challenge/response", + major_status, minor_status); + return FALSE; + } + + if (major_status != GSS_S_CONTINUE_NEEDED) + sasl->state = SASL_AUTH_STATE_GSSAPI_SENT_AUTH_RESPONSE; + + major_status = gss_release_buffer (&minor_status, input_buffer); + if (major_status != GSS_S_COMPLETE) + return FALSE; + } else if (sasl->state == SASL_AUTH_STATE_GSSAPI_SENT_AUTH_RESPONSE) { + gchar *features; + + major_status = gss_unwrap (&minor_status, sasl->gss_ctx, + input_buffer, output_buffer, + NULL, NULL); + + if (GSS_ERROR(major_status)) { + sasl_gssapi_fail (sasl, "while unwrapping server capabilities", + major_status, minor_status); + return FALSE; + } + + major_status = gss_release_buffer (&minor_status, input_buffer); + if (major_status != GSS_S_COMPLETE) + return FALSE; + + major_status = gss_release_buffer (&minor_status, output_buffer); + if (major_status != GSS_S_COMPLETE) + return FALSE; + + input_buffer_desc.length = 4 + strlen(sasl->username); + features = g_malloc (input_buffer_desc.length); + + features[0] = 1; + features[1] = 0xFF; + features[2] = 0xFF; + features[3] = 0xFF; + strcpy(features+4, sasl->username); + + input_buffer_desc.value = features; + major_status = gss_wrap (&minor_status, sasl->gss_ctx, + 0, /* Just integrity checking here */ + GSS_C_QOP_DEFAULT, + input_buffer, + NULL, + output_buffer); + g_free (features); + if (GSS_ERROR(major_status)) { + sasl_gssapi_fail (sasl, "while wrapping required features", + major_status, minor_status); + return FALSE; + } + } else { + g_assert_not_reached (); + } + + msg = lm_message_new (NULL, LM_MESSAGE_TYPE_RESPONSE); + lm_message_node_set_attributes (msg->node, + "xmlns", XMPP_NS_SASL_AUTH, + NULL); + + if (output_buffer_desc.value != NULL) { + response64 = g_base64_encode ((const guchar *) output_buffer_desc.value, + (gsize) output_buffer_desc.length); + + } else { + response64 = g_strdup (""); + } + + major_status = gss_release_buffer (&minor_status, output_buffer); + if (major_status != GSS_S_COMPLETE) { + g_free (response64); + lm_message_unref (msg); + return FALSE; + } + + lm_message_node_set_value (msg->node, response64); + + result = lm_connection_send (sasl->connection, msg, NULL); + + g_free (response64); + lm_message_unref (msg); + + if (!result) { + g_warning ("Failed to send SASL response\n"); + return FALSE; + } + + return TRUE; +} + +static gboolean +sasl_gssapi_finish (LmSASL *sasl, LmMessageNode *node) +{ + OM_uint32 major_status, minor_status; + + if (sasl->gss_service != GSS_C_NO_NAME) + major_status = gss_release_name (&minor_status, &sasl->gss_service); + if (sasl->gss_ctx != GSS_C_NO_CONTEXT) + major_status = gss_delete_sec_context (&minor_status, &sasl->gss_ctx, + GSS_C_NO_BUFFER); + + return TRUE; +} +#endif + + /* DIGEST-MD5 mechanism code from libgibber */ static gchar * @@ -477,6 +769,11 @@ case AUTH_TYPE_DIGEST: sasl_digest_md5_handle_challenge (sasl, message->node); break; +#ifdef HAVE_GSSAPI + case AUTH_TYPE_GSSAPI: + sasl_gssapi_handle_challenge(sasl, message->node); + break; +#endif default: g_warning ("Wrong auth type"); break; @@ -525,6 +822,21 @@ } } break; +#ifdef HAVE_GSSAPI + case AUTH_TYPE_GSSAPI: + if (sasl->state != SASL_AUTH_STATE_GSSAPI_SENT_AUTH_RESPONSE && + sasl->state != SASL_AUTH_STATE_GSSAPI_SENT_FINAL_RESPONSE) { + sasl_gssapi_finish(sasl, message->node); + g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, + "%s: server sent success before starting auth", + G_STRFUNC); + if (sasl->handler) { + sasl->handler (sasl, sasl->connection, + FALSE, "server error"); + } + } + break; +#endif default: g_warning ("Wrong auth type"); break; @@ -630,6 +942,12 @@ mech = "DIGEST-MD5"; sasl->state = SASL_AUTH_STATE_DIGEST_MD5_STARTED; } +#ifdef HAVE_GSSAPI + else if (sasl->auth_type == AUTH_TYPE_GSSAPI) { + mech = "GSSAPI"; + sasl_gssapi_start(sasl, auth_msg->node); + } +#endif lm_message_node_set_attributes (auth_msg->node, "xmlns", XMPP_NS_SASL_AUTH, @@ -661,7 +979,7 @@ for (m = mechanisms->children; m; m = m->next) { const gchar *name; - + name = lm_message_node_get_value (m); if (!name) { @@ -675,7 +993,11 @@ sasl->auth_type |= AUTH_TYPE_DIGEST; continue; } - + if (strcmp (name, "GSSAPI") == 0) { + sasl->auth_type |= AUTH_TYPE_GSSAPI; + continue; + } + g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SASL, "%s: unknown SASL auth mechanism: %s", G_STRFUNC, name); } @@ -694,8 +1016,15 @@ return FALSE; } - /* Prefer DIGEST */ - if (sasl->auth_type & AUTH_TYPE_DIGEST) { + /* Prefer GSSAPI if available */ +#ifdef HAVE_GSSAPI + if (sasl->auth_type & AUTH_TYPE_GSSAPI) { + sasl->auth_type = AUTH_TYPE_GSSAPI; + return sasl_start (sasl); + } +#endif + /* Otherwise prefer DIGEST */ + else if (sasl->auth_type & AUTH_TYPE_DIGEST) { sasl->auth_type = AUTH_TYPE_DIGEST; return sasl_start (sasl); }