Only remove the Authorization / Proxy-Authorization header from the
authorDan Winship <danw@src.gnome.org>
Mon, 24 Sep 2007 00:53:00 +0000 (00:53 +0000)
committerDan Winship <danw@src.gnome.org>
Mon, 24 Sep 2007 00:53:00 +0000 (00:53 +0000)
* libsoup/soup-message.c (soup_message_set_auth)
(soup_message_set_proxy_auth): Only remove the Authorization /
Proxy-Authorization header from the message if it was previously
set by soup_message_set_auth(). (Eg, not if it was added by
SoupConnectionNTLM.) #471389

* libsoup/soup-connection-ntlm.h: fix a search-and-replace-o

* tests/ntlm-test.c: Simple NTLM regression test; doesn't really
test the crypto/encoding bits, just that the right headers are
being sent at the right times.

svn path=/trunk/; revision=930

ChangeLog
libsoup/soup-connection-ntlm.h
libsoup/soup-message.c
tests/Makefile.am
tests/ntlm-test.c [new file with mode: 0644]

index 008ef8a..c493abd 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2007-09-23  Dan Winship  <danw@gnome.org>
+
+       * libsoup/soup-message.c (soup_message_set_auth)
+       (soup_message_set_proxy_auth): Only remove the Authorization /
+       Proxy-Authorization header from the message if it was previously
+       set by soup_message_set_auth(). (Eg, not if it was added by
+       SoupConnectionNTLM.) #471389
+
+       * libsoup/soup-connection-ntlm.h: fix a search-and-replace-o
+
+       * tests/ntlm-test.c: Simple NTLM regression test; doesn't really
+       test the crypto/encoding bits, just that the right headers are
+       being sent at the right times.
+
 2007-09-14  Dan Winship  <danw@gnome.org>
 
        Make "make check" pass on Fedora 7:
index b9a2b5c..19dcd8d 100644 (file)
@@ -1,4 +1,4 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-ntlm-offset: 8 -*- */
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2000-2003, Ximian, Inc.
  */
index 1bb55a6..9de865b 100644 (file)
@@ -750,9 +750,10 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth)
 
        priv = SOUP_MESSAGE_GET_PRIVATE (msg);
 
-       if (priv->auth)
+       if (priv->auth) {
                g_object_unref (priv->auth);
-       soup_message_remove_header (msg->request_headers, "Authorization");
+               soup_message_remove_header (msg->request_headers, "Authorization");
+       }
        priv->auth = auth;
        if (!priv->auth)
                return;
@@ -801,10 +802,11 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth)
 
        priv = SOUP_MESSAGE_GET_PRIVATE (msg);
 
-       if (priv->proxy_auth)
+       if (priv->proxy_auth) {
                g_object_unref (priv->proxy_auth);
-       soup_message_remove_header (msg->request_headers,
-                                   "Proxy-Authorization");
+               soup_message_remove_header (msg->request_headers,
+                                           "Proxy-Authorization");
+       }
        priv->proxy_auth = auth;
        if (!priv->proxy_auth)
                return;
index 28fa5bd..c4ea17d 100644 (file)
@@ -13,6 +13,7 @@ noinst_PROGRAMS =     \
        get             \
        getbug          \
        header-parsing  \
+       ntlm-test       \
        revserver       \
        simple-httpd    \
        simple-proxy    \
@@ -28,6 +29,7 @@ dns_SOURCES = dns.c
 get_SOURCES = get.c
 getbug_SOURCES = getbug.c
 header_parsing_SOURCES = header-parsing.c
+ntlm_test_SOURCES = ntlm-test.c
 proxy_test_SOURCES = proxy-test.c apache-wrapper.c apache-wrapper.h
 revserver_SOURCES = revserver.c
 simple_httpd_SOURCES = simple-httpd.c
@@ -46,7 +48,7 @@ if HAVE_XMLRPC_EPI_PHP
 XMLRPC_TESTS = xmlrpc-test
 endif
 
-TESTS = date header-parsing uri-parsing $(APACHE_TESTS) $(SSL_TESTS) $(XMLRPC_TESTS)
+TESTS = date header-parsing uri-parsing ntlm-test $(APACHE_TESTS) $(SSL_TESTS) $(XMLRPC_TESTS)
 
 EXTRA_DIST =           \
        libsoup.supp    \
diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c
new file mode 100644 (file)
index 0000000..09e7a1f
--- /dev/null
@@ -0,0 +1,380 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+/* This doesn't implement full server-side NTLM, and it mostly doesn't
+ * even test that the client is doing the crypto/encoding/etc parts of
+ * NTLM correctly. It only tests that the right message headers get
+ * set in the right messages.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+#include <libsoup/soup-server-message.h>
+#include <libsoup/soup-session-async.h>
+
+typedef enum {
+       NTLM_UNAUTHENTICATED,
+       NTLM_RECEIVED_REQUEST,
+       NTLM_SENT_CHALLENGE,
+       NTLM_AUTHENTICATED_ALICE,
+       NTLM_AUTHENTICATED_BOB,
+} NTLMServerState;
+
+#define NTLM_REQUEST_START "TlRMTVNTUAABAAAA"
+#define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
+
+#define NTLM_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
+
+#define NTLM_RESPONSE_USER(response) ((response)[87] == 'h' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB)
+
+static void
+server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
+{
+       GHashTable *connections = data;
+       const char *auth;
+       char *path;
+       NTLMServerState state, required_user;
+       gboolean not_found = FALSE;
+
+       if (soup_method_get_id (msg->method) != SOUP_METHOD_ID_GET) {
+               soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+               return;
+       }
+
+       path = soup_uri_to_string (soup_message_get_uri (msg), TRUE);
+       if (!strcmp (path, "/noauth"))
+               required_user = 0;
+       else if (!strncmp (path, "/alice", 6))
+               required_user = NTLM_AUTHENTICATED_ALICE;
+       else if (!strncmp (path, "/bob", 4))
+               required_user = NTLM_AUTHENTICATED_BOB;
+       if (strstr (path, "/404"))
+               not_found = TRUE;
+       g_free (path);
+
+       state = GPOINTER_TO_INT (g_hash_table_lookup (connections, context->sock));
+       auth = soup_message_get_header (msg->request_headers, "Authorization");
+
+       if (auth && !strncmp (auth, "NTLM ", 5)) {
+               if (!strncmp (auth + 5, NTLM_REQUEST_START,
+                             strlen (NTLM_REQUEST_START)))
+                       state = NTLM_RECEIVED_REQUEST;
+               else if (state == NTLM_SENT_CHALLENGE &&
+                        !strncmp (auth + 5, NTLM_RESPONSE_START,
+                                  strlen (NTLM_RESPONSE_START)))
+                       state = NTLM_RESPONSE_USER (auth + 5);
+               else
+                       state = NTLM_UNAUTHENTICATED;
+       }
+
+       if (state == NTLM_RECEIVED_REQUEST) {
+               soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
+               soup_message_add_header (msg->response_headers,
+                                        "WWW-Authenticate", "NTLM " NTLM_CHALLENGE);
+               state = NTLM_SENT_CHALLENGE;
+       } else if (!required_user || required_user == state) {
+               if (not_found)
+                       soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+               else {
+                       soup_message_set_response (msg, "text/plain",
+                                                  SOUP_BUFFER_STATIC,
+                                                  "OK\r\n", 4);
+                       soup_message_set_status (msg, SOUP_STATUS_OK);
+               }
+       } else {
+               soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
+               soup_message_add_header (msg->response_headers,
+                                        "WWW-Authenticate", "NTLM");
+               soup_message_add_header (msg->response_headers,
+                                        "Connection", "close");
+       }
+
+       g_hash_table_insert (connections, context->sock,
+                            GINT_TO_POINTER (state));
+}
+
+static void
+authenticate (SoupSession *session, SoupMessage *msg,
+             const char *auth_type, const char *auth_realm,
+             char **username, char **password, gpointer data)
+{
+       const char *user = data;
+
+       *username = g_strdup (user);
+       *password = g_strdup ("password");
+}
+
+typedef struct {
+       gboolean got_prompt;
+       gboolean sent_request;
+       gboolean got_challenge;
+       gboolean sent_response;
+} NTLMState;
+
+static void
+ntlm_prompt_check (SoupMessage *msg, gpointer user_data)
+{
+       NTLMState *state = user_data;
+       const char *header;
+
+       if (state->sent_request)
+               return;
+       header = soup_message_get_header (msg->response_headers,
+                                         "WWW-Authenticate");
+       if (header && !strcmp (header, "NTLM"))
+               state->got_prompt = TRUE;
+}
+
+static void
+ntlm_challenge_check (SoupMessage *msg, gpointer user_data)
+{
+       NTLMState *state = user_data;
+       const char *header;
+
+       header = soup_message_get_header (msg->response_headers,
+                                         "WWW-Authenticate");
+       if (header && !strncmp (header, "NTLM ", 5))
+               state->got_challenge = TRUE;
+}
+
+static void
+ntlm_request_check (SoupMessage *msg, gpointer user_data)
+{
+       NTLMState *state = user_data;
+       const char *header;
+
+       header = soup_message_get_header (msg->request_headers,
+                                         "Authorization");
+       if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START,
+                               strlen ("NTLM " NTLM_REQUEST_START)))
+               state->sent_request = TRUE;
+}
+
+static void
+ntlm_response_check (SoupMessage *msg, gpointer user_data)
+{
+       NTLMState *state = user_data;
+       const char *header;
+
+       header = soup_message_get_header (msg->request_headers,
+                                         "Authorization");
+       if (header && !strncmp (header, "NTLM " NTLM_RESPONSE_START,
+                               strlen ("NTLM " NTLM_RESPONSE_START)))
+               state->sent_response = TRUE;
+}
+
+static int
+do_message (SoupSession *session, SoupUri *base_uri, const char *path,
+           gboolean get_prompt, gboolean do_ntlm, guint status_code)
+{
+       SoupUri *uri;
+       SoupMessage *msg;
+       NTLMState state = { FALSE, FALSE, FALSE, FALSE };
+       int errors = 0;
+
+       uri = soup_uri_copy (base_uri);
+       g_free (uri->path);
+       uri->path = g_strdup (path);
+       msg = soup_message_new_from_uri ("GET", uri);
+       soup_uri_free (uri);
+
+       soup_message_add_header_handler (msg, "WWW-Authenticate",
+                                        SOUP_HANDLER_PRE_BODY,
+                                        ntlm_prompt_check, &state);
+       soup_message_add_header_handler (msg, "WWW-Authenticate",
+                                        SOUP_HANDLER_PRE_BODY,
+                                        ntlm_challenge_check, &state);
+       g_signal_connect (msg, "wrote-headers",
+                         G_CALLBACK (ntlm_request_check), &state);
+       g_signal_connect (msg, "wrote-headers",
+                         G_CALLBACK (ntlm_response_check), &state);
+
+       soup_session_send_message (session, msg);
+       printf ("  %-10s -> ", path);
+
+       if (state.got_prompt) {
+               printf (" PROMPT");
+               if (!get_prompt) {
+                       printf ("???");
+                       errors++;
+               }
+       } else if (get_prompt) {
+               printf (" no-prompt???");
+               errors++;
+       }
+
+       if (state.sent_request) {
+               printf (" REQUEST");
+               if (!do_ntlm) {
+                       printf ("???");
+                       errors++;
+               }
+       } else if (do_ntlm) {
+               printf (" no-request???");
+               errors++;
+       }
+
+       if (state.got_challenge) {
+               printf (" CHALLENGE");
+               if (!do_ntlm) {
+                       printf ("???");
+                       errors++;
+               }
+       } else if (do_ntlm) {
+               printf (" no-challenge???");
+               errors++;
+       }
+
+       if (state.sent_response) {
+               printf (" RESPONSE");
+               if (!do_ntlm) {
+                       printf ("???");
+                       errors++;
+               }
+       } else if (do_ntlm) {
+               printf (" no-response???");
+               errors++;
+       }
+
+       printf (" -> %s", msg->reason_phrase);
+       if (msg->status_code != status_code) {
+               printf ("???");
+               errors++;
+       }
+       printf ("\n");
+
+       return errors;
+}
+
+static int
+do_ntlm_round (SoupUri *base_uri, const char *user)
+{
+       SoupSession *session;
+       int errors = 0;
+       gboolean use_ntlm = user != NULL;
+       gboolean alice = use_ntlm && !strcmp (user, "alice");
+       gboolean bob = use_ntlm && !strcmp (user, "bob");
+
+       g_return_val_if_fail (use_ntlm || !alice, 0);
+
+       session = soup_session_async_new_with_options (
+               SOUP_SESSION_USE_NTLM, use_ntlm,
+               NULL);
+       g_signal_connect (session, "authenticate",
+                         G_CALLBACK (authenticate), (char *)user);
+
+       errors += do_message (session, base_uri, "/noauth",
+                             FALSE, use_ntlm, SOUP_STATUS_OK);
+       errors += do_message (session, base_uri, "/alice",
+                             !use_ntlm || bob, FALSE,
+                             alice ? SOUP_STATUS_OK :
+                             SOUP_STATUS_UNAUTHORIZED);
+       errors += do_message (session, base_uri, "/alice/404",
+                             !use_ntlm, bob,
+                             alice ? SOUP_STATUS_NOT_FOUND :
+                             SOUP_STATUS_UNAUTHORIZED);
+       errors += do_message (session, base_uri, "/alice",
+                             !use_ntlm, bob,
+                             alice ? SOUP_STATUS_OK :
+                             SOUP_STATUS_UNAUTHORIZED);
+       errors += do_message (session, base_uri, "/bob",
+                             !use_ntlm || alice, bob,
+                             bob ? SOUP_STATUS_OK :
+                             SOUP_STATUS_UNAUTHORIZED);
+       errors += do_message (session, base_uri, "/alice",
+                             !use_ntlm || bob, alice,
+                             alice ? SOUP_STATUS_OK :
+                             SOUP_STATUS_UNAUTHORIZED);
+
+       soup_session_abort (session);
+       g_object_unref (session);
+
+       return errors;
+}
+
+static int
+do_ntlm_tests (SoupUri *base_uri)
+{
+       int errors = 0;
+
+       printf ("Round 1: Non-NTLM Connection\n");
+       errors += do_ntlm_round (base_uri, NULL);
+       printf ("Round 2: NTLM Connection, user=alice\n");
+       errors += do_ntlm_round (base_uri, "alice");
+       printf ("Round 3: NTLM Connection, user=bob\n");
+       errors += do_ntlm_round (base_uri, "bob");
+
+       return errors;
+}
+
+static void
+quit (int sig)
+{
+       /* Exit cleanly on ^C in case we're valgrinding. */
+       exit (0);
+}
+
+int
+main (int argc, char **argv)
+{
+       GMainLoop *loop;
+       SoupServer *server;
+       int opt;
+       int port = SOUP_ADDRESS_ANY_PORT;
+       GHashTable *connections;
+       SoupUri *uri;
+       int errors;
+
+       g_type_init ();
+       g_thread_init (NULL);
+       signal (SIGINT, quit);
+
+       while ((opt = getopt (argc, argv, "p:")) != -1) {
+               switch (opt) {
+               case 'p':
+                       port = atoi (optarg);
+                       break;
+               default:
+                       fprintf (stderr, "Usage: %s [-p port]\n",
+                                argv[0]);
+                       exit (1);
+               }
+       }
+
+       connections = g_hash_table_new (NULL, NULL);
+
+       server = soup_server_new (SOUP_SERVER_PORT, port,
+                                 NULL);
+       if (!server) {
+               fprintf (stderr, "Unable to bind to server port %d\n", port);
+               exit (1);
+       }
+       soup_server_add_handler (server, NULL, NULL,
+                                server_callback, NULL, connections);
+       soup_server_run_async (server);
+
+       loop = g_main_loop_new (NULL, TRUE);
+
+       uri = g_new0 (SoupUri, 1);
+       uri->protocol = SOUP_PROTOCOL_HTTP;
+       uri->host = g_strdup ("localhost");
+       uri->port = soup_server_get_port (server);
+       errors = do_ntlm_tests (uri);
+
+       printf ("\n%d errors\n", errors);
+       return errors != 0;
+}