--- /dev/null
+/* -*- 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;
+}