From 586aef885f64352bbd3c1b9e68ae2020f33d5833 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 12 Dec 2012 10:19:00 +0100 Subject: [PATCH] soup-auth-manager: split out connection handling Add a new SoupConnectionAuth class to help with connection tracking, and make SoupAuthNTLM a subclass of it. Allow a single SoupAuthNTLM to carry state information about multiple connections. Make SoupSession store the SoupConnection a SoupMessage is associated with on the message, and use that from SoupConnectionAuth rather than tracking sockets by hand like SoupAuthManager had previously done. Remove the connection tracking in SoupAuthManager, since it is no longer needed. --- docs/reference/Makefile.am | 2 +- docs/reference/libsoup-2.4-sections.txt | 1 + libsoup/Makefile.am | 2 + libsoup/libsoup-2.4.sym | 1 + libsoup/soup-auth-manager.c | 217 ++++++++++++-------------------- libsoup/soup-auth-ntlm.c | 192 +++++++++++++++++----------- libsoup/soup-auth-ntlm.h | 6 +- libsoup/soup-auth.c | 27 ++++ libsoup/soup-auth.h | 8 +- libsoup/soup-connection-auth.c | 173 +++++++++++++++++++++++++ libsoup/soup-connection-auth.h | 52 ++++++++ libsoup/soup-message-private.h | 5 + libsoup/soup-message.c | 13 ++ libsoup/soup-session.c | 1 + 14 files changed, 483 insertions(+), 217 deletions(-) create mode 100644 libsoup/soup-connection-auth.c create mode 100644 libsoup/soup-connection-auth.h diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am index 74a6d92..17ae6ca 100644 --- a/docs/reference/Makefile.am +++ b/docs/reference/Makefile.am @@ -32,7 +32,7 @@ CFILE_GLOB= IGNORE_HFILES= soup.h soup-marshal.h soup-enum-types.h \ soup-message-private.h soup-session-private.h \ soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h \ - soup-connection.h soup-auth-manager.h soup-auth-manager-ntlm.h \ + soup-connection.h soup-auth-manager.h soup-connection-auth.h \ soup-message-queue.h soup-path-map.h soup-gnome-features.h \ soup-proxy-resolver.h soup-proxy-resolver-gnome.h \ soup-proxy-resolver-static.h soup-directory-input-stream.h \ diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt index 963cbce..e1bbbff 100644 --- a/docs/reference/libsoup-2.4-sections.txt +++ b/docs/reference/libsoup-2.4-sections.txt @@ -536,6 +536,7 @@ soup_auth_get_info soup_auth_authenticate soup_auth_is_authenticated +soup_auth_is_ready soup_auth_get_authorization soup_auth_get_protection_space diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 7a9d6fc..5974744 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -119,6 +119,8 @@ libsoup_2_4_la_SOURCES = \ soup-client-input-stream.c \ soup-connection.h \ soup-connection.c \ + soup-connection-auth.h \ + soup-connection-auth.c \ soup-content-decoder.c \ soup-content-processor.h \ soup-content-processor.c \ diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym index 8ec30b1..521a5f7 100644 --- a/libsoup/libsoup-2.4.sym +++ b/libsoup/libsoup-2.4.sym @@ -53,6 +53,7 @@ soup_auth_get_type soup_auth_has_saved_password soup_auth_is_authenticated soup_auth_is_for_proxy +soup_auth_is_ready soup_auth_new soup_auth_ntlm_get_type soup_auth_save_password diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c index d284826..773291b 100644 --- a/libsoup/soup-auth-manager.c +++ b/libsoup/soup-auth-manager.c @@ -13,6 +13,7 @@ #include "soup-auth-manager.h" #include "soup.h" +#include "soup-connection-auth.h" #include "soup-marshal.h" #include "soup-message-private.h" #include "soup-message-queue.h" @@ -36,13 +37,10 @@ G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT, typedef struct { SoupSession *session; GPtrArray *auth_types; - gboolean has_ntlm; + gboolean auto_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)) @@ -53,6 +51,8 @@ typedef struct { } SoupAuthHost; static void soup_auth_host_free (SoupAuthHost *host); +static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv, + SoupURI *uri, SoupAuth *auth); static void soup_auth_manager_init (SoupAuthManager *manager) @@ -64,10 +64,6 @@ 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 @@ -81,9 +77,6 @@ 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); } @@ -133,7 +126,7 @@ soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) g_ptr_array_sort (priv->auth_types, auth_type_compare_func); if (type == SOUP_TYPE_AUTH_NTLM) - priv->has_ntlm = TRUE; + priv->auto_ntlm = TRUE; return TRUE; } @@ -153,7 +146,7 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType 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; + priv->auto_ntlm = FALSE; g_ptr_array_remove_index (priv->auth_types, i); return TRUE; @@ -349,10 +342,9 @@ check_auth (SoupMessage *msg, SoupAuth *auth) } static SoupAuthHost * -get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg) +get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri) { SoupAuthHost *host; - SoupURI *uri = soup_message_get_uri (msg); host = g_hash_table_lookup (priv->auth_hosts, uri); if (host) @@ -375,14 +367,30 @@ soup_auth_host_free (SoupAuthHost *host) g_slice_free (SoupAuthHost, host); } +static gboolean +make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host) +{ + SoupAuth *auth; + + if (!priv->auto_ntlm) + return FALSE; + + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, + SOUP_AUTH_HOST, host->uri->host, + NULL); + record_auth_for_uri (priv, host->uri, auth); + g_object_unref (auth); + return TRUE; +} + static SoupAuth * lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) { SoupAuthHost *host; const char *path, *realm; - host = get_auth_host_for_message (priv, msg); - if (!host->auth_realms) + host = get_auth_host_for_uri (priv, soup_message_get_uri (msg)); + if (!host->auth_realms && !make_auto_ntlm_auth (priv, host)) return NULL; path = soup_message_get_uri (msg)->path; @@ -395,7 +403,7 @@ lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) return NULL; } -static gboolean +static void authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, SoupMessage *msg, gboolean prior_auth_failed, gboolean proxy, gboolean can_interact) @@ -416,7 +424,7 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, uri = NULL; if (!uri) - return FALSE; + return; } else uri = soup_message_get_uri (msg); @@ -431,110 +439,19 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, prior_auth_failed); } - - return soup_auth_is_authenticated (auth); -} - -static void -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) +record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri, + SoupAuth *auth) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuthHost *host; - SoupAuth *auth, *prior_auth, *old_auth; + SoupAuth *old_auth; const char *path; char *auth_info, *old_auth_info; 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 */ - prior_auth = soup_message_get_auth (msg); - if (prior_auth && check_auth (msg, prior_auth)) { - auth = prior_auth; - if (!soup_auth_is_authenticated (auth)) - prior_auth_failed = TRUE; - } else { - auth = create_auth (priv, msg); - if (!auth) - return; - } + host = get_auth_host_for_uri (priv, uri); auth_info = soup_auth_get_info (auth); if (!host->auth_realms) { @@ -544,7 +461,7 @@ auth_got_headers (SoupMessage *msg, gpointer manager) } /* Record where this auth realm is used. */ - pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg)); + pspace = soup_auth_get_protection_space (auth, uri); for (p = pspace; p; p = p->next) { path = p->data; old_auth_info = soup_path_map_lookup (host->auth_realms, path); @@ -566,16 +483,39 @@ auth_got_headers (SoupMessage *msg, gpointer manager) old_auth = g_hash_table_lookup (host->auths, auth_info); if (old_auth) { g_free (auth_info); - if (auth != old_auth && auth != prior_auth) { - g_object_unref (auth); - auth = old_auth; - } + return old_auth; + } else { + g_hash_table_insert (host->auths, auth_info, + g_object_ref (auth)); + return auth; + } +} + +static void +auth_got_headers (SoupMessage *msg, gpointer manager) +{ + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuth *auth, *prior_auth, *new_auth; + gboolean prior_auth_failed = FALSE; + + /* See if we used auth last time */ + prior_auth = soup_message_get_auth (msg); + if (prior_auth && check_auth (msg, prior_auth)) { + auth = g_object_ref (prior_auth); + if (!soup_auth_is_ready (auth, msg)) + prior_auth_failed = TRUE; } else { - g_hash_table_insert (host->auths, auth_info, auth); + auth = create_auth (priv, msg); + if (!auth) + return; } + new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg), + auth); + g_object_unref (auth); + /* If we need to authenticate, try to do it. */ - authenticate_auth (manager, auth, msg, + authenticate_auth (manager, new_auth, msg, prior_auth_failed, FALSE, TRUE); } @@ -585,16 +525,17 @@ auth_got_body (SoupMessage *msg, gpointer manager) SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuth *auth; - auth = get_connauth_for_msg (priv, msg); - if (auth && soup_auth_is_authenticated (auth)) { - SoupMessageFlags flags; + auth = lookup_auth (priv, msg); + if (auth && soup_auth_is_ready (auth, msg)) { + if (SOUP_IS_CONNECTION_AUTH (auth)) { + SoupMessageFlags flags; + + flags = soup_message_get_flags (msg); + soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); + } - 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 @@ -607,7 +548,7 @@ proxy_auth_got_headers (SoupMessage *msg, gpointer manager) /* See if we used auth last time */ prior_auth = soup_message_get_proxy_auth (msg); if (prior_auth && check_auth (msg, prior_auth)) { - if (!soup_auth_is_authenticated (prior_auth)) + if (!soup_auth_is_ready (prior_auth, msg)) prior_auth_failed = TRUE; } @@ -628,7 +569,7 @@ proxy_auth_got_body (SoupMessage *msg, gpointer manager) SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuth *auth = priv->proxy_auth; - if (auth && soup_auth_is_authenticated (auth)) + if (auth && soup_auth_is_ready (auth, msg)) soup_session_requeue_message (priv->session, msg); } @@ -662,18 +603,20 @@ 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) { - if (!authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) + authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE); + if (!soup_auth_is_ready (auth, msg)) auth = NULL; - } else - auth = get_connauth (priv, socket); + } soup_message_set_auth (msg, auth); auth = priv->proxy_auth; - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE)) - auth = NULL; + if (auth) { + authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE); + if (!soup_auth_is_ready (auth, msg)) + auth = NULL; + } soup_message_set_proxy_auth (msg, auth); } diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c index 63f8c8b..367a3ca 100644 --- a/libsoup/soup-auth-ntlm.c +++ b/libsoup/soup-auth-ntlm.c @@ -44,10 +44,14 @@ typedef enum { typedef struct { SoupNTLMState state; - char *username; - guchar nt_hash[21], lm_hash[21]; - char *nonce, *domain; + char *nonce; char *response_header; +} SoupNTLMConnectionState; + +typedef struct { + char *username, *domain; + guchar nt_hash[21], lm_hash[21]; + gboolean authenticated; #ifdef USE_NTLM_AUTH /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, @@ -80,19 +84,15 @@ static void sso_ntlm_close (SoupAuthNTLMPrivate *priv); * Since: 2.34 */ -G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_AUTH) +G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_CONNECTION_AUTH) static void soup_auth_ntlm_init (SoupAuthNTLM *ntlm) { - SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm); #ifdef USE_NTLM_AUTH + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm); const char *username = NULL, *slash; -#endif - - priv->state = SOUP_NTLM_NEW; -#ifdef USE_NTLM_AUTH priv->sso_available = TRUE; priv->fd_in = -1; priv->fd_out = -1; @@ -123,9 +123,6 @@ soup_auth_ntlm_finalize (GObject *object) 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 @@ -255,75 +252,89 @@ wrfinish: } #endif /* USE_NTLM_AUTH */ +static gpointer +soup_auth_ntlm_create_connection_state (SoupConnectionAuth *auth) +{ + SoupNTLMConnectionState *conn; + + conn = g_slice_new0 (SoupNTLMConnectionState); + conn->state = SOUP_NTLM_NEW; + + return conn; +} + +static void +soup_auth_ntlm_free_connection_state (SoupConnectionAuth *auth, + gpointer state) +{ + SoupNTLMConnectionState *conn = state; + + g_free (conn->nonce); + g_free (conn->response_header); + g_slice_free (SoupNTLMConnectionState, conn); +} + static gboolean -soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg, - GHashTable *auth_params) +soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg, + const char *auth_header, gpointer state) { SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); - GHashTableIter iter; - gpointer key, value; - char *header; + SoupNTLMConnectionState *conn = state; gboolean success = TRUE; - if (priv->state > SOUP_NTLM_SENT_REQUEST) { + 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. */ - priv->state = SOUP_NTLM_FAILED; + conn->state = SOUP_NTLM_FAILED; + + /* FIXME: we should only do this if the password never worked */ + priv->authenticated = FALSE; + 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)) + if (!g_str_has_prefix (auth_header, "NTLM ")) 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; + + if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, &priv->domain)) { + conn->state = SOUP_NTLM_FAILED; return FALSE; } #ifdef USE_NTLM_AUTH - if (priv->sso_available && priv->state == SOUP_NTLM_SENT_REQUEST) { + if (priv->sso_available && conn->state == SOUP_NTLM_SENT_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; + conn->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); + input = g_strdup_printf ("TT %s\n", auth_header + 5); + response = sso_ntlm_response (priv, input, conn->state); sso_ntlm_close (priv); g_free (input); if (!response) { - priv->state = SOUP_NTLM_SSO_FAILED; + conn->state = SOUP_NTLM_SSO_FAILED; success = FALSE; } else if (!g_ascii_strcasecmp (response, "PW")) { priv->sso_available = FALSE; g_free (response); - } else - priv->response_header = response; + } else { + conn->response_header = response; + priv->authenticated = TRUE; + } } out: #endif - g_free (header); - - if (priv->state == SOUP_NTLM_SENT_REQUEST) - priv->state = SOUP_NTLM_RECEIVED_CHALLENGE; + if (conn->state == SOUP_NTLM_SENT_REQUEST) + conn->state = SOUP_NTLM_RECEIVED_CHALLENGE; g_object_set (G_OBJECT (auth), SOUP_AUTH_REALM, priv->domain, @@ -335,7 +346,16 @@ soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg, static GSList * soup_auth_ntlm_get_protection_space (SoupAuth *auth, SoupURI *source_uri) { - g_return_val_if_reached (NULL); + char *space, *p; + + space = g_strdup (source_uri->path); + + /* Strip query and filename component */ + p = strrchr (space, '/'); + if (p && p != space && p[1]) + *p = '\0'; + + return g_slist_prepend (NULL, space); } static void @@ -343,59 +363,66 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username, const char *password) { SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); - const char *slash, *domain; + const char *slash; g_return_if_fail (username != NULL); g_return_if_fail (password != NULL); - g_return_if_fail (priv->nonce != NULL); + + if (priv->username) + g_free (priv->username); + if (priv->domain) + g_free (priv->domain); slash = strpbrk (username, "\\/"); if (slash) { - if (priv->domain) - g_free (priv->domain); - domain = priv->domain = g_strndup (username, slash - username); + priv->domain = g_strndup (username, slash - username); priv->username = g_strdup (slash + 1); } else { - domain = ""; + priv->domain = g_strdup (""); 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); - - g_free (priv->nonce); - priv->nonce = NULL; + priv->authenticated = TRUE; } static gboolean soup_auth_ntlm_is_authenticated (SoupAuth *auth) { - return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->username != NULL && - SOUP_AUTH_NTLM_GET_PRIVATE (auth)->response_header != NULL; + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + + return priv->authenticated; +} + +static gboolean +soup_auth_ntlm_is_connection_ready (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) +{ + SoupNTLMConnectionState *conn = state; + + return conn->state != SOUP_NTLM_FAILED && conn->state != SOUP_NTLM_SSO_FAILED; } static char * -soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg) +soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) { - SoupAuthNTLMPrivate *priv = - SOUP_AUTH_NTLM_GET_PRIVATE (auth); + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + SoupNTLMConnectionState *conn = state; char *header = NULL; - switch (priv->state) { + switch (conn->state) { case SOUP_NTLM_NEW: #ifdef USE_NTLM_AUTH if (sso_ntlm_initiate (priv)) { - header = sso_ntlm_response (priv, "YR\n", priv->state); + header = sso_ntlm_response (priv, "YR\n", conn->state); if (header) { if (g_ascii_strcasecmp (header, "PW") != 0) { - priv->state = SOUP_NTLM_SENT_REQUEST; + conn->state = SOUP_NTLM_SENT_REQUEST; break; } else { g_free (header); @@ -411,12 +438,22 @@ soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg) */ #endif header = soup_ntlm_request (); - priv->state = SOUP_NTLM_SENT_REQUEST; + conn->state = SOUP_NTLM_SENT_REQUEST; break; case SOUP_NTLM_RECEIVED_CHALLENGE: - header = priv->response_header; - priv->response_header = NULL; - priv->state = SOUP_NTLM_SENT_RESPONSE; + if (conn->response_header) { + header = conn->response_header; + conn->response_header = NULL; + } else { + header = soup_ntlm_response (conn->nonce, + priv->username, + priv->nt_hash, + priv->lm_hash, + NULL, + priv->domain); + } + g_clear_pointer (&conn->nonce, g_free); + conn->state = SOUP_NTLM_SENT_RESPONSE; break; #ifdef USE_NTLM_AUTH case SOUP_NTLM_SSO_FAILED: @@ -424,7 +461,7 @@ soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg) g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); priv->sso_available = FALSE; header = soup_ntlm_request (); - priv->state = SOUP_NTLM_SENT_REQUEST; + conn->state = SOUP_NTLM_SENT_REQUEST; break; #endif default: @@ -438,6 +475,7 @@ static void soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) { SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_ntlm_class); + SoupConnectionAuthClass *connauth_class = SOUP_CONNECTION_AUTH_CLASS (auth_ntlm_class); GObjectClass *object_class = G_OBJECT_CLASS (auth_ntlm_class); g_type_class_add_private (auth_ntlm_class, sizeof (SoupAuthNTLMPrivate)); @@ -445,11 +483,15 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) auth_class->scheme_name = "NTLM"; auth_class->strength = 3; - auth_class->update = soup_auth_ntlm_update; auth_class->get_protection_space = soup_auth_ntlm_get_protection_space; auth_class->authenticate = soup_auth_ntlm_authenticate; auth_class->is_authenticated = soup_auth_ntlm_is_authenticated; - auth_class->get_authorization = soup_auth_ntlm_get_authorization; + + connauth_class->create_connection_state = soup_auth_ntlm_create_connection_state; + connauth_class->free_connection_state = soup_auth_ntlm_free_connection_state; + connauth_class->update_connection = soup_auth_ntlm_update_connection; + connauth_class->get_connection_authorization = soup_auth_ntlm_get_connection_authorization; + connauth_class->is_connection_ready = soup_auth_ntlm_is_connection_ready; object_class->finalize = soup_auth_ntlm_finalize; diff --git a/libsoup/soup-auth-ntlm.h b/libsoup/soup-auth-ntlm.h index 7ed9bd3..43c4085 100644 --- a/libsoup/soup-auth-ntlm.h +++ b/libsoup/soup-auth-ntlm.h @@ -6,7 +6,7 @@ #ifndef SOUP_AUTH_NTLM_H #define SOUP_AUTH_NTLM_H 1 -#include "soup-auth.h" +#include "soup-connection-auth.h" #define SOUP_AUTH_NTLM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLM)) #define SOUP_AUTH_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass)) @@ -15,12 +15,12 @@ #define SOUP_AUTH_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass)) typedef struct { - SoupAuth parent; + SoupConnectionAuth parent; } SoupAuthNTLM; typedef struct { - SoupAuthClass parent_class; + SoupConnectionAuthClass parent_class; } SoupAuthNTLMClass; diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c index 77fb45e..5ce92d5 100644 --- a/libsoup/soup-auth.c +++ b/libsoup/soup-auth.c @@ -459,6 +459,33 @@ soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg) } /** + * soup_auth_is_ready: + * @auth: a #SoupAuth + * @msg: a #SoupMessage + * + * Tests if @auth is ready to make a request for @msg with. For most + * auths, this is equivalent to soup_auth_is_authenticated(), but for + * some auth types (eg, NTLM), the auth may be sendable (eg, as an + * authentication request) even before it is authenticated. + * + * Return value: %TRUE if @auth is ready to make a request with. + * + * Since: 2.42 + **/ +gboolean +soup_auth_is_ready (SoupAuth *auth, + SoupMessage *msg) +{ + g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), TRUE); + + if (SOUP_AUTH_GET_CLASS (auth)->is_ready) + return SOUP_AUTH_GET_CLASS (auth)->is_ready (auth, msg); + else + return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth); +} + +/** * soup_auth_get_protection_space: * @auth: a #SoupAuth * @source_uri: the URI of the request that @auth was generated in diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h index 8abda8e..824857e 100644 --- a/libsoup/soup-auth.h +++ b/libsoup/soup-auth.h @@ -44,8 +44,11 @@ typedef struct { char * (*get_authorization) (SoupAuth *auth, SoupMessage *msg); + + gboolean (*is_ready) (SoupAuth *auth, + SoupMessage *msg); + /* Padding for future expansion */ - void (*_libsoup_reserved1) (void); void (*_libsoup_reserved2) (void); void (*_libsoup_reserved3) (void); void (*_libsoup_reserved4) (void); @@ -76,6 +79,9 @@ void soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password); gboolean soup_auth_is_authenticated (SoupAuth *auth); +SOUP_AVAILABLE_IN_2_42 +gboolean soup_auth_is_ready (SoupAuth *auth, + SoupMessage *msg); char *soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg); diff --git a/libsoup/soup-connection-auth.c b/libsoup/soup-connection-auth.c new file mode 100644 index 0000000..fc32733 --- /dev/null +++ b/libsoup/soup-connection-auth.c @@ -0,0 +1,173 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-connection-auth.c: Abstract base class for hacky Microsoft + * connection-based auth mechanisms (NTLM and Negotiate) + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "soup-connection-auth.h" +#include "soup.h" +#include "soup-connection.h" +#include "soup-message-private.h" + +G_DEFINE_ABSTRACT_TYPE (SoupConnectionAuth, soup_connection_auth, SOUP_TYPE_AUTH) + +struct SoupConnectionAuthPrivate { + GHashTable *conns; +}; + +static void +soup_connection_auth_init (SoupConnectionAuth *auth) +{ + auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthPrivate); + + auth->priv->conns = g_hash_table_new (NULL, NULL); +} + +static void connection_disconnected (SoupConnection *conn, gpointer user_data); + +static void +soup_connection_auth_free_connection_state (SoupConnectionAuth *auth, + SoupConnection *conn, + gpointer state) +{ + g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_disconnected), auth); + SOUP_CONNECTION_AUTH_GET_CLASS (auth)->free_connection_state (auth, state); +} + +static void +connection_disconnected (SoupConnection *conn, gpointer user_data) +{ + SoupConnectionAuth *auth = user_data; + gpointer state; + + state = g_hash_table_lookup (auth->priv->conns, conn); + g_hash_table_remove (auth->priv->conns, conn); + soup_connection_auth_free_connection_state (auth, conn, state); +} + +static void +soup_connection_auth_finalize (GObject *object) +{ + SoupConnectionAuth *auth = SOUP_CONNECTION_AUTH (object); + GHashTableIter iter; + gpointer conn, state; + + g_hash_table_iter_init (&iter, auth->priv->conns); + while (g_hash_table_iter_next (&iter, &conn, &state)) { + soup_connection_auth_free_connection_state (auth, conn, state); + g_hash_table_iter_remove (&iter); + } + g_hash_table_destroy (auth->priv->conns); + + G_OBJECT_CLASS (soup_connection_auth_parent_class)->finalize (object); +} + +static gpointer +get_connection_state_for_message (SoupConnectionAuth *auth, SoupMessage *msg) +{ + SoupConnection *conn; + gpointer state; + + conn = soup_message_get_connection (msg); + state = g_hash_table_lookup (auth->priv->conns, conn); + if (state) + return state; + + state = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->create_connection_state (auth); + if (conn) { + g_signal_connect (conn, "disconnected", + G_CALLBACK (connection_disconnected), auth); + } + + g_hash_table_insert (auth->priv->conns, conn, state); + return state; +} + +static gboolean +soup_connection_auth_update (SoupAuth *auth, + SoupMessage *msg, + GHashTable *auth_params) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + GHashTableIter iter; + GString *auth_header; + gpointer key, value; + gboolean result; + + /* Recreate @auth_header out of @auth_params. If the + * base64 data ended with 1 or more "="s, then it + * will have been parsed as key=value. Otherwise + * it will all have been parsed as key, and value + * will be %NULL. + */ + auth_header = g_string_new (soup_auth_get_scheme_name (auth)); + g_hash_table_iter_init (&iter, auth_params); + if (g_hash_table_iter_next (&iter, &key, &value)) { + if (value) { + g_string_append_printf (auth_header, " %s=%s", + (char *)key, + (char *)value); + } else { + g_string_append_printf (auth_header, " %s", + (char *)key); + } + + if (g_hash_table_iter_next (&iter, &key, &value)) { + g_string_free (auth_header, TRUE); + return FALSE; + } + } + + result = SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + update_connection (cauth, msg, auth_header->str, conn); + + g_string_free (auth_header, TRUE); + return result; +} + +static char * +soup_connection_auth_get_authorization (SoupAuth *auth, + SoupMessage *msg) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + + return SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + get_connection_authorization (cauth, msg, conn); +} + +static gboolean +soup_connection_auth_is_ready (SoupAuth *auth, + SoupMessage *msg) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + + return SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + is_connection_ready (SOUP_CONNECTION_AUTH (auth), msg, conn); +} + +static void +soup_connection_auth_class_init (SoupConnectionAuthClass *connauth_class) +{ + SoupAuthClass *auth_class = SOUP_AUTH_CLASS (connauth_class); + GObjectClass *object_class = G_OBJECT_CLASS (connauth_class); + + g_type_class_add_private (connauth_class, sizeof (SoupConnectionAuthPrivate)); + + auth_class->update = soup_connection_auth_update; + auth_class->get_authorization = soup_connection_auth_get_authorization; + auth_class->is_ready = soup_connection_auth_is_ready; + + object_class->finalize = soup_connection_auth_finalize; +} diff --git a/libsoup/soup-connection-auth.h b/libsoup/soup-connection-auth.h new file mode 100644 index 0000000..251ca35 --- /dev/null +++ b/libsoup/soup-connection-auth.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef SOUP_CONNECTION_AUTH_H +#define SOUP_CONNECTION_AUTH_H 1 + +#include + +G_BEGIN_DECLS + +#define SOUP_TYPE_CONNECTION_AUTH (soup_connection_auth_get_type ()) +#define SOUP_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuth)) +#define SOUP_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass)) +#define SOUP_IS_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CONNECTION_AUTH)) +#define SOUP_IS_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CONNECTION_AUTH)) +#define SOUP_CONNECTION_AUTH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass)) + +typedef struct SoupConnectionAuthPrivate SoupConnectionAuthPrivate; + +typedef struct { + SoupAuth parent; + + SoupConnectionAuthPrivate *priv; +} SoupConnectionAuth; + +typedef struct { + SoupAuthClass parent_class; + + gpointer (*create_connection_state) (SoupConnectionAuth *auth); + void (*free_connection_state) (SoupConnectionAuth *auth, + gpointer conn); + + gboolean (*update_connection) (SoupConnectionAuth *auth, + SoupMessage *msg, + const char *auth_header, + gpointer conn); + char *(*get_connection_authorization) (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer conn); + gboolean (*is_connection_ready) (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer conn); +} SoupConnectionAuthClass; + +SOUP_AVAILABLE_IN_2_42 +GType soup_connection_auth_get_type (void); + +G_END_DECLS + +#endif /* SOUP_CONNECTION_AUTH_H */ diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h index 74b6e0d..de7cb7d 100644 --- a/libsoup/soup-message-private.h +++ b/libsoup/soup-message-private.h @@ -31,6 +31,7 @@ typedef struct { SoupAddress *addr; SoupAuth *auth, *proxy_auth; + SoupConnection *connection; GSList *disabled_features; @@ -138,4 +139,8 @@ GInputStream *soup_message_setup_body_istream (GInputStream *body_stream, void soup_message_set_soup_request (SoupMessage *msg, SoupRequest *req); +SoupConnection *soup_message_get_connection (SoupMessage *msg); +void soup_message_set_connection (SoupMessage *msg, + SoupConnection *conn); + #endif /* SOUP_MESSAGE_PRIVATE_H */ diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index 7fe6db8..67b6e6f 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -1260,6 +1260,19 @@ soup_message_get_proxy_auth (SoupMessage *msg) return SOUP_MESSAGE_GET_PRIVATE (msg)->proxy_auth; } +SoupConnection * +soup_message_get_connection (SoupMessage *msg) +{ + return SOUP_MESSAGE_GET_PRIVATE (msg)->connection; +} + +void +soup_message_set_connection (SoupMessage *msg, + SoupConnection *conn) +{ + SOUP_MESSAGE_GET_PRIVATE (msg)->connection = conn; +} + /** * soup_message_cleanup_response: * @msg: a #SoupMessage diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 63e90b3..20e9232 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -1162,6 +1162,7 @@ soup_session_set_item_connection (SoupSession *session, } item->conn = conn; + soup_message_set_connection (item->msg, conn); if (item->conn) { g_object_ref (item->conn); -- 2.7.4