/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* 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.
*
* 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, see .
*
* Authors: Jeffrey Stedfast
*/
/* If building without Kerberos support, this class is an empty shell. */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#ifndef _WIN32
#include
#include
#endif
#include
#include
#include "camel-net-utils.h"
#include "camel-network-settings.h"
#include "camel-sasl-gssapi.h"
#include "camel-session.h"
#ifdef HAVE_KRB5
#ifdef HAVE_HEIMDAL_KRB5
#include
#else
#include
#endif /* HAVE_HEIMDAL_KRB5 */
#ifdef HAVE_ET_COM_ERR_H
#include
#else
#ifdef HAVE_COM_ERR_H
#include
#endif /* HAVE_COM_ERR_H */
#endif /* HAVE_ET_COM_ERR_H */
#ifdef HAVE_MIT_KRB5
#include
#include
#endif /* HAVE_MIT_KRB5 */
#ifdef HAVE_HEIMDAL_KRB5
#include
#else
#ifdef HAVE_SUN_KRB5
#include
#include
extern gss_OID gss_nt_service_name;
#endif /* HAVE_SUN_KRB5 */
#endif /* HAVE_HEIMDAL_KRB5 */
#define CAMEL_SASL_GSSAPI_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiPrivate))
static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
static const gss_OID_desc gss_mech_spnego = {
6,
(gpointer) &spnego_OID
};
#ifndef GSS_C_OID_KRBV5_DES
#define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
#endif
#define DBUS_PATH "/org/gnome/KrbAuthDialog"
#define DBUS_INTERFACE "org.gnome.KrbAuthDialog"
#define DBUS_METHOD "org.gnome.KrbAuthDialog.acquireTgt"
static CamelServiceAuthType sasl_gssapi_auth_type = {
N_("GSSAPI"),
N_("This option will connect to the server using "
"Kerberos 5 authentication."),
"GSSAPI",
FALSE
};
enum {
GSSAPI_STATE_INIT,
GSSAPI_STATE_CONTINUE_NEEDED,
GSSAPI_STATE_COMPLETE,
GSSAPI_STATE_AUTHENTICATED
};
#define GSSAPI_SECURITY_LAYER_NONE (1 << 0)
#define GSSAPI_SECURITY_LAYER_INTEGRITY (1 << 1)
#define GSSAPI_SECURITY_LAYER_PRIVACY (1 << 2)
#define DESIRED_SECURITY_LAYER GSSAPI_SECURITY_LAYER_NONE
struct _CamelSaslGssapiPrivate {
gint state;
gss_ctx_id_t ctx;
gss_name_t target;
gchar *override_host;
gchar *override_user;
gss_OID mech, used_mech;
};
#endif /* HAVE_KRB5 */
G_DEFINE_TYPE (CamelSaslGssapi, camel_sasl_gssapi, CAMEL_TYPE_SASL)
#ifdef HAVE_KRB5
static void
gssapi_set_mechanism_exception (gss_OID mech, OM_uint32 minor, GError **error)
{
OM_uint32 tmajor, tminor, message_status = 0;
char *message = NULL;
do {
char *message_part;
char *new_message;
gss_buffer_desc status_string;
tmajor = gss_display_status (&tminor, minor, GSS_C_MECH_CODE,
mech, &message_status,
&status_string);
if (tmajor != GSS_S_COMPLETE) {
message_part = g_strdup_printf (
_("(Unknown GSSAPI mechanism code: %x)"),
minor);
message_status = 0;
} else {
message_part = g_strdup (status_string.value);
gss_release_buffer (&tminor, &status_string);
}
if (message) {
new_message = g_strconcat (message, message_part, NULL);
free (message_part);
} else {
new_message = message_part;
}
g_free (message);
message = new_message;
} while (message_status != 0);
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
"%s", message);
g_free (message);
}
static void
gssapi_set_exception (gss_OID mech, OM_uint32 major, OM_uint32 minor,
GError **error)
{
const gchar *str;
switch (major) {
case GSS_S_BAD_MECH:
str = _("The specified mechanism is not supported by the "
"provided credential, or is unrecognized by the "
"implementation.");
break;
case GSS_S_BAD_NAME:
str = _("The provided target_name parameter was ill-formed.");
break;
case GSS_S_BAD_NAMETYPE:
str = _("The provided target_name parameter contained an "
"invalid or unsupported type of name.");
break;
case GSS_S_BAD_BINDINGS:
str = _("The input_token contains different channel "
"bindings to those specified via the "
"input_chan_bindings parameter.");
break;
case GSS_S_BAD_SIG:
str = _("The input_token contains an invalid signature, or a "
"signature that could not be verified.");
break;
case GSS_S_NO_CRED:
str = _("The supplied credentials were not valid for context "
"initiation, or the credential handle did not "
"reference any credentials.");
break;
case GSS_S_NO_CONTEXT:
str = _("The supplied context handle did not refer to a valid context.");
break;
case GSS_S_DEFECTIVE_TOKEN:
str = _("The consistency checks performed on the input_token failed.");
break;
case GSS_S_DEFECTIVE_CREDENTIAL:
str = _("The consistency checks performed on the credential failed.");
break;
case GSS_S_CREDENTIALS_EXPIRED:
str = _("The referenced credentials have expired.");
break;
case GSS_S_FAILURE:
return gssapi_set_mechanism_exception (mech, minor, error);
break;
default:
str = _("Bad authentication response from server.");
}
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
"%s", str);
}
static void
sasl_gssapi_finalize (GObject *object)
{
CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
guint32 status;
if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
gss_delete_sec_context (
&status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
if (sasl->priv->target != GSS_C_NO_NAME)
gss_release_name (&status, &sasl->priv->target);
g_free (sasl->priv->override_host);
g_free (sasl->priv->override_user);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (camel_sasl_gssapi_parent_class)->finalize (object);
}
/* DBUS Specific code */
static gboolean
send_dbus_message (const gchar *name)
{
gint success = FALSE;
GError *error = NULL;
GDBusConnection *connection;
GDBusMessage *message, *reply;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (error) {
g_warning ("could not get system bus: %s\n", error->message);
g_error_free (error);
return FALSE;
}
g_dbus_connection_set_exit_on_close (connection, FALSE);
/* Create a new message on the DBUS_INTERFACE */
message = g_dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt");
if (!message) {
g_object_unref (connection);
return FALSE;
}
/* Appends the data as an argument to the message */
if (strchr (name, '\\'))
name = strchr (name, '\\');
g_dbus_message_set_body (message, g_variant_new ("(s)", name));
/* Sends the message: Have a 300 sec wait timeout */
reply = g_dbus_connection_send_message_with_reply_sync (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 300 * 1000, NULL, NULL, &error);
if (!error && reply) {
if (g_dbus_message_to_gerror (reply, &error)) {
g_object_unref (reply);
reply = NULL;
}
}
if (error) {
g_dbus_error_strip_remote_error (error);
g_warning ("%s: %s\n", G_STRFUNC, error->message);
g_error_free (error);
}
if (reply) {
GVariant *body = g_dbus_message_get_body (reply);
if (body)
g_variant_get (body, "(b)", &success);
g_object_unref (reply);
}
/* Free the message */
g_object_unref (message);
g_object_unref (connection);
return success;
}
/* END DBus stuff */
static GByteArray *
sasl_gssapi_challenge_sync (CamelSasl *sasl,
GByteArray *token,
GCancellable *cancellable,
GError **error)
{
CamelSaslGssapiPrivate *priv;
OM_uint32 major, minor, flags, time;
gss_buffer_desc inbuf, outbuf;
GByteArray *challenge = NULL;
gss_buffer_t input_token;
gint conf_state;
gss_qop_t qop;
gchar *str;
struct addrinfo *ai, hints;
const gchar *service_name;
gchar *host = NULL;
gchar *user = NULL;
priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
service_name = camel_sasl_get_service_name (sasl);
if (priv->override_host && priv->override_user) {
host = g_strdup (priv->override_host);
user = g_strdup (priv->override_user);
}
if (!host || !user) {
CamelNetworkSettings *network_settings;
CamelSettings *settings;
CamelService *service;
service = camel_sasl_get_service (sasl);
settings = camel_service_ref_settings (service);
g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
network_settings = CAMEL_NETWORK_SETTINGS (settings);
host = camel_network_settings_dup_host (network_settings);
user = camel_network_settings_dup_user (network_settings);
g_object_unref (settings);
}
g_return_val_if_fail (user != NULL, NULL);
if (!host || !*host) {
g_free (host);
host = g_strdup ("localhost");
}
switch (priv->state) {
case GSSAPI_STATE_INIT:
memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_CANONNAME;
ai = camel_getaddrinfo (
host, NULL, &hints, cancellable, error);
if (ai == NULL)
goto exit;
/* HTTP authentication should be SPNEGO not just KRB5 */
if (!strcmp (service_name, "HTTP"))
priv->mech = (gss_OID)&gss_mech_spnego;
str = g_strdup_printf ("%s@%s", service_name, ai->ai_canonname);
camel_freeaddrinfo (ai);
inbuf.value = str;
inbuf.length = strlen (str);
major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
g_free (str);
if (major != GSS_S_COMPLETE) {
gssapi_set_exception (priv->mech, major, minor, error);
goto exit;
}
input_token = GSS_C_NO_BUFFER;
goto challenge;
break;
case GSSAPI_STATE_CONTINUE_NEEDED:
if (token == NULL) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("Bad authentication response from server."));
goto exit;
}
inbuf.value = token->data;
inbuf.length = token->len;
input_token = &inbuf;
challenge:
major = gss_init_sec_context (
&minor, GSS_C_NO_CREDENTIAL,
&priv->ctx, priv->target,
priv->mech,
GSS_C_MUTUAL_FLAG |
GSS_C_REPLAY_FLAG |
GSS_C_SEQUENCE_FLAG,
0, GSS_C_NO_CHANNEL_BINDINGS,
input_token, &priv->used_mech, &outbuf, &flags, &time);
switch (major) {
case GSS_S_COMPLETE:
priv->state = GSSAPI_STATE_COMPLETE;
break;
case GSS_S_CONTINUE_NEEDED:
priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
break;
default:
if (priv->used_mech == GSS_C_OID_KRBV5_DES &&
major == (OM_uint32) GSS_S_FAILURE &&
(minor == (OM_uint32) KRB5KRB_AP_ERR_TKT_EXPIRED ||
minor == (OM_uint32) KRB5KDC_ERR_NEVER_VALID) &&
send_dbus_message (user))
goto challenge;
gssapi_set_exception (priv->used_mech, major, minor, error);
goto exit;
}
challenge = g_byte_array_new ();
g_byte_array_append (challenge, outbuf.value, outbuf.length);
#ifndef HAVE_HEIMDAL_KRB5
gss_release_buffer (&minor, &outbuf);
#endif
break;
case GSSAPI_STATE_COMPLETE:
if (token == NULL) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("Bad authentication response from server."));
goto exit;
}
inbuf.value = token->data;
inbuf.length = token->len;
major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
if (major != GSS_S_COMPLETE) {
gssapi_set_exception (priv->used_mech, major, minor, error);
goto exit;
}
if (outbuf.length < 4) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("Bad authentication response from server."));
#ifndef HAVE_HEIMDAL_KRB5
gss_release_buffer (&minor, &outbuf);
#endif
goto exit;
}
/* check that our desired security layer is supported */
if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
_("Unsupported security layer."));
#ifndef HAVE_HEIMDAL_KRB5
gss_release_buffer (&minor, &outbuf);
#endif
goto exit;
}
inbuf.length = 4 + strlen (user);
inbuf.value = str = g_malloc (inbuf.length);
memcpy (inbuf.value, outbuf.value, 4);
str[0] = DESIRED_SECURITY_LAYER;
memcpy (str + 4, user, inbuf.length - 4);
#ifndef HAVE_HEIMDAL_KRB5
gss_release_buffer (&minor, &outbuf);
#endif
major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
if (major != GSS_S_COMPLETE) {
gssapi_set_exception (priv->used_mech, major, minor, error);
g_free (str);
goto exit;
}
g_free (str);
challenge = g_byte_array_new ();
g_byte_array_append (challenge, outbuf.value, outbuf.length);
#ifndef HAVE_HEIMDAL_KRB5
gss_release_buffer (&minor, &outbuf);
#endif
priv->state = GSSAPI_STATE_AUTHENTICATED;
camel_sasl_set_authenticated (sasl, TRUE);
break;
default:
break;
}
exit:
g_free (host);
g_free (user);
return challenge;
}
#endif /* HAVE_KRB5 */
static void
camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
{
#ifdef HAVE_KRB5
GObjectClass *object_class;
CamelSaslClass *sasl_class;
g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->finalize = sasl_gssapi_finalize;
sasl_class = CAMEL_SASL_CLASS (class);
sasl_class->auth_type = &sasl_gssapi_auth_type;
sasl_class->challenge_sync = sasl_gssapi_challenge_sync;
#endif /* HAVE_KRB5 */
}
static void
camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
{
#ifdef HAVE_KRB5
sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
sasl->priv->state = GSSAPI_STATE_INIT;
sasl->priv->ctx = GSS_C_NO_CONTEXT;
sasl->priv->target = GSS_C_NO_NAME;
sasl->priv->override_host = NULL;
sasl->priv->override_user = NULL;
sasl->priv->mech = GSS_C_OID_KRBV5_DES;
#endif /* HAVE_KRB5 */
}
/**
* camel_sasl_gssapi_is_available:
*
* Returns: Whether the GSSAPI/KRB5 sasl authentication mechanism is available,
* which means whether Camel was built with KRB5 enabled.
*
* Since: 3.12
**/
gboolean
camel_sasl_gssapi_is_available (void)
{
#ifdef HAVE_KRB5
return TRUE;
#else /* HAVE_KRB5 */
return FALSE;
#endif /* HAVE_KRB5 */
}
/**
* camel_sasl_gssapi_override_host_and_user:
* @override_host: Host name to use during challenge processing; can be %NULL
* @override_user: User name to use during challenge processing; can be %NULL
*
* Set host and user to use, instead of those in CamelService's settings.
* It's both or none, aka either set both, or the settings values are used.
* This is used to not require CamelService instance at all.
*
* Since: 3.12
**/
void
camel_sasl_gssapi_override_host_and_user (CamelSaslGssapi *sasl,
const gchar *override_host,
const gchar *override_user)
{
g_return_if_fail (CAMEL_IS_SASL_GSSAPI (sasl));
#ifdef HAVE_KRB5
if (sasl->priv->override_host != override_host) {
g_free (sasl->priv->override_host);
sasl->priv->override_host = g_strdup (override_host);
}
if (sasl->priv->override_user != override_user) {
g_free (sasl->priv->override_user);
sasl->priv->override_user = g_strdup (override_user);
}
#else /* HAVE_KRB5 */
g_warning ("%s: KRB5 not available", G_STRFUNC);
#endif /* HAVE_KRB5 */
}