[evolution-data-server/meego-eds] Add support for NTLM single-sign-on using /usr/bin/ntlm_auth



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]