From 9e90b875f77b58c3851d9c6ad5d0cdc052e22c17 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 11 Dec 2012 11:10:45 +0100 Subject: [PATCH] Reorganize NTLM authentication, fix ntlm_auth support Move the auth-managery parts of SoupAuthManagerNTLM down into SoupAuthManager, and the NTLMy parts into SoupAuthNTLM (in preparation for supporting other kinds of connection-based auth such as Negotiate). The reorganization also makes it possible to use SoupAuthNTLM to implement a mock version of /usr/bin/ntlm_auth, so we can extend ntlm-test to test both the built-in-NTLM and external-NTLM codepaths. Doing this reveals that, AFAICT, the external codepath did not previously actually work, because it mis-used G_SPAWN_FILE_AND_ARGV_ZERO and so ended up passing incorrect arguments to /usr/bin/ntlm_auth. --- .gitignore | 1 + libsoup/Makefile.am | 2 - libsoup/soup-auth-manager-ntlm.c | 1359 -------------------------------------- libsoup/soup-auth-manager-ntlm.h | 36 - libsoup/soup-auth-manager.c | 142 +++- libsoup/soup-auth-manager.h | 8 - libsoup/soup-auth-ntlm.c | 1077 +++++++++++++++++++++++++++++- libsoup/soup-auth-ntlm.h | 5 - libsoup/soup-auth.c | 29 +- libsoup/soup-message.c | 10 +- libsoup/soup-session.c | 8 +- tests/Makefile.am | 2 + tests/ntlm-test-helper.c | 103 +++ tests/ntlm-test.c | 60 +- 14 files changed, 1352 insertions(+), 1490 deletions(-) delete mode 100644 libsoup/soup-auth-manager-ntlm.c delete mode 100644 libsoup/soup-auth-manager-ntlm.h create mode 100644 tests/ntlm-test-helper.c diff --git a/.gitignore b/.gitignore index e02235b..9fbae76 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ tests/get tests/getbug tests/header-parsing tests/httpd.conf +tests/ntlm-test-helper tests/pull-api tests/simple-httpd tests/simple-proxy diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 59553f0..7a9d6fc 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -107,8 +107,6 @@ libsoup_2_4_la_SOURCES = \ soup-auth-domain-digest.c \ soup-auth-manager.h \ soup-auth-manager.c \ - soup-auth-manager-ntlm.h \ - soup-auth-manager-ntlm.c \ soup-body-input-stream.h \ soup-body-input-stream.c \ soup-body-output-stream.h \ diff --git a/libsoup/soup-auth-manager-ntlm.c b/libsoup/soup-auth-manager-ntlm.c deleted file mode 100644 index ecb9c7c..0000000 --- a/libsoup/soup-auth-manager-ntlm.c +++ /dev/null @@ -1,1359 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-auth-manager-ntlm.c: NTLM auth manager - * - * Copyright (C) 2001-2007 Novell, Inc. - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#include "soup-auth-manager-ntlm.h" -#include "soup.h" -#include "soup-auth-ntlm.h" -#include "soup-message-private.h" - -static void soup_auth_manager_ntlm_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); -static SoupSessionFeatureInterface *soup_auth_manager_parent_feature_interface; - -G_DEFINE_TYPE_WITH_CODE (SoupAuthManagerNTLM, soup_auth_manager_ntlm, SOUP_TYPE_AUTH_MANAGER, - G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, - soup_auth_manager_ntlm_session_feature_init)) - -typedef enum { - SOUP_NTLM_NEW, -#ifdef USE_NTLM_AUTH - SOUP_NTLM_SENT_SSO_REQUEST, - SOUP_NTLM_RECEIVED_SSO_CHALLENGE, - SOUP_NTLM_SENT_SSO_RESPONSE, - SOUP_NTLM_SSO_UNAVAILABLE, - SOUP_NTLM_SSO_FAILED, -#endif - SOUP_NTLM_SENT_REQUEST, - SOUP_NTLM_RECEIVED_CHALLENGE, - SOUP_NTLM_SENT_RESPONSE, - SOUP_NTLM_FAILED -} SoupNTLMState; - -typedef struct { - SoupSocket *socket; - SoupNTLMState state; - char *response_header; - - char *nonce, *domain; - SoupAuth *auth; -#ifdef USE_NTLM_AUTH - char *challenge_header; - int fd_in; - int fd_out; -#endif -} SoupNTLMConnection; - -static void free_ntlm_connection (SoupNTLMConnection *conn); - -typedef struct { - gboolean use_ntlm; - - SoupSession *session; - GHashTable *connections_by_msg; - GHashTable *connections_by_id; -#ifdef USE_NTLM_AUTH - gboolean ntlm_auth_accessible; -#endif -} SoupAuthManagerNTLMPrivate; -#define SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMPrivate)) - -static char *soup_ntlm_request (void); -static gboolean soup_ntlm_parse_challenge (const char *challenge, - char **nonce, - char **default_domain); -static char *soup_ntlm_response (const char *nonce, - const char *user, - const char *password, - const char *host, - const char *domain); -#ifdef USE_NTLM_AUTH -static void sso_ntlm_close (SoupNTLMConnection *conn); -#endif - -static void -soup_auth_manager_ntlm_init (SoupAuthManagerNTLM *ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - - priv->connections_by_id = g_hash_table_new_full (NULL, NULL, NULL, - (GDestroyNotify)free_ntlm_connection); - priv->connections_by_msg = g_hash_table_new (NULL, NULL); -#ifdef USE_NTLM_AUTH - priv->ntlm_auth_accessible = (access (NTLM_AUTH, X_OK) == 0); -#endif -} - -static void -free_ntlm_connection (SoupNTLMConnection *conn) -{ - g_free (conn->response_header); - g_free (conn->nonce); - g_free (conn->domain); - g_clear_object (&conn->auth); -#ifdef USE_NTLM_AUTH - g_free (conn->challenge_header); - sso_ntlm_close (conn); -#endif - g_slice_free (SoupNTLMConnection, conn); -} - -static void -soup_auth_manager_ntlm_finalize (GObject *object) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (object); - - g_hash_table_destroy (priv->connections_by_id); - g_hash_table_destroy (priv->connections_by_msg); - - G_OBJECT_CLASS (soup_auth_manager_ntlm_parent_class)->finalize (object); -} - -static void -soup_auth_manager_ntlm_class_init (SoupAuthManagerNTLMClass *auth_manager_ntlm_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_ntlm_class); - - g_type_class_add_private (auth_manager_ntlm_class, sizeof (SoupAuthManagerNTLMPrivate)); - - object_class->finalize = soup_auth_manager_ntlm_finalize; -} - -static void -soup_auth_manager_ntlm_attach (SoupSessionFeature *manager, SoupSession *session) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (manager); - - /* FIXME: should support multiple sessions */ - priv->session = session; - - soup_auth_manager_parent_feature_interface->attach (manager, session); -} - -static void -delete_conn (SoupSocket *socket, gpointer user_data) -{ - SoupAuthManagerNTLMPrivate *priv = user_data; - - g_hash_table_remove (priv->connections_by_id, socket); - g_signal_handlers_disconnect_by_func (socket, delete_conn, priv); -} - -static SoupNTLMConnection * -get_connection (SoupAuthManagerNTLMPrivate *priv, SoupSocket *socket) -{ - SoupNTLMConnection *conn; - - conn = g_hash_table_lookup (priv->connections_by_id, socket); - if (conn) - return conn; - - conn = g_slice_new0 (SoupNTLMConnection); - conn->socket = socket; - conn->state = SOUP_NTLM_NEW; -#ifdef USE_NTLM_AUTH - conn->fd_in = -1; - conn->fd_out = -1; -#endif - g_hash_table_insert (priv->connections_by_id, socket, conn); - - g_signal_connect (socket, "disconnected", - G_CALLBACK (delete_conn), priv); - return conn; -} - -static void -unset_conn (SoupMessage *msg, gpointer user_data) -{ - SoupAuthManagerNTLMPrivate *priv = user_data; - - g_hash_table_remove (priv->connections_by_msg, msg); - g_signal_handlers_disconnect_by_func (msg, unset_conn, priv); -} - -static SoupNTLMConnection * -set_connection_for_msg (SoupAuthManagerNTLMPrivate *priv, SoupMessage *msg, - SoupNTLMConnection *conn) -{ - if (!g_hash_table_lookup (priv->connections_by_msg, msg)) { - g_signal_connect (msg, "finished", - G_CALLBACK (unset_conn), priv); - g_signal_connect (msg, "restarted", - G_CALLBACK (unset_conn), priv); - } - g_hash_table_insert (priv->connections_by_msg, msg, conn); - - return conn; -} - -static SoupNTLMConnection * -get_connection_for_msg (SoupAuthManagerNTLMPrivate *priv, SoupMessage *msg) -{ - return g_hash_table_lookup (priv->connections_by_msg, msg); -} - -#ifdef USE_NTLM_AUTH -static void -sso_ntlm_close (SoupNTLMConnection *conn) -{ - if (conn->fd_in != -1) { - close (conn->fd_in); - conn->fd_in = -1; - } - - if (conn->fd_out != -1) { - close (conn->fd_out); - conn->fd_out = -1; - } -} - -static gboolean -sso_ntlm_initiate (SoupNTLMConnection *conn, SoupAuthManagerNTLMPrivate *priv) -{ - char *username = NULL, *slash, *domain = NULL; - char *argv[9]; - gboolean ret; - - /* Return if ntlm_auth execution process exist already */ - if (conn->fd_in != -1 && conn->fd_out != -1) - return TRUE; - else - /* Clean all sso data before re-initiate */ - sso_ntlm_close (conn); - - if (!priv->ntlm_auth_accessible) - goto done; - - username = getenv ("NTLMUSER"); - if (!username) - username = getenv ("USER"); - if (!username) - goto done; - - slash = strpbrk (username, "\\/"); - if (slash) { - domain = g_strdup (username); - slash = domain + (slash - username); - *slash = '\0'; - username = slash + 1; - } - - argv[0] = NTLM_AUTH; - argv[1] = "--helper-protocol"; - argv[2] = "ntlmssp-client-1"; - argv[3] = "--use-cached-creds"; - argv[4] = "--username"; - argv[5] = username; - argv[6] = domain ? "--domain" : NULL; - argv[7] = domain; - argv[8] = NULL; - /* Spawn child process */ - ret = g_spawn_async_with_pipes (NULL, argv, NULL, - G_SPAWN_FILE_AND_ARGV_ZERO | - G_SPAWN_STDERR_TO_DEV_NULL, - NULL, NULL, - NULL, &conn->fd_in, &conn->fd_out, - NULL, NULL); - if (!ret) - goto done; - g_free (domain); - return TRUE; -done: - g_free (domain); - return FALSE; -} - -static char * -sso_ntlm_response (SoupNTLMConnection *conn, const char *input, SoupNTLMState conn_state) -{ - ssize_t size; - char buf[1024], *response = NULL; - char *tmpbuf = buf; - size_t len_in = strlen (input), len_out = sizeof (buf); - - while (len_in > 0) { - int written = write (conn->fd_in, input, len_in); - if (written == -1) { - /* Interrupted by a signal, retry it */ - if (errno == EINTR) - continue; - /* write failed if other errors happen */ - goto done; - } - input += written; - len_in -= written; - } - /* Read one line */ - while (len_out > 0) { - size = read (conn->fd_out, tmpbuf, len_out); - if (size == -1) { - if (errno == EINTR) - continue; - goto done; - } else if (size == 0) - goto done; - else if (tmpbuf[size - 1] == '\n') { - tmpbuf[size - 1] = '\0'; - goto wrfinish; - } - tmpbuf += size; - len_out -= size; - } - goto done; -wrfinish: - if (g_ascii_strcasecmp (buf, "PW") == 0) { - /* Samba/winbind installed but not configured */ - response = g_strdup ("PW"); - goto done; - } - if (conn_state == SOUP_NTLM_NEW && - g_ascii_strncasecmp (buf, "YR ", 3) != 0) - /* invalid response for type 1 message */ - goto done; - if (conn_state == SOUP_NTLM_RECEIVED_SSO_CHALLENGE && - g_ascii_strncasecmp (buf, "KK ", 3) != 0 && - g_ascii_strncasecmp (buf, "AF ", 3) != 0) - /* invalid response for type 3 message */ - goto done; - - response = g_strdup_printf ("NTLM %.*s", (int)(size - 4), buf + 3); - goto done; -done: - return response; -} -#endif /* USE_NTLM_AUTH */ - -static void -ntlm_authorize_pre (SoupMessage *msg, gpointer ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - const char *val; - char *challenge = NULL; - SoupURI *uri; - - conn = get_connection_for_msg (priv, msg); - if (!conn) - return; - - val = soup_message_headers_get_list (msg->response_headers, - "WWW-Authenticate"); - if (!val) - return; - challenge = soup_auth_manager_extract_challenge (val, "NTLM"); - if (!challenge) - return; - - if (conn->state > SOUP_NTLM_SENT_REQUEST) { - /* We already authenticated, but then got another 401. - * That means "permission denied", so don't try to - * authenticate again. - */ - conn->state = SOUP_NTLM_FAILED; - goto done; - } - - if (!soup_ntlm_parse_challenge (challenge, &conn->nonce, &conn->domain)) { - conn->state = SOUP_NTLM_FAILED; - goto done; - } - - conn->auth = soup_auth_ntlm_new (conn->domain, - soup_message_get_uri (msg)->host); -#ifdef USE_NTLM_AUTH - conn->challenge_header = g_strdup (challenge + 5); - if (conn->state == SOUP_NTLM_SENT_SSO_REQUEST) { - conn->state = SOUP_NTLM_RECEIVED_SSO_CHALLENGE; - goto done; - } -#endif - conn->state = SOUP_NTLM_RECEIVED_CHALLENGE; - - uri = soup_message_get_uri (msg); - if (uri->password) - soup_auth_authenticate (conn->auth, uri->user, uri->password); - else { - soup_auth_manager_emit_authenticate (SOUP_AUTH_MANAGER (ntlm), - msg, conn->auth, FALSE); - } - - done: - g_free (challenge); - - /* Remove the WWW-Authenticate headers so the session won't try - * to do Basic auth too. - */ - soup_message_headers_remove (msg->response_headers, "WWW-Authenticate"); -} - -static void -ntlm_authorize_post (SoupMessage *msg, gpointer ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - const char *username = NULL, *password = NULL; - char *slash, *domain = NULL; - SoupMessageFlags flags; - - conn = get_connection_for_msg (priv, msg); - if (!conn || !conn->auth) - return; - -#ifdef USE_NTLM_AUTH - if (conn->state == SOUP_NTLM_RECEIVED_SSO_CHALLENGE) { - char *input, *header; - input = g_strdup_printf ("TT %s\n", conn->challenge_header); - /* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */ - if (sso_ntlm_initiate (conn, priv)) { - header = sso_ntlm_response (conn, input, conn->state); - g_free (input); - /* Close ntlm_auth as it is no longer needed for current connection */ - sso_ntlm_close (conn); - if (!header) { - conn->state = SOUP_NTLM_SSO_FAILED; - g_free (header); - goto ssofailure; - } - if (!g_ascii_strcasecmp (header, "PW")) { - conn->state = SOUP_NTLM_SSO_UNAVAILABLE; - g_free (header); - goto ssofailure; - } - - conn->response_header = header; - soup_session_requeue_message (priv->session, msg); - goto done; - } - conn->state = SOUP_NTLM_SSO_FAILED; -ssofailure: - soup_session_requeue_message (priv->session, msg); - goto done; - } -#endif - username = soup_auth_ntlm_get_username (conn->auth); - password = soup_auth_ntlm_get_password (conn->auth); - if (!username || !password) - goto done; - - slash = strpbrk (username, "\\/"); - if (slash) { - domain = g_strdup (username); - slash = domain + (slash - username); - *slash = '\0'; - username = slash + 1; - } else - domain = g_strdup (""); - - conn->response_header = soup_ntlm_response (conn->nonce, - username, password, - NULL, domain); - - flags = soup_message_get_flags (msg); - soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); - soup_session_requeue_message (priv->session, msg); - -done: - g_free (domain); - g_free (conn->domain); - conn->domain = NULL; - g_free (conn->nonce); - conn->nonce = NULL; - g_object_unref (conn->auth); - conn->auth = NULL; -} - -static void -soup_auth_manager_ntlm_request_queued (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - - if (priv->use_ntlm) { - soup_message_add_status_code_handler ( - msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (ntlm_authorize_pre), ntlm); - soup_message_add_status_code_handler ( - msg, "got_body", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (ntlm_authorize_post), ntlm); - } - - soup_auth_manager_parent_feature_interface->request_queued (ntlm, session, msg); -} - -static void -soup_auth_manager_ntlm_request_started (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg, - SoupSocket *socket) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - char *header = NULL; - - if (!priv->use_ntlm) - goto super; - - conn = get_connection (priv, socket); - set_connection_for_msg (priv, msg, conn); - - switch (conn->state) { - case SOUP_NTLM_NEW: -#ifdef USE_NTLM_AUTH - /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, - * by delegating the NTLM challenge/response protocal to a helper - * in ntlm_auth. - * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html - * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html - * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html - * The preprocessor variable 'USE_NTLM_AUTH' indicates whether - * this feature is enabled. Another one 'NTLM_AUTH' contains absolute - * path of it. - * If NTLM single-sign-on fails, go back to original request handling process. - */ - if (sso_ntlm_initiate (conn, priv)) { - header = sso_ntlm_response (conn, "YR\n", conn->state); - if (header) { - if (g_ascii_strcasecmp (header, "PW") != 0) { - conn->state = SOUP_NTLM_SENT_SSO_REQUEST; - break; - } else { - g_free (header); - header = NULL; - goto ssounavailable; - } - } else { - g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); - goto ssounavailable; - } - } - case SOUP_NTLM_SSO_UNAVAILABLE: - ssounavailable: -#endif - header = soup_ntlm_request (); - conn->state = SOUP_NTLM_SENT_REQUEST; - break; -#ifdef USE_NTLM_AUTH - case SOUP_NTLM_RECEIVED_SSO_CHALLENGE: - header = conn->response_header; - conn->response_header = NULL; - conn->state = SOUP_NTLM_SENT_SSO_RESPONSE; - break; - case SOUP_NTLM_SSO_FAILED: - /* Restart request without SSO */ - g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); - header = soup_ntlm_request (); - conn->state = SOUP_NTLM_SENT_REQUEST; - break; -#endif - case SOUP_NTLM_RECEIVED_CHALLENGE: - header = conn->response_header; - conn->response_header = NULL; - conn->state = SOUP_NTLM_SENT_RESPONSE; - break; - default: - break; - } - - if (header && !soup_message_get_auth (msg)) { - soup_message_headers_replace (msg->request_headers, - "Authorization", header); - g_free (header); - } - -super: - soup_auth_manager_parent_feature_interface->request_started (ntlm, session, msg, socket); -} - -static void -soup_auth_manager_ntlm_request_unqueued (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg) -{ - g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_pre, ntlm); - g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_post, ntlm); - - soup_auth_manager_parent_feature_interface->request_unqueued (ntlm, session, msg); -} - -static gboolean -soup_auth_manager_ntlm_add_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) { - priv->use_ntlm = TRUE; - return TRUE; - } - - return soup_auth_manager_parent_feature_interface->add_feature (feature, type); -} - -static gboolean -soup_auth_manager_ntlm_remove_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) { - priv->use_ntlm = FALSE; - return TRUE; - } - - return soup_auth_manager_parent_feature_interface->remove_feature (feature, type); -} - -static gboolean -soup_auth_manager_ntlm_has_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) - return priv->use_ntlm; - - return soup_auth_manager_parent_feature_interface->has_feature (feature, type); -} - -static void -soup_auth_manager_ntlm_session_feature_init (SoupSessionFeatureInterface *feature_interface, - gpointer interface_data) -{ - soup_auth_manager_parent_feature_interface = - g_type_interface_peek_parent (feature_interface); - - feature_interface->attach = soup_auth_manager_ntlm_attach; - feature_interface->request_queued = soup_auth_manager_ntlm_request_queued; - feature_interface->request_started = soup_auth_manager_ntlm_request_started; - feature_interface->request_unqueued = soup_auth_manager_ntlm_request_unqueued; - feature_interface->add_feature = soup_auth_manager_ntlm_add_feature; - feature_interface->remove_feature = soup_auth_manager_ntlm_remove_feature; - feature_interface->has_feature = soup_auth_manager_ntlm_has_feature; -} - -/* NTLM code */ - -static void md4sum (const unsigned char *in, - int nbytes, - unsigned char digest[16]); - -typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */ - -static void deskey (DES_KS, unsigned char *, int); - -static void des (DES_KS, unsigned char *); - -static void setup_schedule (const guchar *key_56, DES_KS ks); - -static void calc_response (const guchar *key, - const guchar *plaintext, - guchar *results); - -#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \ - "\x4B\x47\x53\x21\x40\x23\x24\x25" \ - "\x00\x00\x00\x00\x00" - -static void -lanmanager_hash (const char *password, guchar hash[21]) -{ - guchar lm_password [15]; - DES_KS ks; - int i; - - for (i = 0; i < 14 && password [i]; i++) - lm_password [i] = toupper ((unsigned char) password [i]); - - for (; i < 15; i++) - lm_password [i] = '\0'; - - memcpy (hash, LM_PASSWORD_MAGIC, 21); - - setup_schedule (lm_password, ks); - des (ks, hash); - - setup_schedule (lm_password + 7, ks); - des (ks, hash + 8); -} - -static void -nt_hash (const char *password, guchar hash[21]) -{ - unsigned char *buf, *p; - - p = buf = g_malloc (strlen (password) * 2); - - while (*password) { - *p++ = *password++; - *p++ = '\0'; - } - - md4sum (buf, p - buf, hash); - memset (hash + 16, 0, 5); - - g_free (buf); -} - -typedef struct { - guint16 length; - guint16 length2; - guint16 offset; - guchar zero_pad[2]; -} NTLMString; - -#define NTLM_CHALLENGE_NONCE_OFFSET 24 -#define NTLM_CHALLENGE_NONCE_LENGTH 8 -#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12 - -#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" -#define NTLM_RESPONSE_FLAGS 0x8201 - -typedef struct { - guchar header[12]; - - NTLMString lm_resp; - NTLMString nt_resp; - NTLMString domain; - NTLMString user; - NTLMString host; - NTLMString session_key; - - guint32 flags; -} NTLMResponse; - -static void -ntlm_set_string (NTLMString *string, int *offset, int len) -{ - string->offset = GUINT16_TO_LE (*offset); - string->length = string->length2 = GUINT16_TO_LE (len); - *offset += len; -} - -static char * -soup_ntlm_request (void) -{ - return g_strdup ("NTLM TlRMTVNTUAABAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA"); -} - -static gboolean -soup_ntlm_parse_challenge (const char *challenge, - char **nonce, - char **default_domain) -{ - gsize clen; - NTLMString domain; - guchar *chall; - - if (strncmp (challenge, "NTLM ", 5) != 0) - return FALSE; - - chall = g_base64_decode (challenge + 5, &clen); - if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET || - clen < NTLM_CHALLENGE_NONCE_OFFSET + NTLM_CHALLENGE_NONCE_LENGTH) { - g_free (chall); - return FALSE; - } - - if (default_domain) { - memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain)); - domain.length = GUINT16_FROM_LE (domain.length); - domain.offset = GUINT16_FROM_LE (domain.offset); - - if (clen < domain.length + domain.offset) { - g_free (chall); - return FALSE; - } - - *default_domain = g_convert ((char *)chall + domain.offset, - domain.length, "UTF-8", "UCS-2LE", - NULL, NULL, NULL); - } - - if (nonce) { - *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET, - NTLM_CHALLENGE_NONCE_LENGTH); - } - - g_free (chall); - return TRUE; -} - -static char * -soup_ntlm_response (const char *nonce, - const char *user, - const char *password, - const char *host, - const char *domain) -{ - int offset; - gsize hlen, dlen, ulen; - guchar hash[21], lm_resp[24], nt_resp[24]; - char *user_conv, *host_conv, *domain_conv; - NTLMResponse resp; - char *out, *p; - int state, save; - - nt_hash (password, hash); - calc_response (hash, (guchar *)nonce, nt_resp); - lanmanager_hash (password, hash); - calc_response (hash, (guchar *)nonce, lm_resp); - - memset (&resp, 0, sizeof (resp)); - memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header)); - resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS); - - offset = sizeof (resp); - - if (!host) - host = "UNKNOWN"; - - domain_conv = g_convert (domain, -1, "UCS-2LE", "UTF-8", NULL, &dlen, NULL); - user_conv = g_convert (user, -1, "UCS-2LE", "UTF-8", NULL, &ulen, NULL); - host_conv = g_convert (host, -1, "UCS-2LE", "UTF-8", NULL, &hlen, NULL); - - ntlm_set_string (&resp.domain, &offset, dlen); - ntlm_set_string (&resp.user, &offset, ulen); - ntlm_set_string (&resp.host, &offset, hlen); - ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp)); - ntlm_set_string (&resp.nt_resp, &offset, sizeof (nt_resp)); - - out = g_malloc (((offset + 3) * 4) / 3 + 6); - strncpy (out, "NTLM ", 5); - p = out + 5; - - state = save = 0; - - p += g_base64_encode_step ((const guchar *) &resp, sizeof (resp), - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) domain_conv, dlen, - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) user_conv, ulen, - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) host_conv, hlen, - FALSE, p, &state, &save); - p += g_base64_encode_step (lm_resp, sizeof (lm_resp), - FALSE, p, &state, &save); - p += g_base64_encode_step (nt_resp, sizeof (nt_resp), - FALSE, p, &state, &save); - p += g_base64_encode_close (FALSE, p, &state, &save); - *p = '\0'; - - g_free (domain_conv); - g_free (user_conv); - g_free (host_conv); - - return out; -} - -/* DES utils */ -/* Set up a key schedule based on a 56bit key */ -static void -setup_schedule (const guchar *key_56, DES_KS ks) -{ - guchar key[8]; - int i, c, bit; - - key[0] = (key_56[0]) ; - key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF); - key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF); - key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF); - key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF); - key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF); - key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF); - key[7] = ((key_56[6] << 1) & 0xFF); - - /* Fix parity */ - for (i = 0; i < 8; i++) { - for (c = bit = 0; bit < 8; bit++) - if (key[i] & (1 << bit)) - c++; - if (!(c & 1)) - key[i] ^= 0x01; - } - - deskey (ks, key, 0); -} - -static void -calc_response (const guchar *key, const guchar *plaintext, guchar *results) -{ - DES_KS ks; - - memcpy (results, plaintext, 8); - memcpy (results + 8, plaintext, 8); - memcpy (results + 16, plaintext, 8); - - setup_schedule (key, ks); - des (ks, results); - - setup_schedule (key + 7, ks); - des (ks, results + 8); - - setup_schedule (key + 14, ks); - des (ks, results + 16); -} - - -/* - * MD4 encoder. (The one everyone else uses is not GPL-compatible; - * this is a reimplementation from spec.) This doesn't need to be - * efficient for our purposes, although it would be nice to fix - * it to not malloc()... - */ - -#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) ) -#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) ) -#define H(X,Y,Z) ( (X)^(Y)^(Z) ) -#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) ) - -static void -md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) -{ - unsigned char *M; - guint32 A, B, C, D, AA, BB, CC, DD, X[16]; - int pbytes, nbits = nbytes * 8, i, j; - - pbytes = (120 - (nbytes % 64)) % 64; - M = alloca (nbytes + pbytes + 8); - memcpy (M, in, nbytes); - memset (M + nbytes, 0, pbytes + 8); - M[nbytes] = 0x80; - M[nbytes + pbytes] = nbits & 0xFF; - M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF; - M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF; - M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF; - - A = 0x67452301; - B = 0xEFCDAB89; - C = 0x98BADCFE; - D = 0x10325476; - - for (i = 0; i < nbytes + pbytes + 8; i += 64) { - for (j = 0; j < 16; j++) { - X[j] = (M[i + j*4]) | - (M[i + j*4 + 1] << 8) | - (M[i + j*4 + 2] << 16) | - (M[i + j*4 + 3] << 24); - } - - AA = A; - BB = B; - CC = C; - DD = D; - - A = ROT (A + F(B, C, D) + X[0], 3); - D = ROT (D + F(A, B, C) + X[1], 7); - C = ROT (C + F(D, A, B) + X[2], 11); - B = ROT (B + F(C, D, A) + X[3], 19); - A = ROT (A + F(B, C, D) + X[4], 3); - D = ROT (D + F(A, B, C) + X[5], 7); - C = ROT (C + F(D, A, B) + X[6], 11); - B = ROT (B + F(C, D, A) + X[7], 19); - A = ROT (A + F(B, C, D) + X[8], 3); - D = ROT (D + F(A, B, C) + X[9], 7); - C = ROT (C + F(D, A, B) + X[10], 11); - B = ROT (B + F(C, D, A) + X[11], 19); - A = ROT (A + F(B, C, D) + X[12], 3); - D = ROT (D + F(A, B, C) + X[13], 7); - C = ROT (C + F(D, A, B) + X[14], 11); - B = ROT (B + F(C, D, A) + X[15], 19); - - A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13); - - A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15); - - A += AA; - B += BB; - C += CC; - D += DD; - } - - digest[0] = A & 0xFF; - digest[1] = (A >> 8) & 0xFF; - digest[2] = (A >> 16) & 0xFF; - digest[3] = (A >> 24) & 0xFF; - digest[4] = B & 0xFF; - digest[5] = (B >> 8) & 0xFF; - digest[6] = (B >> 16) & 0xFF; - digest[7] = (B >> 24) & 0xFF; - digest[8] = C & 0xFF; - digest[9] = (C >> 8) & 0xFF; - digest[10] = (C >> 16) & 0xFF; - digest[11] = (C >> 24) & 0xFF; - digest[12] = D & 0xFF; - digest[13] = (D >> 8) & 0xFF; - digest[14] = (D >> 16) & 0xFF; - digest[15] = (D >> 24) & 0xFF; -} - - -/* Public domain DES implementation from Phil Karn */ -static const guint32 Spbox[8][64] = { - { 0x01010400,0x00000000,0x00010000,0x01010404, - 0x01010004,0x00010404,0x00000004,0x00010000, - 0x00000400,0x01010400,0x01010404,0x00000400, - 0x01000404,0x01010004,0x01000000,0x00000004, - 0x00000404,0x01000400,0x01000400,0x00010400, - 0x00010400,0x01010000,0x01010000,0x01000404, - 0x00010004,0x01000004,0x01000004,0x00010004, - 0x00000000,0x00000404,0x00010404,0x01000000, - 0x00010000,0x01010404,0x00000004,0x01010000, - 0x01010400,0x01000000,0x01000000,0x00000400, - 0x01010004,0x00010000,0x00010400,0x01000004, - 0x00000400,0x00000004,0x01000404,0x00010404, - 0x01010404,0x00010004,0x01010000,0x01000404, - 0x01000004,0x00000404,0x00010404,0x01010400, - 0x00000404,0x01000400,0x01000400,0x00000000, - 0x00010004,0x00010400,0x00000000,0x01010004 }, - { 0x80108020,0x80008000,0x00008000,0x00108020, - 0x00100000,0x00000020,0x80100020,0x80008020, - 0x80000020,0x80108020,0x80108000,0x80000000, - 0x80008000,0x00100000,0x00000020,0x80100020, - 0x00108000,0x00100020,0x80008020,0x00000000, - 0x80000000,0x00008000,0x00108020,0x80100000, - 0x00100020,0x80000020,0x00000000,0x00108000, - 0x00008020,0x80108000,0x80100000,0x00008020, - 0x00000000,0x00108020,0x80100020,0x00100000, - 0x80008020,0x80100000,0x80108000,0x00008000, - 0x80100000,0x80008000,0x00000020,0x80108020, - 0x00108020,0x00000020,0x00008000,0x80000000, - 0x00008020,0x80108000,0x00100000,0x80000020, - 0x00100020,0x80008020,0x80000020,0x00100020, - 0x00108000,0x00000000,0x80008000,0x00008020, - 0x80000000,0x80100020,0x80108020,0x00108000 }, - { 0x00000208,0x08020200,0x00000000,0x08020008, - 0x08000200,0x00000000,0x00020208,0x08000200, - 0x00020008,0x08000008,0x08000008,0x00020000, - 0x08020208,0x00020008,0x08020000,0x00000208, - 0x08000000,0x00000008,0x08020200,0x00000200, - 0x00020200,0x08020000,0x08020008,0x00020208, - 0x08000208,0x00020200,0x00020000,0x08000208, - 0x00000008,0x08020208,0x00000200,0x08000000, - 0x08020200,0x08000000,0x00020008,0x00000208, - 0x00020000,0x08020200,0x08000200,0x00000000, - 0x00000200,0x00020008,0x08020208,0x08000200, - 0x08000008,0x00000200,0x00000000,0x08020008, - 0x08000208,0x00020000,0x08000000,0x08020208, - 0x00000008,0x00020208,0x00020200,0x08000008, - 0x08020000,0x08000208,0x00000208,0x08020000, - 0x00020208,0x00000008,0x08020008,0x00020200 }, - { 0x00802001,0x00002081,0x00002081,0x00000080, - 0x00802080,0x00800081,0x00800001,0x00002001, - 0x00000000,0x00802000,0x00802000,0x00802081, - 0x00000081,0x00000000,0x00800080,0x00800001, - 0x00000001,0x00002000,0x00800000,0x00802001, - 0x00000080,0x00800000,0x00002001,0x00002080, - 0x00800081,0x00000001,0x00002080,0x00800080, - 0x00002000,0x00802080,0x00802081,0x00000081, - 0x00800080,0x00800001,0x00802000,0x00802081, - 0x00000081,0x00000000,0x00000000,0x00802000, - 0x00002080,0x00800080,0x00800081,0x00000001, - 0x00802001,0x00002081,0x00002081,0x00000080, - 0x00802081,0x00000081,0x00000001,0x00002000, - 0x00800001,0x00002001,0x00802080,0x00800081, - 0x00002001,0x00002080,0x00800000,0x00802001, - 0x00000080,0x00800000,0x00002000,0x00802080 }, - { 0x00000100,0x02080100,0x02080000,0x42000100, - 0x00080000,0x00000100,0x40000000,0x02080000, - 0x40080100,0x00080000,0x02000100,0x40080100, - 0x42000100,0x42080000,0x00080100,0x40000000, - 0x02000000,0x40080000,0x40080000,0x00000000, - 0x40000100,0x42080100,0x42080100,0x02000100, - 0x42080000,0x40000100,0x00000000,0x42000000, - 0x02080100,0x02000000,0x42000000,0x00080100, - 0x00080000,0x42000100,0x00000100,0x02000000, - 0x40000000,0x02080000,0x42000100,0x40080100, - 0x02000100,0x40000000,0x42080000,0x02080100, - 0x40080100,0x00000100,0x02000000,0x42080000, - 0x42080100,0x00080100,0x42000000,0x42080100, - 0x02080000,0x00000000,0x40080000,0x42000000, - 0x00080100,0x02000100,0x40000100,0x00080000, - 0x00000000,0x40080000,0x02080100,0x40000100 }, - { 0x20000010,0x20400000,0x00004000,0x20404010, - 0x20400000,0x00000010,0x20404010,0x00400000, - 0x20004000,0x00404010,0x00400000,0x20000010, - 0x00400010,0x20004000,0x20000000,0x00004010, - 0x00000000,0x00400010,0x20004010,0x00004000, - 0x00404000,0x20004010,0x00000010,0x20400010, - 0x20400010,0x00000000,0x00404010,0x20404000, - 0x00004010,0x00404000,0x20404000,0x20000000, - 0x20004000,0x00000010,0x20400010,0x00404000, - 0x20404010,0x00400000,0x00004010,0x20000010, - 0x00400000,0x20004000,0x20000000,0x00004010, - 0x20000010,0x20404010,0x00404000,0x20400000, - 0x00404010,0x20404000,0x00000000,0x20400010, - 0x00000010,0x00004000,0x20400000,0x00404010, - 0x00004000,0x00400010,0x20004010,0x00000000, - 0x20404000,0x20000000,0x00400010,0x20004010 }, - { 0x00200000,0x04200002,0x04000802,0x00000000, - 0x00000800,0x04000802,0x00200802,0x04200800, - 0x04200802,0x00200000,0x00000000,0x04000002, - 0x00000002,0x04000000,0x04200002,0x00000802, - 0x04000800,0x00200802,0x00200002,0x04000800, - 0x04000002,0x04200000,0x04200800,0x00200002, - 0x04200000,0x00000800,0x00000802,0x04200802, - 0x00200800,0x00000002,0x04000000,0x00200800, - 0x04000000,0x00200800,0x00200000,0x04000802, - 0x04000802,0x04200002,0x04200002,0x00000002, - 0x00200002,0x04000000,0x04000800,0x00200000, - 0x04200800,0x00000802,0x00200802,0x04200800, - 0x00000802,0x04000002,0x04200802,0x04200000, - 0x00200800,0x00000000,0x00000002,0x04200802, - 0x00000000,0x00200802,0x04200000,0x00000800, - 0x04000002,0x04000800,0x00000800,0x00200002 }, - { 0x10001040,0x00001000,0x00040000,0x10041040, - 0x10000000,0x10001040,0x00000040,0x10000000, - 0x00040040,0x10040000,0x10041040,0x00041000, - 0x10041000,0x00041040,0x00001000,0x00000040, - 0x10040000,0x10000040,0x10001000,0x00001040, - 0x00041000,0x00040040,0x10040040,0x10041000, - 0x00001040,0x00000000,0x00000000,0x10040040, - 0x10000040,0x10001000,0x00041040,0x00040000, - 0x00041040,0x00040000,0x10041000,0x00001000, - 0x00000040,0x10040040,0x00001000,0x00041040, - 0x10001000,0x00000040,0x10000040,0x10040000, - 0x10040040,0x10000000,0x00040000,0x10001040, - 0x00000000,0x10041040,0x00040040,0x10000040, - 0x10040000,0x10001000,0x10001040,0x00000000, - 0x10041040,0x00041000,0x00041000,0x00001040, - 0x00001040,0x00040040,0x10000000,0x10041000 } -}; - -#undef F -#define F(l,r,key){\ - work = ((r >> 4) | (r << 28)) ^ key[0];\ - l ^= Spbox[6][work & 0x3f];\ - l ^= Spbox[4][(work >> 8) & 0x3f];\ - l ^= Spbox[2][(work >> 16) & 0x3f];\ - l ^= Spbox[0][(work >> 24) & 0x3f];\ - work = r ^ key[1];\ - l ^= Spbox[7][work & 0x3f];\ - l ^= Spbox[5][(work >> 8) & 0x3f];\ - l ^= Spbox[3][(work >> 16) & 0x3f];\ - l ^= Spbox[1][(work >> 24) & 0x3f];\ -} -/* Encrypt or decrypt a block of data in ECB mode */ -static void -des (guint32 ks[16][2], unsigned char block[8]) -{ - guint32 left,right,work; - - /* Read input block and place in left/right in big-endian order */ - left = ((guint32)block[0] << 24) - | ((guint32)block[1] << 16) - | ((guint32)block[2] << 8) - | (guint32)block[3]; - right = ((guint32)block[4] << 24) - | ((guint32)block[5] << 16) - | ((guint32)block[6] << 8) - | (guint32)block[7]; - - /* Hoey's clever initial permutation algorithm, from Outerbridge - * (see Schneier p 478) - * - * The convention here is the same as Outerbridge: rotate each - * register left by 1 bit, i.e., so that "left" contains permuted - * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32 - * (using origin-1 numbering as in the FIPS). This allows us to avoid - * one of the two rotates that would otherwise be required in each of - * the 16 rounds. - */ - work = ((left >> 4) ^ right) & 0x0f0f0f0f; - right ^= work; - left ^= work << 4; - work = ((left >> 16) ^ right) & 0xffff; - right ^= work; - left ^= work << 16; - work = ((right >> 2) ^ left) & 0x33333333; - left ^= work; - right ^= (work << 2); - work = ((right >> 8) ^ left) & 0xff00ff; - left ^= work; - right ^= (work << 8); - right = (right << 1) | (right >> 31); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left << 1) | (left >> 31); - - /* Now do the 16 rounds */ - F(left,right,ks[0]); - F(right,left,ks[1]); - F(left,right,ks[2]); - F(right,left,ks[3]); - F(left,right,ks[4]); - F(right,left,ks[5]); - F(left,right,ks[6]); - F(right,left,ks[7]); - F(left,right,ks[8]); - F(right,left,ks[9]); - F(left,right,ks[10]); - F(right,left,ks[11]); - F(left,right,ks[12]); - F(right,left,ks[13]); - F(left,right,ks[14]); - F(right,left,ks[15]); - - /* Inverse permutation, also from Hoey via Outerbridge and Schneier */ - right = (right << 31) | (right >> 1); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left >> 1) | (left << 31); - work = ((left >> 8) ^ right) & 0xff00ff; - right ^= work; - left ^= work << 8; - work = ((left >> 2) ^ right) & 0x33333333; - right ^= work; - left ^= work << 2; - work = ((right >> 16) ^ left) & 0xffff; - left ^= work; - right ^= work << 16; - work = ((right >> 4) ^ left) & 0x0f0f0f0f; - left ^= work; - right ^= work << 4; - - /* Put the block back into the user's buffer with final swap */ - block[0] = right >> 24; - block[1] = right >> 16; - block[2] = right >> 8; - block[3] = right; - block[4] = left >> 24; - block[5] = left >> 16; - block[6] = left >> 8; - block[7] = left; -} - -/* Key schedule-related tables from FIPS-46 */ - -/* permuted choice table (key) */ -static const unsigned char pc1[] = { - 57, 49, 41, 33, 25, 17, 9, - 1, 58, 50, 42, 34, 26, 18, - 10, 2, 59, 51, 43, 35, 27, - 19, 11, 3, 60, 52, 44, 36, - - 63, 55, 47, 39, 31, 23, 15, - 7, 62, 54, 46, 38, 30, 22, - 14, 6, 61, 53, 45, 37, 29, - 21, 13, 5, 28, 20, 12, 4 -}; - -/* number left rotations of pc1 */ -static const unsigned char totrot[] = { - 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 -}; - -/* permuted choice key (table) */ -static const unsigned char pc2[] = { - 14, 17, 11, 24, 1, 5, - 3, 28, 15, 6, 21, 10, - 23, 19, 12, 4, 26, 8, - 16, 7, 27, 20, 13, 2, - 41, 52, 31, 37, 47, 55, - 30, 40, 51, 45, 33, 48, - 44, 49, 39, 56, 34, 53, - 46, 42, 50, 36, 29, 32 -}; - -/* End of DES-defined tables */ - - -/* bit 0 is left-most in byte */ -static const int bytebit[] = { - 0200,0100,040,020,010,04,02,01 -}; - - -/* Generate key schedule for encryption or decryption - * depending on the value of "decrypt" - */ -static void -deskey (DES_KS k, unsigned char *key, int decrypt) -{ - unsigned char pc1m[56]; /* place to modify pc1 into */ - unsigned char pcr[56]; /* place to rotate pc1 into */ - register int i,j,l; - int m; - unsigned char ks[8]; - - for (j=0; j<56; j++) { /* convert pc1 to bits of key */ - l=pc1[j]-1; /* integer bit location */ - m = l & 07; /* find bit */ - pc1m[j]=(key[l>>3] & /* find which key byte l is in */ - bytebit[m]) /* and which bit of that byte */ - ? 1 : 0; /* and store 1-bit result */ - } - for (i=0; i<16; i++) { /* key chunk for each iteration */ - memset(ks,0,sizeof(ks)); /* Clear key schedule */ - for (j=0; j<56; j++) /* rotate pc1 the right amount */ - pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28]; - /* rotate left and right halves independently */ - for (j=0; j<48; j++){ /* select bits individually */ - /* check bit that goes to ks[j] */ - if (pcr[pc2[j]-1]){ - /* mask it in if it's there */ - l= j % 6; - ks[j/6] |= bytebit[l] >> 2; - } - } - /* Now convert to packed odd/even interleaved form */ - k[i][0] = ((guint32)ks[0] << 24) - | ((guint32)ks[2] << 16) - | ((guint32)ks[4] << 8) - | ((guint32)ks[6]); - k[i][1] = ((guint32)ks[1] << 24) - | ((guint32)ks[3] << 16) - | ((guint32)ks[5] << 8) - | ((guint32)ks[7]); - } -} diff --git a/libsoup/soup-auth-manager-ntlm.h b/libsoup/soup-auth-manager-ntlm.h deleted file mode 100644 index f0b4f57..0000000 --- a/libsoup/soup-auth-manager-ntlm.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifndef SOUP_AUTH_MANAGER_NTLM_H -#define SOUP_AUTH_MANAGER_NTLM_H 1 - -#include "soup-auth-manager.h" - -G_BEGIN_DECLS - -#define SOUP_TYPE_AUTH_MANAGER_NTLM (soup_auth_manager_ntlm_get_type ()) -#define SOUP_AUTH_MANAGER_NTLM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLM)) -#define SOUP_AUTH_MANAGER_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMClass)) -#define SOUP_IS_AUTH_MANAGER_NTLM(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_MANAGER_NTLM)) -#define SOUP_IS_AUTH_MANAGER_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_MANAGER_NTLM)) -#define SOUP_AUTH_MANAGER_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMClass)) - -typedef struct { - SoupAuthManager parent; - -} SoupAuthManagerNTLM; - -typedef struct { - SoupAuthManagerClass parent_class; - -} SoupAuthManagerNTLMClass; - -#define SOUP_AUTH_MANAGER_NTLM_USE_NTLM "use-ntlm" - -GType soup_auth_manager_ntlm_get_type (void); - -G_END_DECLS - -#endif /* SOUP_AUTH_MANAGER_NTLM_NTLM_H */ diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c index 2a2030c..d284826 100644 --- a/libsoup/soup-auth-manager.c +++ b/libsoup/soup-auth-manager.c @@ -36,9 +36,13 @@ G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT, typedef struct { SoupSession *session; GPtrArray *auth_types; + gboolean has_ntlm; SoupAuth *proxy_auth; GHashTable *auth_hosts; + + GHashTable *connauths; + GHashTable *sockets_by_msg; } SoupAuthManagerPrivate; #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate)) @@ -60,6 +64,10 @@ soup_auth_manager_init (SoupAuthManager *manager) soup_uri_host_equal, NULL, (GDestroyNotify)soup_auth_host_free); + + priv->connauths = g_hash_table_new_full (NULL, NULL, + NULL, g_object_unref); + priv->sockets_by_msg = g_hash_table_new (NULL, NULL); } static void @@ -73,6 +81,9 @@ soup_auth_manager_finalize (GObject *object) g_clear_object (&priv->proxy_auth); + g_hash_table_destroy (priv->connauths); + g_hash_table_destroy (priv->sockets_by_msg); + G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object); } @@ -120,6 +131,10 @@ soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) auth_class = g_type_class_ref (type); g_ptr_array_add (priv->auth_types, auth_class); g_ptr_array_sort (priv->auth_types, auth_type_compare_func); + + if (type == SOUP_TYPE_AUTH_NTLM) + priv->has_ntlm = TRUE; + return TRUE; } @@ -134,8 +149,12 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) return FALSE; auth_class = g_type_class_peek (type); + for (i = 0; i < priv->auth_types->len; i++) { if (priv->auth_types->pdata[i] == (gpointer)auth_class) { + if (type == SOUP_TYPE_AUTH_NTLM) + priv->has_ntlm = FALSE; + g_ptr_array_remove_index (priv->auth_types, i); return TRUE; } @@ -162,13 +181,6 @@ soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) return FALSE; } -void -soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying); -} - static void soup_auth_manager_attach (SoupSessionFeature *manager, SoupSession *session) { @@ -252,7 +264,7 @@ next_challenge_start (GSList *items) return NULL; } -char * +static char * soup_auth_manager_extract_challenge (const char *challenges, const char *scheme) { GSList *items, *i, *next; @@ -416,15 +428,77 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, soup_uri_set_password (uri, NULL); soup_uri_set_user (uri, NULL); } else if (!soup_auth_is_authenticated (auth) && can_interact) { - soup_auth_manager_emit_authenticate (manager, msg, auth, - prior_auth_failed); + g_signal_emit (manager, signals[AUTHENTICATE], 0, + msg, auth, prior_auth_failed); } return soup_auth_is_authenticated (auth); } static void -update_auth (SoupMessage *msg, gpointer manager) +delete_connauth (SoupSocket *socket, gpointer user_data) +{ + SoupAuthManagerPrivate *priv = user_data; + + g_hash_table_remove (priv->connauths, socket); + g_signal_handlers_disconnect_by_func (socket, delete_connauth, priv); +} + +static SoupAuth * +get_connauth (SoupAuthManagerPrivate *priv, SoupSocket *socket) +{ + SoupAuth *auth; + + if (!priv->has_ntlm) + return NULL; + + auth = g_hash_table_lookup (priv->connauths, socket); + if (!auth) { + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL); + g_hash_table_insert (priv->connauths, socket, auth); + g_signal_connect (socket, "disconnected", + G_CALLBACK (delete_connauth), priv); + } + + return auth; +} + +static void +unset_socket (SoupMessage *msg, gpointer user_data) +{ + SoupAuthManagerPrivate *priv = user_data; + + g_hash_table_remove (priv->sockets_by_msg, msg); + g_signal_handlers_disconnect_by_func (msg, unset_socket, priv); +} + +static void +set_socket_for_msg (SoupAuthManagerPrivate *priv, + SoupMessage *msg, SoupSocket *sock) +{ + if (!g_hash_table_lookup (priv->sockets_by_msg, msg)) { + g_signal_connect (msg, "finished", + G_CALLBACK (unset_socket), priv); + g_signal_connect (msg, "restarted", + G_CALLBACK (unset_socket), priv); + } + g_hash_table_insert (priv->sockets_by_msg, msg, sock); +} + +static SoupAuth * +get_connauth_for_msg (SoupAuthManagerPrivate *priv, SoupMessage *msg) +{ + SoupSocket *sock; + + sock = g_hash_table_lookup (priv->sockets_by_msg, msg); + if (sock) + return g_hash_table_lookup (priv->connauths, sock); + else + return NULL; +} + +static void +auth_got_headers (SoupMessage *msg, gpointer manager) { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuthHost *host; @@ -434,6 +508,20 @@ update_auth (SoupMessage *msg, gpointer manager) GSList *pspace, *p; gboolean prior_auth_failed = FALSE; + auth = get_connauth_for_msg (priv, msg); + if (auth) { + const char *header; + + header = auth_header_for_message (msg); + if (header && soup_auth_update (auth, msg, header)) { + if (!soup_auth_is_authenticated (auth)) { + g_signal_emit (manager, signals[AUTHENTICATE], 0, + msg, auth, FALSE); + } + return; + } + } + host = get_auth_host_for_message (priv, msg); /* See if we used auth last time */ @@ -492,17 +580,25 @@ update_auth (SoupMessage *msg, gpointer manager) } static void -requeue_if_authenticated (SoupMessage *msg, gpointer manager) +auth_got_body (SoupMessage *msg, gpointer manager) { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); - SoupAuth *auth = lookup_auth (priv, msg); + SoupAuth *auth; + + auth = get_connauth_for_msg (priv, msg); + if (auth && soup_auth_is_authenticated (auth)) { + SoupMessageFlags flags; + flags = soup_message_get_flags (msg); + soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); + } else + auth = lookup_auth (priv, msg); if (auth && soup_auth_is_authenticated (auth)) soup_session_requeue_message (priv->session, msg); } static void -update_proxy_auth (SoupMessage *msg, gpointer manager) +proxy_auth_got_headers (SoupMessage *msg, gpointer manager) { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuth *prior_auth; @@ -527,7 +623,7 @@ update_proxy_auth (SoupMessage *msg, gpointer manager) } static void -requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager) +proxy_auth_got_body (SoupMessage *msg, gpointer manager) { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuth *auth = priv->proxy_auth; @@ -543,17 +639,17 @@ soup_auth_manager_request_queued (SoupSessionFeature *manager, { soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (update_auth), manager); + G_CALLBACK (auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (requeue_if_authenticated), manager); + G_CALLBACK (auth_got_body), manager); soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (update_proxy_auth), manager); + G_CALLBACK (proxy_auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (requeue_if_proxy_authenticated), manager); + G_CALLBACK (proxy_auth_got_body), manager); } static void @@ -566,9 +662,13 @@ soup_auth_manager_request_started (SoupSessionFeature *feature, SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuth *auth; + set_socket_for_msg (priv, msg, socket); auth = lookup_auth (priv, msg); - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) - auth = NULL; + if (auth) { + if (!authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) + auth = NULL; + } else + auth = get_connauth (priv, socket); soup_message_set_auth (msg, auth); auth = priv->proxy_auth; diff --git a/libsoup/soup-auth-manager.h b/libsoup/soup-auth-manager.h index d82fbb1..a4ee316 100644 --- a/libsoup/soup-auth-manager.h +++ b/libsoup/soup-auth-manager.h @@ -32,14 +32,6 @@ typedef struct { GType soup_auth_manager_get_type (void); -void soup_auth_manager_emit_authenticate (SoupAuthManager *manager, - SoupMessage *msg, - SoupAuth *auth, - gboolean retrying); - -char *soup_auth_manager_extract_challenge (const char *challenges, - const char *scheme); - G_END_DECLS #endif /* SOUP_AUTH_MANAGER_H */ diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c index dc24d51..11b7568 100644 --- a/libsoup/soup-auth-ntlm.c +++ b/libsoup/soup-auth-ntlm.c @@ -9,16 +9,71 @@ #include #endif +#include +#include #include +#include #include "soup-auth-ntlm.h" #include "soup.h" +#include "soup-message-private.h" + +static void soup_ntlm_lanmanager_hash (const char *password, + guchar hash[21]); +static void soup_ntlm_nt_hash (const char *password, + guchar hash[21]); +static char *soup_ntlm_request (void); +static gboolean soup_ntlm_parse_challenge (const char *challenge, + char **nonce, + char **default_domain); +static char *soup_ntlm_response (const char *nonce, + const char *user, + guchar nt_hash[21], + guchar lm_hash[21], + const char *host, + const char *domain); + +typedef enum { + SOUP_NTLM_NEW, +#ifdef USE_NTLM_AUTH + SOUP_NTLM_SENT_SSO_REQUEST, + SOUP_NTLM_RECEIVED_SSO_CHALLENGE, + SOUP_NTLM_SENT_SSO_RESPONSE, + SOUP_NTLM_SSO_UNAVAILABLE, + SOUP_NTLM_SSO_FAILED, +#endif + SOUP_NTLM_SENT_REQUEST, + SOUP_NTLM_RECEIVED_CHALLENGE, + SOUP_NTLM_SENT_RESPONSE, + SOUP_NTLM_FAILED +} SoupNTLMState; typedef struct { - char *username, *password; + SoupNTLMState state; + char *username; + guchar nt_hash[21], lm_hash[21]; + char *nonce, *domain; + char *response_header; + +#ifdef USE_NTLM_AUTH + /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, + * by delegating the NTLM challenge/response protocal to a helper + * in ntlm_auth. + * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html + * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html + * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html + */ + int fd_in; + int fd_out; +#endif } SoupAuthNTLMPrivate; #define SOUP_AUTH_NTLM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMPrivate)) +#ifdef USE_NTLM_AUTH +static gboolean ntlm_auth_available, ntlm_auth_debug; +static void sso_ntlm_close (SoupAuthNTLMPrivate *priv); +#endif + /** * SOUP_TYPE_AUTH_NTLM: * @@ -35,6 +90,30 @@ G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_AUTH) static void soup_auth_ntlm_init (SoupAuthNTLM *ntlm) { + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm); +#ifdef USE_NTLM_AUTH + const char *username = NULL, *slash; +#endif + + priv->state = SOUP_NTLM_NEW; + +#ifdef USE_NTLM_AUTH + priv->fd_in = -1; + priv->fd_out = -1; + + username = getenv ("NTLMUSER"); + if (!username) + username = g_get_user_name (); + + slash = strpbrk (username, "\\/"); + if (slash) { + priv->username = g_strdup (slash + 1); + priv->domain = g_strndup (username, slash - username); + } else { + priv->username = g_strdup (username); + priv->domain = NULL; + } +#endif } static void @@ -43,31 +122,212 @@ soup_auth_ntlm_finalize (GObject *object) SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (object); g_free (priv->username); - if (priv->password) { - memset (priv->password, 0, strlen (priv->password)); - g_free (priv->password); - } + g_free (priv->domain); + + memset (priv->nt_hash, 0, sizeof (priv->nt_hash)); + memset (priv->lm_hash, 0, sizeof (priv->lm_hash)); + + g_free (priv->nonce); + g_free (priv->response_header); + +#ifdef USE_NTLM_AUTH + sso_ntlm_close (priv); +#endif G_OBJECT_CLASS (soup_auth_ntlm_parent_class)->finalize (object); } -SoupAuth * -soup_auth_ntlm_new (const char *realm, const char *host) +#ifdef USE_NTLM_AUTH +static void +sso_ntlm_close (SoupAuthNTLMPrivate *priv) { - SoupAuth *auth; + if (priv->fd_in != -1) { + close (priv->fd_in); + priv->fd_in = -1; + } - auth = g_object_new (SOUP_TYPE_AUTH_NTLM, - SOUP_AUTH_REALM, realm, - SOUP_AUTH_HOST, host, - NULL); - return auth; + if (priv->fd_out != -1) { + close (priv->fd_out); + priv->fd_out = -1; + } } static gboolean +sso_ntlm_initiate (SoupAuthNTLMPrivate *priv) +{ + char *argv[9]; + gboolean ret; + + if (!ntlm_auth_available && !ntlm_auth_debug) + return FALSE; + + /* Return if ntlm_auth execution process exist already */ + if (priv->fd_in != -1 && priv->fd_out != -1) + return TRUE; + else { + /* Clean all sso data before re-initiate */ + sso_ntlm_close (priv); + } + + if (ntlm_auth_debug) { + argv[0] = (char *) g_getenv ("SOUP_NTLM_AUTH_DEBUG"); + if (!*argv[0]) + return FALSE; + } else + argv[0] = NTLM_AUTH; + argv[1] = "--helper-protocol"; + argv[2] = "ntlmssp-client-1"; + argv[3] = "--use-cached-creds"; + argv[4] = "--username"; + argv[5] = priv->username; + argv[6] = priv->domain ? "--domain" : NULL; + argv[7] = priv->domain; + argv[8] = NULL; + + ret = g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, + NULL, &priv->fd_in, &priv->fd_out, + NULL, NULL); + return ret; +} + +static char * +sso_ntlm_response (SoupAuthNTLMPrivate *priv, const char *input, SoupNTLMState conn_state) +{ + ssize_t size; + char buf[1024]; + char *tmpbuf = buf; + size_t len_in = strlen (input), len_out = sizeof (buf); + + while (len_in > 0) { + int written = write (priv->fd_in, input, len_in); + if (written == -1) { + if (errno == EINTR) + continue; + /* write failed if other errors happen */ + return NULL; + } + input += written; + len_in -= written; + } + /* Read one line */ + while (len_out > 0) { + size = read (priv->fd_out, tmpbuf, len_out); + if (size == -1) { + if (errno == EINTR) + continue; + return NULL; + } else if (size == 0) + return NULL; + else if (tmpbuf[size - 1] == '\n') { + tmpbuf[size - 1] = '\0'; + goto wrfinish; + } + tmpbuf += size; + len_out -= size; + } + return NULL; + +wrfinish: + if (g_ascii_strcasecmp (buf, "PW") == 0) { + /* Samba/winbind installed but not configured */ + return g_strdup ("PW"); + } + if (conn_state == SOUP_NTLM_NEW && + g_ascii_strncasecmp (buf, "YR ", 3) != 0) { + /* invalid response for type 1 message */ + return NULL; + } + if (conn_state == SOUP_NTLM_RECEIVED_SSO_CHALLENGE && + g_ascii_strncasecmp (buf, "KK ", 3) != 0 && + g_ascii_strncasecmp (buf, "AF ", 3) != 0) { + /* invalid response for type 3 message */ + return NULL; + } + + return g_strdup_printf ("NTLM %.*s", (int)(size - 4), buf + 3); +} +#endif /* USE_NTLM_AUTH */ + +static gboolean soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params) { - g_return_val_if_reached (FALSE); + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + GHashTableIter iter; + gpointer key, value; + char *header; + gboolean success = TRUE; + + if (priv->state > SOUP_NTLM_SENT_REQUEST) { + /* We already authenticated, but then got another 401. + * That means "permission denied", so don't try to + * authenticate again. + */ + priv->state = SOUP_NTLM_FAILED; + return FALSE; + } + + /* The header will be something like "NTLM blahblahblah===", + * which soup_auth_update() will parse as + * "blahblahblah" = "==". Undo that. + */ + g_hash_table_iter_init (&iter, auth_params); + if (!g_hash_table_iter_next (&iter, &key, &value)) + return FALSE; + if (value) + header = g_strdup_printf ("%s=%s", (char *)key, (char *)value); + else + header = g_strdup (key); + + if (!soup_ntlm_parse_challenge (header, &priv->nonce, &priv->domain)) { + g_free (header); + priv->state = SOUP_NTLM_FAILED; + return FALSE; + } + +#ifdef USE_NTLM_AUTH + if (priv->state == SOUP_NTLM_SENT_SSO_REQUEST) { + char *input, *response; + + /* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */ + if (!sso_ntlm_initiate (priv)) { + priv->state = SOUP_NTLM_SSO_FAILED; + success = FALSE; + goto out; + } + + input = g_strdup_printf ("TT %s\n", header); + response = sso_ntlm_response (priv, input, priv->state); + sso_ntlm_close (priv); + g_free (input); + + if (!response) { + priv->state = SOUP_NTLM_SSO_FAILED; + success = FALSE; + } else if (!g_ascii_strcasecmp (header, "PW")) { + priv->state = SOUP_NTLM_SSO_UNAVAILABLE; + g_free (response); + success = FALSE; + } else { + priv->state = SOUP_NTLM_RECEIVED_SSO_CHALLENGE; + priv->response_header = response; + } + } + out: +#endif + + g_free (header); + + if (priv->state == SOUP_NTLM_SENT_REQUEST) + priv->state = SOUP_NTLM_RECEIVED_CHALLENGE; + + g_object_set (G_OBJECT (auth), + SOUP_AUTH_REALM, priv->domain, + SOUP_AUTH_HOST, soup_message_get_uri (msg)->host, + NULL); + return success; } static GSList * @@ -81,24 +341,99 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username, const char *password) { SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + const char *slash, *domain; g_return_if_fail (username != NULL); g_return_if_fail (password != NULL); + g_return_if_fail (priv->nonce != NULL); + + slash = strpbrk (username, "\\/"); + if (slash) { + if (priv->domain) + g_free (priv->domain); + domain = priv->domain = g_strndup (username, slash - username); + priv->username = g_strdup (slash + 1); + } else { + domain = ""; + priv->username = g_strdup (username); + } + + soup_ntlm_nt_hash (password, priv->nt_hash); + soup_ntlm_lanmanager_hash (password, priv->lm_hash); + + priv->response_header = soup_ntlm_response (priv->nonce, + priv->username, + priv->nt_hash, + priv->lm_hash, + NULL, + domain); - priv->username = g_strdup (username); - priv->password = g_strdup (password); + g_free (priv->nonce); + priv->nonce = NULL; } static gboolean soup_auth_ntlm_is_authenticated (SoupAuth *auth) { - return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->password != NULL; + return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->username != NULL && + SOUP_AUTH_NTLM_GET_PRIVATE (auth)->response_header != NULL; } static char * soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg) { - g_return_val_if_reached (NULL); + SoupAuthNTLMPrivate *priv = + SOUP_AUTH_NTLM_GET_PRIVATE (auth); + char *header = NULL; + + switch (priv->state) { + case SOUP_NTLM_NEW: +#ifdef USE_NTLM_AUTH + if (sso_ntlm_initiate (priv)) { + header = sso_ntlm_response (priv, "YR\n", priv->state); + if (header) { + if (g_ascii_strcasecmp (header, "PW") != 0) { + priv->state = SOUP_NTLM_SENT_SSO_REQUEST; + break; + } else { + g_free (header); + header = NULL; + } + } else { + g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); + } + } + /* If NTLM single-sign-on fails, go back to original + * request handling process. + */ + case SOUP_NTLM_SSO_UNAVAILABLE: +#endif + header = soup_ntlm_request (); + priv->state = SOUP_NTLM_SENT_REQUEST; + break; +#ifdef USE_NTLM_AUTH + case SOUP_NTLM_RECEIVED_SSO_CHALLENGE: + header = priv->response_header; + priv->response_header = NULL; + priv->state = SOUP_NTLM_SENT_SSO_RESPONSE; + break; + case SOUP_NTLM_SSO_FAILED: + /* Restart request without SSO */ + g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); + header = soup_ntlm_request (); + priv->state = SOUP_NTLM_SENT_REQUEST; + break; +#endif + case SOUP_NTLM_RECEIVED_CHALLENGE: + header = priv->response_header; + priv->response_header = NULL; + priv->state = SOUP_NTLM_SENT_RESPONSE; + break; + default: + break; + } + + return header; } static void @@ -119,20 +454,710 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) auth_class->get_authorization = soup_auth_ntlm_get_authorization; object_class->finalize = soup_auth_ntlm_finalize; + +#ifdef USE_NTLM_AUTH + ntlm_auth_available = g_file_test (NTLM_AUTH, G_FILE_TEST_IS_EXECUTABLE); + ntlm_auth_debug = (g_getenv ("SOUP_NTLM_AUTH_DEBUG") != NULL); +#endif } -const char * -soup_auth_ntlm_get_username (SoupAuth *auth) +static void md4sum (const unsigned char *in, + int nbytes, + unsigned char digest[16]); + +typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */ + +static void deskey (DES_KS, unsigned char *, int); + +static void des (DES_KS, unsigned char *); + +static void setup_schedule (const guchar *key_56, DES_KS ks); + +static void calc_response (const guchar *key, + const guchar *plaintext, + guchar *results); + +#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \ + "\x4B\x47\x53\x21\x40\x23\x24\x25" \ + "\x00\x00\x00\x00\x00" + +static void +soup_ntlm_lanmanager_hash (const char *password, guchar hash[21]) { - SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + guchar lm_password [15]; + DES_KS ks; + int i; + + for (i = 0; i < 14 && password [i]; i++) + lm_password [i] = g_ascii_toupper ((unsigned char) password [i]); - return priv->username; + for (; i < 15; i++) + lm_password [i] = '\0'; + + memcpy (hash, LM_PASSWORD_MAGIC, 21); + + setup_schedule (lm_password, ks); + des (ks, hash); + + setup_schedule (lm_password + 7, ks); + des (ks, hash + 8); } -const char * -soup_auth_ntlm_get_password (SoupAuth *auth) +static void +soup_ntlm_nt_hash (const char *password, guchar hash[21]) { - SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + unsigned char *buf, *p; + + p = buf = g_malloc (strlen (password) * 2); + + while (*password) { + *p++ = *password++; + *p++ = '\0'; + } + + md4sum (buf, p - buf, hash); + memset (hash + 16, 0, 5); + + g_free (buf); +} + +typedef struct { + guint16 length; + guint16 length2; + guint16 offset; + guchar zero_pad[2]; +} NTLMString; + +#define NTLM_CHALLENGE_NONCE_OFFSET 24 +#define NTLM_CHALLENGE_NONCE_LENGTH 8 +#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12 + +#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" +#define NTLM_RESPONSE_FLAGS 0x8201 + +typedef struct { + guchar header[12]; + + NTLMString lm_resp; + NTLMString nt_resp; + NTLMString domain; + NTLMString user; + NTLMString host; + NTLMString session_key; - return priv->password; + guint32 flags; +} NTLMResponse; + +static void +ntlm_set_string (NTLMString *string, int *offset, int len) +{ + string->offset = GUINT16_TO_LE (*offset); + string->length = string->length2 = GUINT16_TO_LE (len); + *offset += len; +} + +static char * +soup_ntlm_request (void) +{ + return g_strdup ("NTLM TlRMTVNTUAABAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA"); +} + +static gboolean +soup_ntlm_parse_challenge (const char *challenge, + char **nonce, + char **default_domain) +{ + gsize clen; + NTLMString domain; + guchar *chall; + + chall = g_base64_decode (challenge, &clen); + if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET || + clen < NTLM_CHALLENGE_NONCE_OFFSET + NTLM_CHALLENGE_NONCE_LENGTH) { + g_free (chall); + return FALSE; + } + + if (default_domain) { + memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain)); + domain.length = GUINT16_FROM_LE (domain.length); + domain.offset = GUINT16_FROM_LE (domain.offset); + + if (clen < domain.length + domain.offset) { + g_free (chall); + return FALSE; + } + + *default_domain = g_convert ((char *)chall + domain.offset, + domain.length, "UTF-8", "UCS-2LE", + NULL, NULL, NULL); + } + + if (nonce) { + *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET, + NTLM_CHALLENGE_NONCE_LENGTH); + } + + g_free (chall); + return TRUE; +} + +static char * +soup_ntlm_response (const char *nonce, + const char *user, + guchar nt_hash[21], + guchar lm_hash[21], + const char *host, + const char *domain) +{ + int offset; + gsize hlen, dlen, ulen; + guchar lm_resp[24], nt_resp[24]; + char *user_conv, *host_conv, *domain_conv; + NTLMResponse resp; + char *out, *p; + int state, save; + + calc_response (nt_hash, (guchar *)nonce, nt_resp); + calc_response (lm_hash, (guchar *)nonce, lm_resp); + + memset (&resp, 0, sizeof (resp)); + memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header)); + resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS); + + offset = sizeof (resp); + + if (!host) + host = "UNKNOWN"; + + domain_conv = g_convert (domain, -1, "UCS-2LE", "UTF-8", NULL, &dlen, NULL); + user_conv = g_convert (user, -1, "UCS-2LE", "UTF-8", NULL, &ulen, NULL); + host_conv = g_convert (host, -1, "UCS-2LE", "UTF-8", NULL, &hlen, NULL); + + ntlm_set_string (&resp.domain, &offset, dlen); + ntlm_set_string (&resp.user, &offset, ulen); + ntlm_set_string (&resp.host, &offset, hlen); + ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp)); + ntlm_set_string (&resp.nt_resp, &offset, sizeof (nt_resp)); + + out = g_malloc (((offset + 3) * 4) / 3 + 6); + strncpy (out, "NTLM ", 5); + p = out + 5; + + state = save = 0; + + p += g_base64_encode_step ((const guchar *) &resp, sizeof (resp), + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) domain_conv, dlen, + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) user_conv, ulen, + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) host_conv, hlen, + FALSE, p, &state, &save); + p += g_base64_encode_step (lm_resp, sizeof (lm_resp), + FALSE, p, &state, &save); + p += g_base64_encode_step (nt_resp, sizeof (nt_resp), + FALSE, p, &state, &save); + p += g_base64_encode_close (FALSE, p, &state, &save); + *p = '\0'; + + g_free (domain_conv); + g_free (user_conv); + g_free (host_conv); + + return out; +} + +/* DES utils */ +/* Set up a key schedule based on a 56bit key */ +static void +setup_schedule (const guchar *key_56, DES_KS ks) +{ + guchar key[8]; + int i, c, bit; + + key[0] = (key_56[0]) ; + key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF); + key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF); + key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF); + key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF); + key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF); + key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF); + key[7] = ((key_56[6] << 1) & 0xFF); + + /* Fix parity */ + for (i = 0; i < 8; i++) { + for (c = bit = 0; bit < 8; bit++) + if (key[i] & (1 << bit)) + c++; + if (!(c & 1)) + key[i] ^= 0x01; + } + + deskey (ks, key, 0); +} + +static void +calc_response (const guchar *key, const guchar *plaintext, guchar *results) +{ + DES_KS ks; + + memcpy (results, plaintext, 8); + memcpy (results + 8, plaintext, 8); + memcpy (results + 16, plaintext, 8); + + setup_schedule (key, ks); + des (ks, results); + + setup_schedule (key + 7, ks); + des (ks, results + 8); + + setup_schedule (key + 14, ks); + des (ks, results + 16); +} + + +/* + * MD4 encoder. (The one everyone else uses is not GPL-compatible; + * this is a reimplementation from spec.) This doesn't need to be + * efficient for our purposes, although it would be nice to fix + * it to not malloc()... + */ + +#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) ) +#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) ) +#define H(X,Y,Z) ( (X)^(Y)^(Z) ) +#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) ) + +static void +md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) +{ + unsigned char *M; + guint32 A, B, C, D, AA, BB, CC, DD, X[16]; + int pbytes, nbits = nbytes * 8, i, j; + + pbytes = (120 - (nbytes % 64)) % 64; + M = alloca (nbytes + pbytes + 8); + memcpy (M, in, nbytes); + memset (M + nbytes, 0, pbytes + 8); + M[nbytes] = 0x80; + M[nbytes + pbytes] = nbits & 0xFF; + M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF; + M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF; + M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF; + + A = 0x67452301; + B = 0xEFCDAB89; + C = 0x98BADCFE; + D = 0x10325476; + + for (i = 0; i < nbytes + pbytes + 8; i += 64) { + for (j = 0; j < 16; j++) { + X[j] = (M[i + j*4]) | + (M[i + j*4 + 1] << 8) | + (M[i + j*4 + 2] << 16) | + (M[i + j*4 + 3] << 24); + } + + AA = A; + BB = B; + CC = C; + DD = D; + + A = ROT (A + F(B, C, D) + X[0], 3); + D = ROT (D + F(A, B, C) + X[1], 7); + C = ROT (C + F(D, A, B) + X[2], 11); + B = ROT (B + F(C, D, A) + X[3], 19); + A = ROT (A + F(B, C, D) + X[4], 3); + D = ROT (D + F(A, B, C) + X[5], 7); + C = ROT (C + F(D, A, B) + X[6], 11); + B = ROT (B + F(C, D, A) + X[7], 19); + A = ROT (A + F(B, C, D) + X[8], 3); + D = ROT (D + F(A, B, C) + X[9], 7); + C = ROT (C + F(D, A, B) + X[10], 11); + B = ROT (B + F(C, D, A) + X[11], 19); + A = ROT (A + F(B, C, D) + X[12], 3); + D = ROT (D + F(A, B, C) + X[13], 7); + C = ROT (C + F(D, A, B) + X[14], 11); + B = ROT (B + F(C, D, A) + X[15], 19); + + A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13); + + A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15); + + A += AA; + B += BB; + C += CC; + D += DD; + } + + digest[0] = A & 0xFF; + digest[1] = (A >> 8) & 0xFF; + digest[2] = (A >> 16) & 0xFF; + digest[3] = (A >> 24) & 0xFF; + digest[4] = B & 0xFF; + digest[5] = (B >> 8) & 0xFF; + digest[6] = (B >> 16) & 0xFF; + digest[7] = (B >> 24) & 0xFF; + digest[8] = C & 0xFF; + digest[9] = (C >> 8) & 0xFF; + digest[10] = (C >> 16) & 0xFF; + digest[11] = (C >> 24) & 0xFF; + digest[12] = D & 0xFF; + digest[13] = (D >> 8) & 0xFF; + digest[14] = (D >> 16) & 0xFF; + digest[15] = (D >> 24) & 0xFF; +} + + +/* Public domain DES implementation from Phil Karn */ +static const guint32 Spbox[8][64] = { + { 0x01010400,0x00000000,0x00010000,0x01010404, + 0x01010004,0x00010404,0x00000004,0x00010000, + 0x00000400,0x01010400,0x01010404,0x00000400, + 0x01000404,0x01010004,0x01000000,0x00000004, + 0x00000404,0x01000400,0x01000400,0x00010400, + 0x00010400,0x01010000,0x01010000,0x01000404, + 0x00010004,0x01000004,0x01000004,0x00010004, + 0x00000000,0x00000404,0x00010404,0x01000000, + 0x00010000,0x01010404,0x00000004,0x01010000, + 0x01010400,0x01000000,0x01000000,0x00000400, + 0x01010004,0x00010000,0x00010400,0x01000004, + 0x00000400,0x00000004,0x01000404,0x00010404, + 0x01010404,0x00010004,0x01010000,0x01000404, + 0x01000004,0x00000404,0x00010404,0x01010400, + 0x00000404,0x01000400,0x01000400,0x00000000, + 0x00010004,0x00010400,0x00000000,0x01010004 }, + { 0x80108020,0x80008000,0x00008000,0x00108020, + 0x00100000,0x00000020,0x80100020,0x80008020, + 0x80000020,0x80108020,0x80108000,0x80000000, + 0x80008000,0x00100000,0x00000020,0x80100020, + 0x00108000,0x00100020,0x80008020,0x00000000, + 0x80000000,0x00008000,0x00108020,0x80100000, + 0x00100020,0x80000020,0x00000000,0x00108000, + 0x00008020,0x80108000,0x80100000,0x00008020, + 0x00000000,0x00108020,0x80100020,0x00100000, + 0x80008020,0x80100000,0x80108000,0x00008000, + 0x80100000,0x80008000,0x00000020,0x80108020, + 0x00108020,0x00000020,0x00008000,0x80000000, + 0x00008020,0x80108000,0x00100000,0x80000020, + 0x00100020,0x80008020,0x80000020,0x00100020, + 0x00108000,0x00000000,0x80008000,0x00008020, + 0x80000000,0x80100020,0x80108020,0x00108000 }, + { 0x00000208,0x08020200,0x00000000,0x08020008, + 0x08000200,0x00000000,0x00020208,0x08000200, + 0x00020008,0x08000008,0x08000008,0x00020000, + 0x08020208,0x00020008,0x08020000,0x00000208, + 0x08000000,0x00000008,0x08020200,0x00000200, + 0x00020200,0x08020000,0x08020008,0x00020208, + 0x08000208,0x00020200,0x00020000,0x08000208, + 0x00000008,0x08020208,0x00000200,0x08000000, + 0x08020200,0x08000000,0x00020008,0x00000208, + 0x00020000,0x08020200,0x08000200,0x00000000, + 0x00000200,0x00020008,0x08020208,0x08000200, + 0x08000008,0x00000200,0x00000000,0x08020008, + 0x08000208,0x00020000,0x08000000,0x08020208, + 0x00000008,0x00020208,0x00020200,0x08000008, + 0x08020000,0x08000208,0x00000208,0x08020000, + 0x00020208,0x00000008,0x08020008,0x00020200 }, + { 0x00802001,0x00002081,0x00002081,0x00000080, + 0x00802080,0x00800081,0x00800001,0x00002001, + 0x00000000,0x00802000,0x00802000,0x00802081, + 0x00000081,0x00000000,0x00800080,0x00800001, + 0x00000001,0x00002000,0x00800000,0x00802001, + 0x00000080,0x00800000,0x00002001,0x00002080, + 0x00800081,0x00000001,0x00002080,0x00800080, + 0x00002000,0x00802080,0x00802081,0x00000081, + 0x00800080,0x00800001,0x00802000,0x00802081, + 0x00000081,0x00000000,0x00000000,0x00802000, + 0x00002080,0x00800080,0x00800081,0x00000001, + 0x00802001,0x00002081,0x00002081,0x00000080, + 0x00802081,0x00000081,0x00000001,0x00002000, + 0x00800001,0x00002001,0x00802080,0x00800081, + 0x00002001,0x00002080,0x00800000,0x00802001, + 0x00000080,0x00800000,0x00002000,0x00802080 }, + { 0x00000100,0x02080100,0x02080000,0x42000100, + 0x00080000,0x00000100,0x40000000,0x02080000, + 0x40080100,0x00080000,0x02000100,0x40080100, + 0x42000100,0x42080000,0x00080100,0x40000000, + 0x02000000,0x40080000,0x40080000,0x00000000, + 0x40000100,0x42080100,0x42080100,0x02000100, + 0x42080000,0x40000100,0x00000000,0x42000000, + 0x02080100,0x02000000,0x42000000,0x00080100, + 0x00080000,0x42000100,0x00000100,0x02000000, + 0x40000000,0x02080000,0x42000100,0x40080100, + 0x02000100,0x40000000,0x42080000,0x02080100, + 0x40080100,0x00000100,0x02000000,0x42080000, + 0x42080100,0x00080100,0x42000000,0x42080100, + 0x02080000,0x00000000,0x40080000,0x42000000, + 0x00080100,0x02000100,0x40000100,0x00080000, + 0x00000000,0x40080000,0x02080100,0x40000100 }, + { 0x20000010,0x20400000,0x00004000,0x20404010, + 0x20400000,0x00000010,0x20404010,0x00400000, + 0x20004000,0x00404010,0x00400000,0x20000010, + 0x00400010,0x20004000,0x20000000,0x00004010, + 0x00000000,0x00400010,0x20004010,0x00004000, + 0x00404000,0x20004010,0x00000010,0x20400010, + 0x20400010,0x00000000,0x00404010,0x20404000, + 0x00004010,0x00404000,0x20404000,0x20000000, + 0x20004000,0x00000010,0x20400010,0x00404000, + 0x20404010,0x00400000,0x00004010,0x20000010, + 0x00400000,0x20004000,0x20000000,0x00004010, + 0x20000010,0x20404010,0x00404000,0x20400000, + 0x00404010,0x20404000,0x00000000,0x20400010, + 0x00000010,0x00004000,0x20400000,0x00404010, + 0x00004000,0x00400010,0x20004010,0x00000000, + 0x20404000,0x20000000,0x00400010,0x20004010 }, + { 0x00200000,0x04200002,0x04000802,0x00000000, + 0x00000800,0x04000802,0x00200802,0x04200800, + 0x04200802,0x00200000,0x00000000,0x04000002, + 0x00000002,0x04000000,0x04200002,0x00000802, + 0x04000800,0x00200802,0x00200002,0x04000800, + 0x04000002,0x04200000,0x04200800,0x00200002, + 0x04200000,0x00000800,0x00000802,0x04200802, + 0x00200800,0x00000002,0x04000000,0x00200800, + 0x04000000,0x00200800,0x00200000,0x04000802, + 0x04000802,0x04200002,0x04200002,0x00000002, + 0x00200002,0x04000000,0x04000800,0x00200000, + 0x04200800,0x00000802,0x00200802,0x04200800, + 0x00000802,0x04000002,0x04200802,0x04200000, + 0x00200800,0x00000000,0x00000002,0x04200802, + 0x00000000,0x00200802,0x04200000,0x00000800, + 0x04000002,0x04000800,0x00000800,0x00200002 }, + { 0x10001040,0x00001000,0x00040000,0x10041040, + 0x10000000,0x10001040,0x00000040,0x10000000, + 0x00040040,0x10040000,0x10041040,0x00041000, + 0x10041000,0x00041040,0x00001000,0x00000040, + 0x10040000,0x10000040,0x10001000,0x00001040, + 0x00041000,0x00040040,0x10040040,0x10041000, + 0x00001040,0x00000000,0x00000000,0x10040040, + 0x10000040,0x10001000,0x00041040,0x00040000, + 0x00041040,0x00040000,0x10041000,0x00001000, + 0x00000040,0x10040040,0x00001000,0x00041040, + 0x10001000,0x00000040,0x10000040,0x10040000, + 0x10040040,0x10000000,0x00040000,0x10001040, + 0x00000000,0x10041040,0x00040040,0x10000040, + 0x10040000,0x10001000,0x10001040,0x00000000, + 0x10041040,0x00041000,0x00041000,0x00001040, + 0x00001040,0x00040040,0x10000000,0x10041000 } +}; + +#undef F +#define F(l,r,key){\ + work = ((r >> 4) | (r << 28)) ^ key[0];\ + l ^= Spbox[6][work & 0x3f];\ + l ^= Spbox[4][(work >> 8) & 0x3f];\ + l ^= Spbox[2][(work >> 16) & 0x3f];\ + l ^= Spbox[0][(work >> 24) & 0x3f];\ + work = r ^ key[1];\ + l ^= Spbox[7][work & 0x3f];\ + l ^= Spbox[5][(work >> 8) & 0x3f];\ + l ^= Spbox[3][(work >> 16) & 0x3f];\ + l ^= Spbox[1][(work >> 24) & 0x3f];\ +} +/* Encrypt or decrypt a block of data in ECB mode */ +static void +des (guint32 ks[16][2], unsigned char block[8]) +{ + guint32 left,right,work; + + /* Read input block and place in left/right in big-endian order */ + left = ((guint32)block[0] << 24) + | ((guint32)block[1] << 16) + | ((guint32)block[2] << 8) + | (guint32)block[3]; + right = ((guint32)block[4] << 24) + | ((guint32)block[5] << 16) + | ((guint32)block[6] << 8) + | (guint32)block[7]; + + /* Hoey's clever initial permutation algorithm, from Outerbridge + * (see Schneier p 478) + * + * The convention here is the same as Outerbridge: rotate each + * register left by 1 bit, i.e., so that "left" contains permuted + * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32 + * (using origin-1 numbering as in the FIPS). This allows us to avoid + * one of the two rotates that would otherwise be required in each of + * the 16 rounds. + */ + work = ((left >> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= work << 4; + work = ((left >> 16) ^ right) & 0xffff; + right ^= work; + left ^= work << 16; + work = ((right >> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >> 8) ^ left) & 0xff00ff; + left ^= work; + right ^= (work << 8); + right = (right << 1) | (right >> 31); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 1) | (left >> 31); + + /* Now do the 16 rounds */ + F(left,right,ks[0]); + F(right,left,ks[1]); + F(left,right,ks[2]); + F(right,left,ks[3]); + F(left,right,ks[4]); + F(right,left,ks[5]); + F(left,right,ks[6]); + F(right,left,ks[7]); + F(left,right,ks[8]); + F(right,left,ks[9]); + F(left,right,ks[10]); + F(right,left,ks[11]); + F(left,right,ks[12]); + F(right,left,ks[13]); + F(left,right,ks[14]); + F(right,left,ks[15]); + + /* Inverse permutation, also from Hoey via Outerbridge and Schneier */ + right = (right << 31) | (right >> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left >> 1) | (left << 31); + work = ((left >> 8) ^ right) & 0xff00ff; + right ^= work; + left ^= work << 8; + work = ((left >> 2) ^ right) & 0x33333333; + right ^= work; + left ^= work << 2; + work = ((right >> 16) ^ left) & 0xffff; + left ^= work; + right ^= work << 16; + work = ((right >> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= work << 4; + + /* Put the block back into the user's buffer with final swap */ + block[0] = right >> 24; + block[1] = right >> 16; + block[2] = right >> 8; + block[3] = right; + block[4] = left >> 24; + block[5] = left >> 16; + block[6] = left >> 8; + block[7] = left; +} + +/* Key schedule-related tables from FIPS-46 */ + +/* permuted choice table (key) */ +static const unsigned char pc1[] = { + 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4 +}; + +/* number left rotations of pc1 */ +static const unsigned char totrot[] = { + 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 +}; + +/* permuted choice key (table) */ +static const unsigned char pc2[] = { + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 +}; + +/* End of DES-defined tables */ + + +/* bit 0 is left-most in byte */ +static const int bytebit[] = { + 0200,0100,040,020,010,04,02,01 +}; + + +/* Generate key schedule for encryption or decryption + * depending on the value of "decrypt" + */ +static void +deskey (DES_KS k, unsigned char *key, int decrypt) +{ + unsigned char pc1m[56]; /* place to modify pc1 into */ + unsigned char pcr[56]; /* place to rotate pc1 into */ + register int i,j,l; + int m; + unsigned char ks[8]; + + for (j=0; j<56; j++) { /* convert pc1 to bits of key */ + l=pc1[j]-1; /* integer bit location */ + m = l & 07; /* find bit */ + pc1m[j]=(key[l>>3] & /* find which key byte l is in */ + bytebit[m]) /* and which bit of that byte */ + ? 1 : 0; /* and store 1-bit result */ + } + for (i=0; i<16; i++) { /* key chunk for each iteration */ + memset(ks,0,sizeof(ks)); /* Clear key schedule */ + for (j=0; j<56; j++) /* rotate pc1 the right amount */ + pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28]; + /* rotate left and right halves independently */ + for (j=0; j<48; j++){ /* select bits individually */ + /* check bit that goes to ks[j] */ + if (pcr[pc2[j]-1]){ + /* mask it in if it's there */ + l= j % 6; + ks[j/6] |= bytebit[l] >> 2; + } + } + /* Now convert to packed odd/even interleaved form */ + k[i][0] = ((guint32)ks[0] << 24) + | ((guint32)ks[2] << 16) + | ((guint32)ks[4] << 8) + | ((guint32)ks[6]); + k[i][1] = ((guint32)ks[1] << 24) + | ((guint32)ks[3] << 16) + | ((guint32)ks[5] << 8) + | ((guint32)ks[7]); + } } diff --git a/libsoup/soup-auth-ntlm.h b/libsoup/soup-auth-ntlm.h index 1f56976..7ed9bd3 100644 --- a/libsoup/soup-auth-ntlm.h +++ b/libsoup/soup-auth-ntlm.h @@ -24,9 +24,4 @@ typedef struct { } SoupAuthNTLMClass; -SoupAuth *soup_auth_ntlm_new (const char *realm, - const char *host); -const char *soup_auth_ntlm_get_username (SoupAuth *auth); -const char *soup_auth_ntlm_get_password (SoupAuth *auth); - #endif /* SOUP_AUTH_NTLM_H */ diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c index bb6f3b4..77fb45e 100644 --- a/libsoup/soup-auth.c +++ b/libsoup/soup-auth.c @@ -107,9 +107,13 @@ soup_auth_get_property (GObject *object, guint prop_id, g_value_set_string (value, soup_auth_get_scheme_name (auth)); break; case PROP_REALM: + if (auth->realm) + g_free (auth->realm); g_value_set_string (value, soup_auth_get_realm (auth)); break; case PROP_HOST: + if (priv->host) + g_free (priv->host); g_value_set_string (value, soup_auth_get_host (auth)); break; case PROP_IS_FOR_PROXY: @@ -162,7 +166,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "Realm", "Authentication realm", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_HOST: * @@ -175,7 +179,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "Host", "Authentication host", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_IS_FOR_PROXY: * @@ -188,7 +192,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "For Proxy", "Whether or not the auth is for a proxy server", FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_IS_AUTHENTICATED: * @@ -242,19 +246,12 @@ soup_auth_new (GType type, SoupMessage *msg, const char *auth_header) } params = soup_header_parse_param_list (auth_header + strlen (scheme)); - if (!params) { - g_object_unref (auth); - return NULL; - } + if (!params) + params = g_hash_table_new (NULL, NULL); realm = g_hash_table_lookup (params, "realm"); - if (!realm) { - soup_header_free_param_list (params); - g_object_unref (auth); - return NULL; - } - - auth->realm = g_strdup (realm); + if (realm) + auth->realm = g_strdup (realm); if (!SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params)) { g_object_unref (auth); @@ -295,10 +292,10 @@ soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header) params = soup_header_parse_param_list (auth_header + strlen (scheme)); if (!params) - return FALSE; + params = g_hash_table_new (NULL, NULL); realm = g_hash_table_lookup (params, "realm"); - if (!realm || strcmp (realm, auth->realm) != 0) { + if (realm && auth->realm && strcmp (realm, auth->realm) != 0) { soup_header_free_param_list (params); return FALSE; } diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index badcd16..7fe6db8 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -1196,7 +1196,6 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) g_return_if_fail (SOUP_IS_MESSAGE (msg)); g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth)); - g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth)); priv = SOUP_MESSAGE_GET_PRIVATE (msg); @@ -1211,9 +1210,11 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) g_object_ref (priv->auth); token = soup_auth_get_authorization (auth, msg); - soup_message_headers_replace (msg->request_headers, - "Authorization", token); - g_free (token); + if (token) { + soup_message_headers_replace (msg->request_headers, + "Authorization", token); + g_free (token); + } } SoupAuth * @@ -1232,7 +1233,6 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) g_return_if_fail (SOUP_IS_MESSAGE (msg)); g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth)); - g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth)); priv = SOUP_MESSAGE_GET_PRIVATE (msg); diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 0a86630..63e90b3 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -13,7 +13,7 @@ #include "soup-session.h" #include "soup.h" -#include "soup-auth-manager-ntlm.h" +#include "soup-auth-manager.h" #include "soup-cache-private.h" #include "soup-connection.h" #include "soup-marshal.h" @@ -219,7 +219,7 @@ soup_session_init (SoupSession *session) priv->features_cache = g_hash_table_new (NULL, NULL); - auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER_NTLM, NULL); + auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER, NULL); g_signal_connect (auth_manager, "authenticate", G_CALLBACK (auth_manager_authenticate), session); soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager), @@ -573,7 +573,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS break; case PROP_USE_NTLM: g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session)); - feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM); + feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); if (feature) { if (g_value_get_boolean (value)) soup_session_feature_add_feature (feature, SOUP_TYPE_AUTH_NTLM); @@ -698,7 +698,7 @@ soup_session_get_property (GObject *object, guint prop_id, g_value_set_int (value, priv->max_conns_per_host); break; case PROP_USE_NTLM: - feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM); + feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); if (feature) g_value_set_boolean (value, soup_session_feature_has_feature (feature, SOUP_TYPE_AUTH_NTLM)); else diff --git a/tests/Makefile.am b/tests/Makefile.am index fdd2c3d..7af3975 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ INCLUDES = \ -I$(top_srcdir) \ -DSRCDIR=\""$(abs_srcdir)"\" \ + -DBUILDDIR=\""$(builddir)"\" \ $(SOUP_MAINTAINER_FLAGS) \ $(XML_CFLAGS) \ $(GLIB_CFLAGS) @@ -25,6 +26,7 @@ noinst_PROGRAMS = \ header-parsing \ misc-test \ multipart-test \ + ntlm-test-helper \ ntlm-test \ redirect-test \ requester-test \ diff --git a/tests/ntlm-test-helper.c b/tests/ntlm-test-helper.c new file mode 100644 index 0000000..60dd049 --- /dev/null +++ b/tests/ntlm-test-helper.c @@ -0,0 +1,103 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2012 Red Hat, Inc. + */ + +#include +#include +#include +#include + +#include +#include "libsoup/soup.h" + +const char *helper_protocol, *username, *domain; +gboolean use_cached_creds; + +static GOptionEntry entries[] = { + { "helper-protocol", 0, 0, + G_OPTION_ARG_STRING, &helper_protocol, + NULL, NULL }, + { "use-cached-creds", 0, 0, + G_OPTION_ARG_NONE, &use_cached_creds, + NULL, NULL }, + { "username", 0, 0, + G_OPTION_ARG_STRING, &username, + NULL, NULL }, + { "domain", 0, 0, + G_OPTION_ARG_STRING, &domain, + NULL, NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GOptionContext *opts; + char buf[256], *header; + SoupMessage *msg; + SoupAuth *auth; + + /* Don't recurse */ + g_unsetenv ("SOUP_NTLM_AUTH_DEBUG"); + + setlocale (LC_ALL, ""); + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, entries, NULL); + if (!g_option_context_parse (opts, &argc, &argv, NULL)) { + g_printerr ("Bad arguments\n"); + exit (1); + } + g_option_context_free (opts); + + if (!username || !use_cached_creds || !helper_protocol || + !g_str_equal (helper_protocol, "ntlmssp-client-1")) { + g_printerr ("Wrong arguments; this program is only intended for use by ntlm-test\n"); + exit (1); + } + + msg = soup_message_new ("GET", "http://localhost/"); + auth = NULL; + + while (fgets (buf, sizeof (buf), stdin)) { + if (strchr (buf, '\n')) + *strchr (buf, '\n') = '\0'; + if (!strcmp (buf, "YR")) { + if (g_getenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS")) { + g_print ("PW\n"); + continue; + } + + g_clear_object (&auth); + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL); + header = soup_auth_get_authorization (auth, msg); + g_print ("YR %s\n", header + 5); + g_free (header); + } else if (g_str_has_prefix (buf, "TT ")) { + header = g_strdup_printf ("NTLM %s\n", buf + 3); + if (!soup_auth_update (auth, msg, header)) { + g_printerr ("Bad challenge\n"); + exit (1); + } + g_free (header); + + soup_auth_authenticate (auth, username, "password"); + header = soup_auth_get_authorization (auth, msg); + if (!header) { + g_printerr ("Internal authentication failure\n"); + exit (1); + } + g_print ("KK %s\n", header + 5); + g_free (header); + } else { + g_printerr ("Unexpected command\n"); + exit (1); + } + } + + g_object_unref (msg); + g_clear_object (&auth); + + return 0; +} diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c index e473ba6..ce08f99 100644 --- a/tests/ntlm-test.c +++ b/tests/ntlm-test.c @@ -19,6 +19,10 @@ typedef enum { NTLM_AUTHENTICATED_BOB } NTLMServerState; +static const char *state_name[] = { + "unauth", "recv", "sent", "alice", "bob" +}; + #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA" #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA" @@ -134,16 +138,22 @@ server_callback (SoupServer *server, SoupMessage *msg, } } + debug_printf (2, " (S:%s)", state_name[state]); g_hash_table_insert (connections, socket, GINT_TO_POINTER (state)); g_object_weak_ref (G_OBJECT (socket), clear_state, connections); } +static gboolean authenticated_ntlm = FALSE; + static void authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user) { - if (!retrying) + if (!retrying) { soup_auth_authenticate (auth, user, "password"); + if (g_str_equal (soup_auth_get_scheme_name (auth), "NTLM")) + authenticated_ntlm = TRUE; + } } typedef struct { @@ -313,7 +323,8 @@ do_message (SoupSession *session, SoupURI *base_uri, const char *path, } static void -do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) +do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, + const char *user, gboolean use_builtin_ntlm) { SoupSession *session; gboolean alice = !g_strcmp0 (user, "alice"); @@ -329,6 +340,8 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) if (user) { g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), (char *)user); + if (use_ntlm && !use_builtin_ntlm) + g_setenv ("NTLMUSER", user, TRUE); } /* 1. Server doesn't request auth, so both get_ntlm_prompt and @@ -336,11 +349,19 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) * if we're using NTLM we'll try that even without the server * asking. */ + authenticated_ntlm = FALSE; do_message (session, base_uri, "/noauth", FALSE, use_ntlm, FALSE, FALSE, SOUP_STATUS_OK); + if (authenticated_ntlm != (use_ntlm && use_builtin_ntlm)) { + debug_printf (1, " ERROR: %s built-in NTLM support, but authenticate signal %s emitted\n", + use_builtin_ntlm ? "Using" : "Not using", + authenticated_ntlm ? "was" : "wasn't"); + errors++; + } + /* 2. Server requires auth as Alice, so it will request that * if we didn't already authenticate the connection to her in * the previous step. If we authenticated as Bob in the @@ -422,16 +443,16 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) } static void -do_ntlm_tests (SoupURI *base_uri) +do_ntlm_tests (SoupURI *base_uri, gboolean use_builtin_ntlm) { debug_printf (1, "Round 1: Non-NTLM Connection, no auth\n"); - do_ntlm_round (base_uri, FALSE, NULL); + do_ntlm_round (base_uri, FALSE, NULL, use_builtin_ntlm); debug_printf (1, "Round 2: NTLM Connection, user=alice\n"); - do_ntlm_round (base_uri, TRUE, "alice"); + do_ntlm_round (base_uri, TRUE, "alice", use_builtin_ntlm); debug_printf (1, "Round 3: NTLM Connection, user=bob\n"); - do_ntlm_round (base_uri, TRUE, "bob"); + do_ntlm_round (base_uri, TRUE, "bob", use_builtin_ntlm); debug_printf (1, "Round 4: Non-NTLM Connection, user=alice\n"); - do_ntlm_round (base_uri, FALSE, "alice"); + do_ntlm_round (base_uri, FALSE, "alice", use_builtin_ntlm); } int @@ -453,7 +474,30 @@ main (int argc, char **argv) uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (uri, soup_server_get_port (server)); - do_ntlm_tests (uri); + + /* Built-in NTLM auth support. (We set SOUP_NTLM_AUTH_DEBUG to + * an empty string to ensure that the built-in support is + * being used, even if /usr/bin/ntlm_auth is available.) + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE); + debug_printf (1, "Built-in NTLM support\n"); + do_ntlm_tests (uri, TRUE); + + /* Samba winbind /usr/bin/ntlm_auth helper support (via a + * helper program that emulate's its interface). + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", BUILDDIR "/ntlm-test-helper", TRUE); + debug_printf (1, "\nExternal helper support\n"); + do_ntlm_tests (uri, FALSE); + + /* Support for when ntlm_auth is installed, but the user has + * no cached credentials (and thus we have to fall back to + * libsoup's built-in NTLM support). + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS", "1", TRUE); + debug_printf (1, "\nExternal -> fallback support\n"); + do_ntlm_tests (uri, TRUE); + soup_uri_free (uri); g_main_loop_unref (loop); -- 2.7.4