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, not_found = FALSE;
62 gboolean basic_allowed = TRUE, ntlm_allowed = TRUE;
64 if (msg->method != SOUP_METHOD_GET) {
65 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
69 if (!strncmp (path, "/alice", 6))
70 required_user = NTLM_AUTHENTICATED_ALICE;
71 else if (!strncmp (path, "/bob", 4))
72 required_user = NTLM_AUTHENTICATED_BOB;
73 else if (!strncmp (path, "/either", 7))
75 else if (!strncmp (path, "/basic", 6))
77 else if (!strncmp (path, "/noauth", 7))
78 basic_allowed = ntlm_allowed = FALSE;
79 auth_required = ntlm_allowed || basic_allowed;
81 if (strstr (path, "/404"))
84 socket = soup_client_context_get_socket (client);
85 state = GPOINTER_TO_INT (g_hash_table_lookup (connections, socket));
86 auth = soup_message_headers_get_one (msg->request_headers,
90 if (!strncmp (auth, "NTLM ", 5)) {
91 if (!strncmp (auth + 5, NTLM_REQUEST_START,
92 strlen (NTLM_REQUEST_START))) {
93 state = NTLM_RECEIVED_REQUEST;
94 /* If they start, they must finish, even if
97 auth_required = ntlm_allowed = TRUE;
98 basic_allowed = FALSE;
99 } else if (state == NTLM_SENT_CHALLENGE &&
100 !strncmp (auth + 5, NTLM_RESPONSE_START,
101 strlen (NTLM_RESPONSE_START))) {
102 state = NTLM_RESPONSE_USER (auth + 5);
104 state = NTLM_UNAUTHENTICATED;
105 } else if (basic_allowed && !strncmp (auth, "Basic ", 6)) {
107 char *decoded = (char *)g_base64_decode (auth + 6, &len);
109 if (!strncmp (decoded, "alice:password", len) &&
110 required_user != NTLM_AUTHENTICATED_BOB)
111 auth_required = FALSE;
112 else if (!strncmp (decoded, "bob:password", len) &&
113 required_user != NTLM_AUTHENTICATED_ALICE)
114 auth_required = FALSE;
119 if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
120 (!required_user || required_user == state))
121 auth_required = FALSE;
124 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
126 if (basic_allowed && state != NTLM_RECEIVED_REQUEST) {
127 soup_message_headers_append (msg->response_headers,
129 "Basic realm=\"ntlm-test\"");
132 if (ntlm_allowed && state == NTLM_RECEIVED_REQUEST) {
133 soup_message_headers_append (msg->response_headers,
135 "NTLM " NTLM_CHALLENGE);
136 state = NTLM_SENT_CHALLENGE;
137 } else if (ntlm_allowed) {
138 soup_message_headers_append (msg->response_headers,
139 "WWW-Authenticate", "NTLM");
140 soup_message_headers_append (msg->response_headers,
141 "Connection", "close");
145 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
147 soup_message_set_response (msg, "text/plain",
150 soup_message_set_status (msg, SOUP_STATUS_OK);
154 g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
155 g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
159 authenticate (SoupSession *session, SoupMessage *msg,
160 SoupAuth *auth, gboolean retrying, gpointer user)
163 soup_auth_authenticate (auth, user, "password");
167 gboolean got_ntlm_prompt;
168 gboolean got_basic_prompt;
169 gboolean sent_ntlm_request;
170 gboolean got_ntlm_challenge;
171 gboolean sent_ntlm_response;
172 gboolean sent_basic_response;
176 prompt_check (SoupMessage *msg, gpointer user_data)
178 NTLMState *state = user_data;
181 header = soup_message_headers_get_list (msg->response_headers,
183 if (header && strstr (header, "Basic "))
184 state->got_basic_prompt = TRUE;
185 if (header && strstr (header, "NTLM") &&
186 !strstr (header, NTLM_CHALLENGE))
187 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 = !g_strcmp0 (user, "alice");
337 gboolean bob = !g_strcmp0 (user, "bob");
338 gboolean alice_via_ntlm = use_ntlm && alice;
339 gboolean bob_via_ntlm = use_ntlm && bob;
340 gboolean alice_via_basic = !use_ntlm && alice;
342 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
344 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
347 g_signal_connect (session, "authenticate",
348 G_CALLBACK (authenticate), (char *)user);
351 /* 1. Server doesn't request auth, so both get_ntlm_prompt and
352 * get_basic_prompt are both FALSE, and likewise do_basic. But
353 * if we're using NTLM we'll try that even without the server
356 do_message (session, base_uri, "/noauth",
361 /* 2. Server requires auth as Alice, so it will request that
362 * if we didn't already authenticate the connection to her in
363 * the previous step. If we authenticated as Bob in the
364 * previous step, then we'll just immediately get a 401 here.
365 * So in no case will we see the client try to do_ntlm.
367 do_message (session, base_uri, "/alice",
368 !alice_via_ntlm, FALSE,
369 !alice_via_ntlm, alice_via_basic,
370 alice ? SOUP_STATUS_OK :
371 SOUP_STATUS_UNAUTHORIZED);
373 /* 3. Server still requires auth as Alice, but this URI
374 * doesn't exist, so Alice should get a 404, but others still
375 * get 401. Alice-via-NTLM is still authenticated, and so
376 * won't get prompts, and Alice-via-Basic knows at this point
377 * to send auth without it being requested, so also won't get
378 * prompts. But Bob/nobody will.
380 do_message (session, base_uri, "/alice/404",
381 !alice, bob_via_ntlm,
382 !alice, alice_via_basic,
383 alice ? SOUP_STATUS_NOT_FOUND :
384 SOUP_STATUS_UNAUTHORIZED);
386 /* 4. Should be exactly the same as #3, except the status code */
387 do_message (session, base_uri, "/alice",
388 !alice, bob_via_ntlm,
389 !alice, alice_via_basic,
390 alice ? SOUP_STATUS_OK :
391 SOUP_STATUS_UNAUTHORIZED);
393 /* 5. This path requires auth as Bob; Alice-via-NTLM will get
394 * an immediate 401 and not try to reauthenticate.
395 * Alice-via-Basic will get a 401 and then try to do Basic
396 * (and fail). Bob-via-NTLM will try to do NTLM right away and
399 do_message (session, base_uri, "/bob",
400 !bob_via_ntlm, bob_via_ntlm,
401 !bob_via_ntlm, alice_via_basic,
402 bob ? SOUP_STATUS_OK :
403 SOUP_STATUS_UNAUTHORIZED);
405 /* 6. Back to /alice. Somewhat the inverse of #5; Bob-via-NTLM
406 * will get an immediate 401 and not try again, Alice-via-NTLM
407 * will try to do NTLM right away and succeed. Alice-via-Basic
408 * still knows about this path, so will try Basic right away
411 do_message (session, base_uri, "/alice",
412 !alice_via_ntlm, alice_via_ntlm,
413 !alice_via_ntlm, alice_via_basic,
414 alice ? SOUP_STATUS_OK :
415 SOUP_STATUS_UNAUTHORIZED);
417 /* 7. Server accepts Basic auth from either user, but not NTLM.
418 * Since Bob-via-NTLM is unauthenticated at this point, he'll try
419 * NTLM before realizing that the server doesn't support it.
421 do_message (session, base_uri, "/basic",
424 user != NULL ? SOUP_STATUS_OK :
425 SOUP_STATUS_UNAUTHORIZED);
427 /* 8. Server accepts Basic or NTLM from either user.
428 * Alice-via-NTLM is still authenticated at this point from #6,
429 * and Bob-via-NTLM is authenticated from #7, so neither
430 * of them will do anything.
432 do_message (session, base_uri, "/either",
434 !use_ntlm, !use_ntlm && user != NULL,
435 user != NULL ? SOUP_STATUS_OK :
436 SOUP_STATUS_UNAUTHORIZED);
438 soup_test_session_abort_unref (session);
442 do_ntlm_tests (SoupURI *base_uri)
444 debug_printf (1, "Round 1: Non-NTLM Connection, no auth\n");
445 do_ntlm_round (base_uri, FALSE, NULL);
446 debug_printf (1, "Round 2: NTLM Connection, user=alice\n");
447 do_ntlm_round (base_uri, TRUE, "alice");
448 debug_printf (1, "Round 3: NTLM Connection, user=bob\n");
449 do_ntlm_round (base_uri, TRUE, "bob");
450 debug_printf (1, "Round 4: Non-NTLM Connection, user=alice\n");
451 do_ntlm_round (base_uri, FALSE, "alice");
455 main (int argc, char **argv)
459 GHashTable *connections;
462 test_init (argc, argv, NULL);
464 server = soup_test_server_new (FALSE);
465 connections = g_hash_table_new (NULL, NULL);
466 soup_server_add_handler (server, NULL,
467 server_callback, connections, NULL);
469 loop = g_main_loop_new (NULL, TRUE);
471 uri = soup_uri_new ("http://127.0.0.1/");
472 soup_uri_set_port (uri, soup_server_get_port (server));
476 g_main_loop_unref (loop);
478 soup_test_server_quit_unref (server);
480 g_hash_table_destroy (connections);