[evolution-data-server/meego-eds] Add support for NTLM single-sign-on using /usr/bin/ntlm_auth
- From: Srinivasa Ragavan <sragavan src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/meego-eds] Add support for NTLM single-sign-on using /usr/bin/ntlm_auth
- Date: Wed, 15 Jun 2011 11:55:32 +0000 (UTC)
commit baf9f3c7333e61b1aa17f90248f169e6105eb406
Author: David Woodhouse <David Woodhouse intel com>
Date: Wed Jun 15 17:21:33 2011 +0530
Add support for NTLM single-sign-on using /usr/bin/ntlm_auth
There's a simple test version of ntlm_auth at
http://david.woodhou.se/ntlm_auth_v2.c
Add camel_sasl_try_empty_password() method.
This indicates that a SASL method with the need_password flag can be tried
without providing a password, for single-sign-on using system credentials.
This will be used by NTLM.
camel/camel-sasl-ntlm.c | 135 +++++++++++++++++++++++++++
camel/camel-sasl.c | 25 +++++
camel/camel-sasl.h | 2 +
camel/providers/imap/camel-imap-store.c | 45 +++++++--
camel/providers/imapx/camel-imapx-server.c | 31 ++++++-
camel/providers/smtp/camel-smtp-transport.c | 63 +++++++------
6 files changed, 257 insertions(+), 44 deletions(-)
---
diff --git a/camel/camel-sasl-ntlm.c b/camel/camel-sasl-ntlm.c
index 6d2313a..c8b2c34 100644
--- a/camel/camel-sasl-ntlm.c
+++ b/camel/camel-sasl-ntlm.c
@@ -28,6 +28,7 @@
#include <glib/gi18n-lib.h>
#include "camel-sasl-ntlm.h"
+#include "camel-stream-process.h"
#define CAMEL_SASL_NTLM_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -35,6 +36,10 @@
struct _CamelSaslNTLMPrivate {
gint placeholder; /* allow for future expansion */
+#ifndef G_OS_WIN32
+ CamelStream *helper_stream;
+ gchar *type1_msg;
+#endif
};
CamelServiceAuthType camel_sasl_ntlm_authtype = {
@@ -65,6 +70,8 @@ G_DEFINE_TYPE (CamelSaslNTLM, camel_sasl_ntlm, CAMEL_TYPE_SASL)
#define NTLM_RESPONSE_HOST_OFFSET 44
#define NTLM_RESPONSE_FLAGS_OFFSET 60
+#define NTLM_AUTH_HELPER "/usr/bin/ntlm_auth"
+
static void ntlm_calc_response (const guchar key[21],
const guchar plaintext[8],
guchar results[24]);
@@ -664,6 +671,10 @@ sasl_ntlm_challenge (CamelSasl *sasl,
GByteArray *token,
GError **error)
{
+#ifndef G_OS_WIN32
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (sasl);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+#endif
CamelService *service;
GByteArray *ret;
gchar *user;
@@ -674,6 +685,50 @@ sasl_ntlm_challenge (CamelSasl *sasl,
ret = g_byte_array_new ();
+#ifndef G_OS_WIN32
+ if (priv->helper_stream && !service->url->passwd) {
+ guchar *data;
+ gsize length = 0;
+ char buf[1024];
+ gsize s = 0;
+ buf [0] = 0;
+
+ if (!token || !token->len) {
+ if (priv->type1_msg) {
+ data = g_base64_decode (priv->type1_msg, &length);
+ g_byte_array_append (ret, data, length);
+ g_free (data);
+ g_free (priv->type1_msg);
+ priv->type1_msg = NULL;
+ }
+ return ret;
+ } else {
+ gchar *type2 = g_base64_encode (token->data, token->len);
+ if (camel_stream_printf (priv->helper_stream, "TT %s\n",
+ type2) >= 0 &&
+ (s = camel_stream_read (priv->helper_stream, buf,
+ sizeof(buf), NULL)) > 4 &&
+ buf[0] == 'K' && buf[1] == 'K' && buf[2] == ' ' &&
+ buf[s-1] == '\n') {
+ buf[s-1] = 0;
+ data = g_base64_decode (buf + 3, &length);
+ g_byte_array_append (ret, data, length);
+ g_free (data);
+ } else
+ g_warning ("Didn't get valid response from ntlm_auth helper");
+
+ g_free (type2);
+ }
+ /* On failure, we just return an empty string. Setting the
+ GError would cause the providers to abort the whole
+ connection, and we want them to ask the user for a password
+ and continue. */
+ g_object_unref (priv->helper_stream);
+ priv->helper_stream = NULL;
+ return ret;
+ }
+#endif
+
if (!token || token->len < NTLM_CHALLENGE_NONCE_OFFSET + 8)
goto fail;
@@ -771,15 +826,95 @@ exit:
return ret;
}
+static gboolean sasl_ntlm_try_empty_password (CamelSasl *sasl)
+{
+#ifndef G_OS_WIN32
+ CamelStream *stream = camel_stream_process_new ();
+ CamelService *service = camel_sasl_get_service (sasl);
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (sasl);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+ gchar *user;
+ gchar buf[1024];
+ gsize s;
+ gchar *command;
+ int ret;
+
+ if (access (NTLM_AUTH_HELPER, X_OK))
+ return FALSE;
+
+ user = strchr (service->url->user, '\\');
+ if (user) {
+ command = g_strdup_printf ("%s --helper-protocol ntlmssp-client-1 "
+ "--use-cached-creds --username '%s' "
+ "--domain '%.*s'", NTLM_AUTH_HELPER,
+ user + 1, (int)(user - service->url->user),
+ service->url->user);
+ } else {
+ command = g_strdup_printf ("%s --helper-protocol ntlmssp-client-1 "
+ "--use-cached-creds --username '%s'",
+ NTLM_AUTH_HELPER, service->url->user);
+ }
+ ret = camel_stream_process_connect (CAMEL_STREAM_PROCESS (stream), command, NULL);
+ g_free (command);
+ if (ret) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+ if (camel_stream_printf (stream, "YR\n") < 0) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+ s = camel_stream_read (stream, buf, sizeof(buf), NULL);
+ if (s < 4) {
+ g_object_unref (stream);
+ return FALSE;
+ }
+ if (buf[0] != 'Y' || buf[1] != 'R' || buf[2] != ' ' || buf[s-1] != '\n') {
+ g_object_unref (stream);
+ return FALSE;
+ }
+
+ buf[s-1] = 0;
+
+ priv->helper_stream = stream;
+ priv->type1_msg = g_strdup (buf + 3);
+ return TRUE;
+#else
+ /* Win32 should be able to use SSPI here. */
+ return FALSE;
+#endif
+}
+
+static void
+sasl_ntlm_finalize (GObject *object)
+{
+#ifndef G_OS_WIN32
+ CamelSaslNTLM *ntlm = CAMEL_SASL_NTLM (object);
+ CamelSaslNTLMPrivate *priv = ntlm->priv;
+
+ if (priv->type1_msg)
+ g_free (priv->type1_msg);
+ if (priv->helper_stream)
+ g_object_unref (priv->helper_stream);
+#endif
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (camel_sasl_ntlm_parent_class)->finalize (object);
+}
+
static void
camel_sasl_ntlm_class_init (CamelSaslNTLMClass *class)
{
+ GObjectClass *object_class;
CamelSaslClass *sasl_class;
g_type_class_add_private (class, sizeof (CamelSaslNTLMPrivate));
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = sasl_ntlm_finalize;
+
sasl_class = CAMEL_SASL_CLASS (class);
sasl_class->challenge = sasl_ntlm_challenge;
+ sasl_class->try_empty_password = sasl_ntlm_try_empty_password;
}
static void
diff --git a/camel/camel-sasl.c b/camel/camel-sasl.c
index 8a616ce..9675df5 100644
--- a/camel/camel-sasl.c
+++ b/camel/camel-sasl.c
@@ -400,6 +400,31 @@ camel_sasl_get_authenticated (CamelSasl *sasl)
}
/**
+ * camel_sasl_try_empty_password:
+ * @sasl: a #CamelSasl object
+ *
+ * Returns: whether or not @sasl can attempt to authenticate without a
+ * password being provided by the caller. This will be %TRUE for an
+ * authentication method which can attempt to use single-sign-on
+ * credentials, but which can fall back to using a provided password
+ * so it still has the @need_password flag set in its description.
+ **/
+gboolean
+camel_sasl_try_empty_password (CamelSasl *sasl)
+{
+ CamelSaslClass *class;
+
+ g_return_val_if_fail (CAMEL_IS_SASL (sasl), FALSE);
+
+ class = CAMEL_SASL_GET_CLASS (sasl);
+
+ if (class->try_empty_password == NULL)
+ return FALSE;
+
+ return class->try_empty_password (sasl);
+}
+
+/**
* camel_sasl_set_authenticated:
* @sasl: a #CamelSasl
* @authenticated: whether we have successfully authenticated
diff --git a/camel/camel-sasl.h b/camel/camel-sasl.h
index 3bf3dc8..2b7ef1a 100644
--- a/camel/camel-sasl.h
+++ b/camel/camel-sasl.h
@@ -63,6 +63,7 @@ struct _CamelSasl {
struct _CamelSaslClass {
CamelObjectClass parent_class;
+ gboolean (*try_empty_password) (CamelSasl *sasl);
GByteArray * (*challenge) (CamelSasl *sasl,
GByteArray *token,
GError **error);
@@ -78,6 +79,7 @@ gchar * camel_sasl_challenge_base64 (CamelSasl *sasl,
CamelSasl * camel_sasl_new (const gchar *service_name,
const gchar *mechanism,
CamelService *service);
+gboolean camel_sasl_try_empty_password (CamelSasl *sasl);
gboolean camel_sasl_get_authenticated (CamelSasl *sasl);
void camel_sasl_set_authenticated (CamelSasl *sasl,
gboolean authenticated);
diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c
index f3c8dad..1197ecb 100644
--- a/camel/providers/imap/camel-imap-store.c
+++ b/camel/providers/imap/camel-imap-store.c
@@ -1047,18 +1047,20 @@ imap_check_folder_still_extant (CamelImapStore *imap_store, const gchar *full_na
}
static gboolean
-try_auth (CamelImapStore *store, const gchar *mech, GError **error)
+try_auth (CamelImapStore *store, CamelSasl *sasl, GError **error)
{
- CamelSasl *sasl;
+ CamelService *service = CAMEL_SERVICE (store);
CamelImapResponse *response;
gchar *resp;
gchar *sasl_resp;
- response = camel_imap_command (store, NULL, error, "AUTHENTICATE %s", mech);
- if (!response)
+ response = camel_imap_command (store, NULL, error, "AUTHENTICATE %s",
+ service->url->authmech);
+ if (!response) {
+ g_object_unref (sasl);
return FALSE;
+ }
- sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store));
while (!camel_sasl_get_authenticated (sasl)) {
resp = camel_imap_response_extract_continuation (store, response, error);
if (!resp)
@@ -1107,6 +1109,7 @@ imap_auth_loop (CamelService *service, GError **error)
CamelSession *session = camel_service_get_session (service);
CamelServiceAuthType *authtype = NULL;
CamelImapResponse *response;
+ CamelSasl *sasl = NULL;
gchar *errbuf = NULL;
gboolean authenticated = FALSE;
const gchar *auth_domain;
@@ -1143,11 +1146,23 @@ imap_auth_loop (CamelService *service, GError **error)
return FALSE;
}
- if (!authtype->need_password) {
- authenticated = try_auth (store, authtype->authproto, error);
- if (!authenticated)
+ sasl = camel_sasl_new ("imap", service->url->authmech,
+ CAMEL_SERVICE (store));
+ if (!sasl) {
+ nosasl:
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error creating SASL authentication object."));
+ return FALSE;
+ }
+
+ if (!authtype->need_password || camel_sasl_try_empty_password (sasl)) {
+ authenticated = try_auth (store, sasl, error);
+ if (!authenticated && !authtype->need_password)
return FALSE;
+ sasl = NULL;
}
+
}
while (!authenticated) {
@@ -1186,6 +1201,8 @@ imap_auth_loop (CamelService *service, GError **error)
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_NEED_PASSWORD,
_("Need password for authentication"));
+ if (sasl)
+ g_object_unref (sasl);
return FALSE;
}
}
@@ -1197,9 +1214,15 @@ imap_auth_loop (CamelService *service, GError **error)
return FALSE;
}
- if (authtype)
- authenticated = try_auth (store, authtype->authproto, &local_error);
- else {
+ if (authtype) {
+ if (!sasl)
+ sasl = camel_sasl_new ("imap", service->url->authmech,
+ CAMEL_SERVICE (store));
+ if (!sasl)
+ goto nosasl;
+ authenticated = try_auth (store, sasl, &local_error);
+ sasl = NULL;
+ } else {
response = camel_imap_command (store, NULL, &local_error,
"LOGIN %S %S",
service->url->user,
diff --git a/camel/providers/imapx/camel-imapx-server.c b/camel/providers/imapx/camel-imapx-server.c
index 242e64f..68baa29 100644
--- a/camel/providers/imapx/camel-imapx-server.c
+++ b/camel/providers/imapx/camel-imapx-server.c
@@ -2987,7 +2987,6 @@ exit:
static gboolean
imapx_reconnect (CamelIMAPXServer *is, GError **error)
{
- CamelSasl *sasl;
CamelIMAPXCommand *ic;
gchar *errbuf = NULL;
CamelService *service = (CamelService *) is->store;
@@ -2995,9 +2994,17 @@ imapx_reconnect (CamelIMAPXServer *is, GError **error)
gboolean authenticated = FALSE;
CamelServiceAuthType *authtype = NULL;
guint32 prompt_flags = CAMEL_SESSION_PASSWORD_SECRET;
+ gboolean need_password = FALSE;
while (!authenticated) {
- if (errbuf) {
+ CamelSasl *sasl = NULL;
+
+ if (authtype && authtype->need_password && !need_password) {
+ /* We tried an empty password, but it didn't work */
+ need_password = TRUE;
+ g_free (errbuf);
+ errbuf = NULL;
+ } else if (errbuf) {
/* We need to un-cache the password before prompting again */
prompt_flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
g_free (service->url->passwd);
@@ -3024,6 +3031,7 @@ imapx_reconnect (CamelIMAPXServer *is, GError **error)
authtype = camel_sasl_authtype (service->url->authmech);
if (!authtype) {
+ noauth:
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
@@ -3033,7 +3041,20 @@ imapx_reconnect (CamelIMAPXServer *is, GError **error)
}
}
- if (service->url->passwd == NULL && (!authtype || authtype->need_password)) {
+ if (authtype) {
+ sasl = camel_sasl_new ("imap", authtype->authproto, service);
+ if (!sasl)
+ goto noauth;
+
+ /* If this is the *first* attempt, set 'need_password'
+ as appropriate. */
+ if (!need_password)
+ need_password = authtype->need_password &&
+ !camel_sasl_try_empty_password (sasl);
+ } else
+ need_password = TRUE;
+
+ if (need_password && service->url->passwd == NULL) {
gchar *base_prompt;
gchar *full_prompt;
@@ -3060,10 +3081,12 @@ imapx_reconnect (CamelIMAPXServer *is, GError **error)
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_NEED_PASSWORD,
_("Need password for authentication"));
+ if (sasl)
+ g_object_unref (sasl);
goto exception;
}
}
- if (authtype && (sasl = camel_sasl_new ("imap", authtype->authproto, service))) {
+ if (sasl) {
ic = camel_imapx_command_new (is, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
g_object_unref (sasl);
} else {
diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c
index 272e444..9625b17 100644
--- a/camel/providers/smtp/camel-smtp-transport.c
+++ b/camel/providers/smtp/camel-smtp-transport.c
@@ -66,7 +66,7 @@ static GList *query_auth_types (CamelService *service, GError **error);
static gchar *get_name (CamelService *service, gboolean brief);
static gboolean smtp_helo (CamelSmtpTransport *transport, GError **error);
-static gboolean smtp_auth (CamelSmtpTransport *transport, const gchar *mech, GError **error);
+static gboolean smtp_auth (CamelSmtpTransport *transport, CamelSasl *sasl, GError **error);
static gboolean smtp_mail (CamelSmtpTransport *transport, const gchar *sender,
gboolean has_8bit_parts, GError **error);
static gboolean smtp_rcpt (CamelSmtpTransport *transport, const gchar *recipient, GError **error);
@@ -410,6 +410,7 @@ static gboolean
smtp_connect (CamelService *service, GError **error)
{
CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
+ CamelSasl *sasl = NULL;
gboolean has_authtypes;
/* We (probably) need to check popb4smtp before we connect ... */
@@ -465,15 +466,26 @@ smtp_connect (CamelService *service, GError **error)
return FALSE;
}
- if (!authtype->need_password) {
- /* authentication mechanism doesn't need a password,
- so if it fails there's nothing we can do */
- authenticated = smtp_auth (
- transport, authtype->authproto, error);
- if (!authenticated) {
+ sasl = camel_sasl_new ("smtp", service->url->authmech,
+ CAMEL_SERVICE (transport));
+
+ if (!sasl) {
+ nosasl:
+ g_set_error (
+ error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
+ _("Error creating SASL authentication object."));
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+ if (!authtype->need_password || camel_sasl_try_empty_password (sasl)) {
+ authenticated = smtp_auth (transport, sasl, error);
+ if (!authenticated && !authtype->need_password) {
+ /* authentication mechanism doesn't need a password,
+ so if it fails there's nothing we can do */
camel_service_disconnect (service, TRUE, NULL);
return FALSE;
}
+ sasl = NULL;
}
password_flags = CAMEL_SESSION_PASSWORD_SECRET;
@@ -484,6 +496,7 @@ smtp_connect (CamelService *service, GError **error)
if (errbuf) {
/* We need to un-cache the password before prompting again */
+ password_flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
g_free (service->url->passwd);
service->url->passwd = NULL;
}
@@ -515,12 +528,19 @@ smtp_connect (CamelService *service, GError **error)
CAMEL_SERVICE_ERROR_NEED_PASSWORD,
_("Need password for authentication"));
camel_service_disconnect (service, TRUE, NULL);
+ if (sasl)
+ g_object_unref (sasl);
return FALSE;
}
}
-
- authenticated = smtp_auth (
- transport, authtype->authproto, &local_error);
+ if (!sasl)
+ sasl = camel_sasl_new ("smtp", service->url->authmech,
+ CAMEL_SERVICE (transport));
+ if (!sasl)
+ goto nosasl;
+
+ authenticated = smtp_auth (transport, sasl, &local_error);
+ sasl = NULL;
if (!authenticated) {
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE)) {
@@ -543,11 +563,6 @@ smtp_connect (CamelService *service, GError **error)
service->url->passwd = NULL;
}
- /* Force a password prompt on the next pass, in
- * case we have an invalid password cached. This
- * avoids repeated authentication attempts using
- * the same invalid password. */
- password_flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
}
}
@@ -1077,34 +1092,24 @@ smtp_helo (CamelSmtpTransport *transport, GError **error)
static gboolean
smtp_auth (CamelSmtpTransport *transport,
- const gchar *mech,
+ CamelSasl *sasl,
GError **error)
{
CamelService *service;
gchar *cmdbuf, *respbuf = NULL, *challenge;
gboolean auth_challenge = FALSE;
- CamelSasl *sasl = NULL;
service = CAMEL_SERVICE (transport);
camel_operation_start_transient (NULL, _("SMTP Authentication"));
- sasl = camel_sasl_new ("smtp", mech, service);
- if (!sasl) {
- camel_operation_end (NULL);
- g_set_error (
- error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
- _("Error creating SASL authentication object."));
- return FALSE;
- }
-
challenge = camel_sasl_challenge_base64 (sasl, NULL, error);
if (challenge) {
auth_challenge = TRUE;
- cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
+ cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", service->url->authmech, challenge);
g_free (challenge);
} else {
- cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
+ cmdbuf = g_strdup_printf ("AUTH %s\r\n", service->url->authmech);
}
d(fprintf (stderr, "sending : %s", cmdbuf));
@@ -1138,7 +1143,7 @@ smtp_auth (CamelSmtpTransport *transport,
"authentication mechanism is broken. Please report this to the\n"
"appropriate vendor and suggest that they re-read rfc2554 again\n"
"for the first time (specifically Section 4).\n",
- mech));
+ service->url->authmech));
}
/* eat whtspc */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]