diff options
-rw-r--r-- | docs/reference/Makefile.am | 2 | ||||
-rw-r--r-- | docs/reference/libsoup-2.4-sections.txt | 1 | ||||
-rw-r--r-- | libsoup/Makefile.am | 2 | ||||
-rw-r--r-- | libsoup/libsoup-2.4.sym | 1 | ||||
-rw-r--r-- | libsoup/soup-auth-manager.c | 217 | ||||
-rw-r--r-- | libsoup/soup-auth-ntlm.c | 192 | ||||
-rw-r--r-- | libsoup/soup-auth-ntlm.h | 6 | ||||
-rw-r--r-- | libsoup/soup-auth.c | 27 | ||||
-rw-r--r-- | libsoup/soup-auth.h | 8 | ||||
-rw-r--r-- | libsoup/soup-connection-auth.c | 173 | ||||
-rw-r--r-- | libsoup/soup-connection-auth.h | 52 | ||||
-rw-r--r-- | libsoup/soup-message-private.h | 5 | ||||
-rw-r--r-- | libsoup/soup-message.c | 13 | ||||
-rw-r--r-- | libsoup/soup-session.c | 1 |
14 files changed, 483 insertions, 217 deletions
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am index 74a6d925..17ae6caf 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 963cbce1..e1bbbffb 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 <SUBSECTION> soup_auth_authenticate soup_auth_is_authenticated +soup_auth_is_ready <SUBSECTION> soup_auth_get_authorization soup_auth_get_protection_space diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 7a9d6fca..59747441 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 8ec30b14..521a5f71 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 d284826e..773291b6 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 63f8c8b2..367a3cac 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 7ed9bd3e..43c40856 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 77fb45e9..5ce92d55 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 8abda8e0..824857e2 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 00000000..fc327335 --- /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 <config.h> +#endif + +#include <ctype.h> +#include <string.h> + +#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 00000000..251ca359 --- /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 <libsoup/soup-auth.h> + +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 74b6e0d7..de7cb7d0 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 7fe6db84..67b6e6f5 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 63e90b3d..20e92329 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); |