char *response_header;
} SoupNTLMConnectionState;
+typedef enum {
+ SOUP_NTLM_PASSWORD_NONE,
+ SOUP_NTLM_PASSWORD_PROVIDED,
+ SOUP_NTLM_PASSWORD_ACCEPTED,
+ SOUP_NTLM_PASSWORD_REJECTED
+} SoupNTLMPasswordState;
+
typedef struct {
char *username, *domain;
guchar nt_hash[21], lm_hash[21];
- gboolean authenticated;
+ SoupNTLMPasswordState password_state;
#ifdef USE_NTLM_AUTH
/* Use Samba's 'winbind' daemon to support NTLM single-sign-on,
SoupNTLMConnectionState *conn = state;
gboolean success = TRUE;
+ /* Note that we only return FALSE if some sort of parsing error
+ * occurs. Otherwise, the SoupAuth is still reusable (though it may
+ * no longer be _ready or _authenticated).
+ */
+
+ if (!g_str_has_prefix (auth_header, "NTLM"))
+ return FALSE;
+
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;
- /* FIXME: we should only do this if the password never worked */
- priv->authenticated = FALSE;
+ if (priv->password_state == SOUP_NTLM_PASSWORD_ACCEPTED) {
+ /* We know our password is correct, so a 401
+ * means "permission denied". Since the conn
+ * state is now FAILED, the auth is no longer
+ * is_ready() for this message, so this will
+ * cause a "retrying" authenticate signal.
+ */
+ return TRUE;
+ }
- return FALSE;
+ /* Otherwise, we just have a bad password. */
+ priv->password_state = SOUP_NTLM_PASSWORD_REJECTED;
+ return TRUE;
}
- if (!g_str_has_prefix (auth_header, "NTLM "))
- return FALSE;
+ if (conn->state == SOUP_NTLM_NEW && !auth_header[4])
+ return TRUE;
- if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, &priv->domain)) {
+ if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce,
+ priv->domain ? NULL : &priv->domain)) {
conn->state = SOUP_NTLM_FAILED;
return FALSE;
}
g_free (response);
} else {
conn->response_header = response;
- priv->authenticated = TRUE;
+ if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED)
+ priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED;
}
}
out:
soup_ntlm_nt_hash (password, priv->nt_hash);
soup_ntlm_lanmanager_hash (password, priv->lm_hash);
- priv->authenticated = TRUE;
+ priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED;
}
static gboolean
{
SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
- return priv->authenticated;
+ return (priv->password_state != SOUP_NTLM_PASSWORD_NONE &&
+ priv->password_state != SOUP_NTLM_PASSWORD_REJECTED);
}
static gboolean
SoupMessage *msg,
gpointer state)
{
+ SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth);
SoupNTLMConnectionState *conn = state;
+ if (priv->password_state == SOUP_NTLM_PASSWORD_REJECTED)
+ return FALSE;
+
+ if (priv->password_state == SOUP_NTLM_PASSWORD_PROVIDED)
+ return TRUE;
+
return conn->state != SOUP_NTLM_FAILED && conn->state != SOUP_NTLM_SSO_FAILED;
}
+static void
+got_final_auth_result (SoupMessage *msg, gpointer data)
+{
+ SoupAuth *auth = data;
+
+ g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (got_final_auth_result), auth);
+
+ if (auth != soup_message_get_auth (msg))
+ return;
+
+ if (msg->status_code != SOUP_STATUS_UNAUTHORIZED)
+ SOUP_AUTH_NTLM_GET_PRIVATE (auth)->password_state = SOUP_NTLM_PASSWORD_ACCEPTED;
+}
+
static char *
soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth,
SoupMessage *msg,
}
g_clear_pointer (&conn->nonce, g_free);
conn->state = SOUP_NTLM_SENT_RESPONSE;
+
+ if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED) {
+ /* We need to know if this worked */
+ g_signal_connect (msg, "got-headers",
+ G_CALLBACK (got_final_auth_result),
+ auth);
+ }
break;
#ifdef USE_NTLM_AUTH
case SOUP_NTLM_SSO_FAILED:
do_ntlm_round (base_uri, FALSE, "alice", use_builtin_ntlm);
}
+static void
+retry_test_authenticate (SoupSession *session, SoupMessage *msg,
+ SoupAuth *auth, gboolean retrying,
+ gpointer user_data)
+{
+ gboolean *retried = user_data;
+
+ if (!retrying) {
+ /* server_callback doesn't actually verify the password,
+ * only the username. So we pass an incorrect username
+ * rather than an incorrect password.
+ */
+ soup_auth_authenticate (auth, "wrong", "password");
+ } else if (!*retried) {
+ soup_auth_authenticate (auth, "alice", "password");
+ *retried = TRUE;
+ }
+}
+
+static void
+do_retrying_test (SoupURI *base_uri)
+{
+ SoupSession *session;
+ SoupMessage *msg;
+ SoupURI *uri;
+ gboolean retried = FALSE;
+
+ debug_printf (1, " /alice\n");
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION,
+ SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM,
+ NULL);
+ g_signal_connect (session, "authenticate",
+ G_CALLBACK (retry_test_authenticate), &retried);
+
+ uri = soup_uri_new_with_base (base_uri, "/alice");
+ msg = soup_message_new_from_uri ("GET", uri);
+ soup_uri_free (uri);
+
+ soup_session_send_message (session, msg);
+
+ if (!retried) {
+ debug_printf (1, " Didn't retry!\n");
+ errors++;
+ }
+ if (msg->status_code != SOUP_STATUS_OK) {
+ debug_printf (1, " Unexpected final status %d %s\n",
+ msg->status_code, msg->reason_phrase);
+ errors++;
+ }
+ g_object_unref (msg);
+
+ soup_test_session_abort_unref (session);
+
+ debug_printf (1, " /bob\n");
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION,
+ SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM,
+ NULL);
+ g_signal_connect (session, "authenticate",
+ G_CALLBACK (retry_test_authenticate), &retried);
+ retried = FALSE;
+
+ uri = soup_uri_new_with_base (base_uri, "/bob");
+ msg = soup_message_new_from_uri ("GET", uri);
+ soup_uri_free (uri);
+
+ soup_session_send_message (session, msg);
+
+ if (!retried) {
+ debug_printf (1, " Didn't retry!\n");
+ errors++;
+ }
+ if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
+ debug_printf (1, " Unexpected final status %d %s\n",
+ msg->status_code, msg->reason_phrase);
+ errors++;
+ }
+ g_object_unref (msg);
+
+ soup_test_session_abort_unref (session);
+}
+
int
main (int argc, char **argv)
{
debug_printf (1, "\nExternal -> fallback support\n");
do_ntlm_tests (uri, TRUE);
+ /* Other tests */
+ g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE);
+ debug_printf (1, "\nRetrying on failed password\n");
+ do_retrying_test (uri);
+
soup_uri_free (uri);
soup_test_server_quit_unref (server);