1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2007 Red Hat, Inc.
6 /* This doesn't implement full server-side NTLM, and it mostly doesn't
7 * even test that the client is doing the crypto/encoding/etc parts of
8 * NTLM correctly. It only tests that the right message headers get
9 * set in the right messages.
23 #include <libsoup/soup-address.h>
24 #include <libsoup/soup-auth.h>
25 #include <libsoup/soup-message.h>
26 #include <libsoup/soup-server.h>
27 #include <libsoup/soup-session-async.h>
29 #include "test-utils.h"
33 NTLM_RECEIVED_REQUEST,
35 NTLM_AUTHENTICATED_ALICE,
36 NTLM_AUTHENTICATED_BOB
39 #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA"
40 #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
42 #define NTLM_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
44 #define NTLM_RESPONSE_USER(response) ((response)[102] == 'E' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB)
47 clear_state (gpointer connections, GObject *ex_connection)
49 g_hash_table_remove (connections, ex_connection);
53 server_callback (SoupServer *server, SoupMessage *msg,
54 const char *path, GHashTable *query,
55 SoupClientContext *client, gpointer data)
57 GHashTable *connections = data;
60 NTLMServerState state, required_user = 0;
61 gboolean auth_required = FALSE, not_found = FALSE;
62 gboolean basic_allowed = FALSE, ntlm_allowed = FALSE;
64 if (msg->method != SOUP_METHOD_GET) {
65 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
69 if (!strncmp (path, "/alice", 6)) {
72 required_user = NTLM_AUTHENTICATED_ALICE;
73 } else if (!strncmp (path, "/bob", 4)) {
76 required_user = NTLM_AUTHENTICATED_BOB;
77 } else if (!strncmp (path, "/either", 7)) {
79 ntlm_allowed = basic_allowed = TRUE;
80 } else if (!strncmp (path, "/basic", 6)) {
85 if (strstr (path, "/404"))
88 socket = soup_client_context_get_socket (client);
89 state = GPOINTER_TO_INT (g_hash_table_lookup (connections, socket));
90 auth = soup_message_headers_get_one (msg->request_headers,
94 if (!strncmp (auth, "NTLM ", 5)) {
95 if (!strncmp (auth + 5, NTLM_REQUEST_START,
96 strlen (NTLM_REQUEST_START))) {
97 state = NTLM_RECEIVED_REQUEST;
98 /* If they start, they must finish */
99 auth_required = ntlm_allowed = TRUE;
100 basic_allowed = FALSE;
101 } else if (state == NTLM_SENT_CHALLENGE &&
102 !strncmp (auth + 5, NTLM_RESPONSE_START,
103 strlen (NTLM_RESPONSE_START))) {
104 state = NTLM_RESPONSE_USER (auth + 5);
106 state = NTLM_UNAUTHENTICATED;
107 } else if (!strncmp (auth, "Basic ", 6) && basic_allowed) {
109 char *decoded = (char *)g_base64_decode (auth + 6, &len);
111 if (!strncmp (decoded, "alice:password", len) ||
112 !strncmp (decoded, "bob:password", len))
113 auth_required = FALSE;
118 if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
119 (!required_user || required_user == state))
120 auth_required = FALSE;
123 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
126 soup_message_headers_append (msg->response_headers,
128 "Basic realm=\"ntlm-test\"");
131 if (state == NTLM_RECEIVED_REQUEST) {
132 soup_message_headers_append (msg->response_headers,
134 "NTLM " NTLM_CHALLENGE);
135 state = NTLM_SENT_CHALLENGE;
136 } else if (ntlm_allowed) {
137 soup_message_headers_append (msg->response_headers,
138 "WWW-Authenticate", "NTLM");
139 soup_message_headers_append (msg->response_headers,
140 "Connection", "close");
144 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
146 soup_message_set_response (msg, "text/plain",
149 soup_message_set_status (msg, SOUP_STATUS_OK);
153 g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
154 g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
158 authenticate (SoupSession *session, SoupMessage *msg,
159 SoupAuth *auth, gboolean retrying, gpointer user)
161 soup_auth_authenticate (auth, user, "password");
165 gboolean got_ntlm_prompt;
166 gboolean got_basic_prompt;
167 gboolean sent_ntlm_request;
168 gboolean got_ntlm_challenge;
169 gboolean sent_ntlm_response;
170 gboolean sent_basic_response;
174 prompt_check (SoupMessage *msg, gpointer user_data)
176 NTLMState *state = user_data;
179 header = soup_message_headers_get_list (msg->response_headers,
181 if (header && strstr (header, "Basic "))
182 state->got_basic_prompt = TRUE;
183 if (!state->sent_ntlm_request) {
184 if (header && strstr (header, "NTLM") &&
185 !strstr (header, NTLM_CHALLENGE))
186 state->got_ntlm_prompt = TRUE;
191 challenge_check (SoupMessage *msg, gpointer user_data)
193 NTLMState *state = user_data;
196 header = soup_message_headers_get_list (msg->response_headers,
198 if (header && !strncmp (header, "NTLM ", 5))
199 state->got_ntlm_challenge = TRUE;
203 request_check (SoupMessage *msg, gpointer user_data)
205 NTLMState *state = user_data;
208 header = soup_message_headers_get_one (msg->request_headers,
210 if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START,
211 strlen ("NTLM " NTLM_REQUEST_START)))
212 state->sent_ntlm_request = TRUE;
216 response_check (SoupMessage *msg, gpointer user_data)
218 NTLMState *state = user_data;
221 header = soup_message_headers_get_one (msg->request_headers,
223 if (header && !strncmp (header, "NTLM " NTLM_RESPONSE_START,
224 strlen ("NTLM " NTLM_RESPONSE_START)))
225 state->sent_ntlm_response = TRUE;
226 if (header && !strncmp (header, "Basic ", 6))
227 state->sent_basic_response = TRUE;
231 do_message (SoupSession *session, SoupURI *base_uri, const char *path,
232 gboolean get_ntlm_prompt, gboolean do_ntlm,
233 gboolean get_basic_prompt, gboolean do_basic,
238 NTLMState state = { FALSE, FALSE, FALSE, FALSE };
240 uri = soup_uri_new_with_base (base_uri, path);
241 msg = soup_message_new_from_uri ("GET", uri);
244 g_signal_connect (msg, "got_headers",
245 G_CALLBACK (prompt_check), &state);
246 g_signal_connect (msg, "got_headers",
247 G_CALLBACK (challenge_check), &state);
248 g_signal_connect (msg, "wrote-headers",
249 G_CALLBACK (request_check), &state);
250 g_signal_connect (msg, "wrote-headers",
251 G_CALLBACK (response_check), &state);
253 soup_session_send_message (session, msg);
254 debug_printf (1, " %-10s -> ", path);
256 if (state.got_ntlm_prompt) {
257 debug_printf (1, " NTLM_PROMPT");
258 if (!get_ntlm_prompt) {
259 debug_printf (1, "???");
262 } else if (get_ntlm_prompt) {
263 debug_printf (1, " no-ntlm-prompt???");
267 if (state.got_basic_prompt) {
268 debug_printf (1, " BASIC_PROMPT");
269 if (!get_basic_prompt) {
270 debug_printf (1, "???");
273 } else if (get_basic_prompt) {
274 debug_printf (1, " no-basic-prompt???");
278 if (state.sent_ntlm_request) {
279 debug_printf (1, " REQUEST");
281 debug_printf (1, "???");
284 } else if (do_ntlm) {
285 debug_printf (1, " no-request???");
289 if (state.got_ntlm_challenge) {
290 debug_printf (1, " CHALLENGE");
292 debug_printf (1, "???");
295 } else if (do_ntlm) {
296 debug_printf (1, " no-challenge???");
300 if (state.sent_ntlm_response) {
301 debug_printf (1, " NTLM_RESPONSE");
303 debug_printf (1, "???");
306 } else if (do_ntlm) {
307 debug_printf (1, " no-ntlm-response???");
311 if (state.sent_basic_response) {
312 debug_printf (1, " BASIC_RESPONSE");
314 debug_printf (1, "???");
317 } else if (do_basic) {
318 debug_printf (1, " no-basic-response???");
322 debug_printf (1, " -> %s", msg->reason_phrase);
323 if (msg->status_code != status_code) {
324 debug_printf (1, "???");
327 debug_printf (1, "\n");
329 g_object_unref (msg);
333 do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user)
335 SoupSession *session;
336 gboolean alice = use_ntlm && !strcmp (user, "alice");
337 gboolean bob = use_ntlm && !strcmp (user, "bob");
339 g_return_if_fail (use_ntlm || !alice);
341 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
343 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
346 g_signal_connect (session, "authenticate",
347 G_CALLBACK (authenticate), (char *)user);
350 do_message (session, base_uri, "/noauth",
354 do_message (session, base_uri, "/alice",
355 !use_ntlm || bob, FALSE,
357 alice ? SOUP_STATUS_OK :
358 SOUP_STATUS_UNAUTHORIZED);
359 do_message (session, base_uri, "/alice/404",
362 alice ? SOUP_STATUS_NOT_FOUND :
363 SOUP_STATUS_UNAUTHORIZED);
364 do_message (session, base_uri, "/alice",
367 alice ? SOUP_STATUS_OK :
368 SOUP_STATUS_UNAUTHORIZED);
369 do_message (session, base_uri, "/bob",
370 !use_ntlm || alice, bob,
372 bob ? SOUP_STATUS_OK :
373 SOUP_STATUS_UNAUTHORIZED);
374 do_message (session, base_uri, "/alice",
375 !use_ntlm || bob, alice,
377 alice ? SOUP_STATUS_OK :
378 SOUP_STATUS_UNAUTHORIZED);
379 do_message (session, base_uri, "/basic",
382 user != NULL ? SOUP_STATUS_OK :
383 SOUP_STATUS_UNAUTHORIZED);
384 do_message (session, base_uri, "/either",
386 !use_ntlm, !use_ntlm && user != NULL,
387 user != NULL ? SOUP_STATUS_OK :
388 SOUP_STATUS_UNAUTHORIZED);
390 soup_test_session_abort_unref (session);
394 do_ntlm_tests (SoupURI *base_uri)
396 debug_printf (1, "Round 1: Non-NTLM Connection, no auth\n");
397 do_ntlm_round (base_uri, FALSE, NULL);
398 debug_printf (1, "Round 2: NTLM Connection, user=alice\n");
399 do_ntlm_round (base_uri, TRUE, "alice");
400 debug_printf (1, "Round 3: NTLM Connection, user=bob\n");
401 do_ntlm_round (base_uri, TRUE, "bob");
402 debug_printf (1, "Round 4: Non-NTLM Connection, user=alice\n");
403 do_ntlm_round (base_uri, FALSE, "alice");
407 main (int argc, char **argv)
411 GHashTable *connections;
414 test_init (argc, argv, NULL);
416 server = soup_test_server_new (FALSE);
417 connections = g_hash_table_new (NULL, NULL);
418 soup_server_add_handler (server, NULL,
419 server_callback, connections, NULL);
421 loop = g_main_loop_new (NULL, TRUE);
423 uri = soup_uri_new ("http://127.0.0.1/");
424 soup_uri_set_port (uri, soup_server_get_port (server));
428 g_main_loop_unref (loop);
430 soup_test_server_quit_unref (server);
432 g_hash_table_destroy (connections);