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.
12 #include "test-utils.h"
16 NTLM_RECEIVED_REQUEST,
18 NTLM_AUTHENTICATED_ALICE,
19 NTLM_AUTHENTICATED_BOB
22 #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA"
23 #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
25 #define NTLM_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
27 #define NTLM_RESPONSE_USER(response) ((response)[102] == 'E' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB)
30 clear_state (gpointer connections, GObject *ex_connection)
32 g_hash_table_remove (connections, ex_connection);
36 server_callback (SoupServer *server, SoupMessage *msg,
37 const char *path, GHashTable *query,
38 SoupClientContext *client, gpointer data)
40 GHashTable *connections = data;
43 NTLMServerState state, required_user = 0;
44 gboolean auth_required, not_found = FALSE;
45 gboolean basic_allowed = TRUE, ntlm_allowed = TRUE;
47 if (msg->method != SOUP_METHOD_GET) {
48 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
52 if (!strncmp (path, "/alice", 6))
53 required_user = NTLM_AUTHENTICATED_ALICE;
54 else if (!strncmp (path, "/bob", 4))
55 required_user = NTLM_AUTHENTICATED_BOB;
56 else if (!strncmp (path, "/either", 7))
58 else if (!strncmp (path, "/basic", 6))
60 else if (!strncmp (path, "/noauth", 7))
61 basic_allowed = ntlm_allowed = FALSE;
62 auth_required = ntlm_allowed || basic_allowed;
64 if (strstr (path, "/404"))
67 socket = soup_client_context_get_socket (client);
68 state = GPOINTER_TO_INT (g_hash_table_lookup (connections, socket));
69 auth = soup_message_headers_get_one (msg->request_headers,
73 if (!strncmp (auth, "NTLM ", 5)) {
74 if (!strncmp (auth + 5, NTLM_REQUEST_START,
75 strlen (NTLM_REQUEST_START))) {
76 state = NTLM_RECEIVED_REQUEST;
77 /* If they start, they must finish, even if
80 auth_required = ntlm_allowed = TRUE;
81 basic_allowed = FALSE;
82 } else if (state == NTLM_SENT_CHALLENGE &&
83 !strncmp (auth + 5, NTLM_RESPONSE_START,
84 strlen (NTLM_RESPONSE_START))) {
85 state = NTLM_RESPONSE_USER (auth + 5);
87 state = NTLM_UNAUTHENTICATED;
88 } else if (basic_allowed && !strncmp (auth, "Basic ", 6)) {
90 char *decoded = (char *)g_base64_decode (auth + 6, &len);
92 if (!strncmp (decoded, "alice:password", len) &&
93 required_user != NTLM_AUTHENTICATED_BOB)
94 auth_required = FALSE;
95 else if (!strncmp (decoded, "bob:password", len) &&
96 required_user != NTLM_AUTHENTICATED_ALICE)
97 auth_required = FALSE;
102 if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
103 (!required_user || required_user == state))
104 auth_required = FALSE;
107 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
109 if (basic_allowed && state != NTLM_RECEIVED_REQUEST) {
110 soup_message_headers_append (msg->response_headers,
112 "Basic realm=\"ntlm-test\"");
115 if (ntlm_allowed && state == NTLM_RECEIVED_REQUEST) {
116 soup_message_headers_append (msg->response_headers,
118 "NTLM " NTLM_CHALLENGE);
119 state = NTLM_SENT_CHALLENGE;
120 } else if (ntlm_allowed) {
121 soup_message_headers_append (msg->response_headers,
122 "WWW-Authenticate", "NTLM");
123 soup_message_headers_append (msg->response_headers,
124 "Connection", "close");
128 soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
130 soup_message_set_response (msg, "text/plain",
133 soup_message_set_status (msg, SOUP_STATUS_OK);
137 g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
138 g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
142 authenticate (SoupSession *session, SoupMessage *msg,
143 SoupAuth *auth, gboolean retrying, gpointer user)
146 soup_auth_authenticate (auth, user, "password");
150 gboolean got_ntlm_prompt;
151 gboolean got_basic_prompt;
152 gboolean sent_ntlm_request;
153 gboolean got_ntlm_challenge;
154 gboolean sent_ntlm_response;
155 gboolean sent_basic_response;
159 prompt_check (SoupMessage *msg, gpointer user_data)
161 NTLMState *state = user_data;
164 header = soup_message_headers_get_list (msg->response_headers,
166 if (header && strstr (header, "Basic "))
167 state->got_basic_prompt = TRUE;
168 if (header && strstr (header, "NTLM") &&
169 !strstr (header, NTLM_CHALLENGE))
170 state->got_ntlm_prompt = TRUE;
174 challenge_check (SoupMessage *msg, gpointer user_data)
176 NTLMState *state = user_data;
179 header = soup_message_headers_get_list (msg->response_headers,
181 if (header && !strncmp (header, "NTLM ", 5))
182 state->got_ntlm_challenge = TRUE;
186 request_check (SoupMessage *msg, gpointer user_data)
188 NTLMState *state = user_data;
191 header = soup_message_headers_get_one (msg->request_headers,
193 if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START,
194 strlen ("NTLM " NTLM_REQUEST_START)))
195 state->sent_ntlm_request = TRUE;
199 response_check (SoupMessage *msg, gpointer user_data)
201 NTLMState *state = user_data;
204 header = soup_message_headers_get_one (msg->request_headers,
206 if (header && !strncmp (header, "NTLM " NTLM_RESPONSE_START,
207 strlen ("NTLM " NTLM_RESPONSE_START)))
208 state->sent_ntlm_response = TRUE;
209 if (header && !strncmp (header, "Basic ", 6))
210 state->sent_basic_response = TRUE;
214 do_message (SoupSession *session, SoupURI *base_uri, const char *path,
215 gboolean get_ntlm_prompt, gboolean do_ntlm,
216 gboolean get_basic_prompt, gboolean do_basic,
221 NTLMState state = { FALSE, FALSE, FALSE, FALSE };
223 uri = soup_uri_new_with_base (base_uri, path);
224 msg = soup_message_new_from_uri ("GET", uri);
227 g_signal_connect (msg, "got_headers",
228 G_CALLBACK (prompt_check), &state);
229 g_signal_connect (msg, "got_headers",
230 G_CALLBACK (challenge_check), &state);
231 g_signal_connect (msg, "wrote-headers",
232 G_CALLBACK (request_check), &state);
233 g_signal_connect (msg, "wrote-headers",
234 G_CALLBACK (response_check), &state);
236 soup_session_send_message (session, msg);
237 debug_printf (1, " %-10s -> ", path);
239 if (state.got_ntlm_prompt) {
240 debug_printf (1, " NTLM_PROMPT");
241 if (!get_ntlm_prompt) {
242 debug_printf (1, "???");
245 } else if (get_ntlm_prompt) {
246 debug_printf (1, " no-ntlm-prompt???");
250 if (state.got_basic_prompt) {
251 debug_printf (1, " BASIC_PROMPT");
252 if (!get_basic_prompt) {
253 debug_printf (1, "???");
256 } else if (get_basic_prompt) {
257 debug_printf (1, " no-basic-prompt???");
261 if (state.sent_ntlm_request) {
262 debug_printf (1, " REQUEST");
264 debug_printf (1, "???");
267 } else if (do_ntlm) {
268 debug_printf (1, " no-request???");
272 if (state.got_ntlm_challenge) {
273 debug_printf (1, " CHALLENGE");
275 debug_printf (1, "???");
278 } else if (do_ntlm) {
279 debug_printf (1, " no-challenge???");
283 if (state.sent_ntlm_response) {
284 debug_printf (1, " NTLM_RESPONSE");
286 debug_printf (1, "???");
289 } else if (do_ntlm) {
290 debug_printf (1, " no-ntlm-response???");
294 if (state.sent_basic_response) {
295 debug_printf (1, " BASIC_RESPONSE");
297 debug_printf (1, "???");
300 } else if (do_basic) {
301 debug_printf (1, " no-basic-response???");
305 debug_printf (1, " -> %s", msg->reason_phrase);
306 if (msg->status_code != status_code) {
307 debug_printf (1, "???");
310 debug_printf (1, "\n");
312 g_object_unref (msg);
316 do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user)
318 SoupSession *session;
319 gboolean alice = !g_strcmp0 (user, "alice");
320 gboolean bob = !g_strcmp0 (user, "bob");
321 gboolean alice_via_ntlm = use_ntlm && alice;
322 gboolean bob_via_ntlm = use_ntlm && bob;
323 gboolean alice_via_basic = !use_ntlm && alice;
325 session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
327 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
330 g_signal_connect (session, "authenticate",
331 G_CALLBACK (authenticate), (char *)user);
334 /* 1. Server doesn't request auth, so both get_ntlm_prompt and
335 * get_basic_prompt are both FALSE, and likewise do_basic. But
336 * if we're using NTLM we'll try that even without the server
339 do_message (session, base_uri, "/noauth",
344 /* 2. Server requires auth as Alice, so it will request that
345 * if we didn't already authenticate the connection to her in
346 * the previous step. If we authenticated as Bob in the
347 * previous step, then we'll just immediately get a 401 here.
348 * So in no case will we see the client try to do_ntlm.
350 do_message (session, base_uri, "/alice",
351 !alice_via_ntlm, FALSE,
352 !alice_via_ntlm, alice_via_basic,
353 alice ? SOUP_STATUS_OK :
354 SOUP_STATUS_UNAUTHORIZED);
356 /* 3. Server still requires auth as Alice, but this URI
357 * doesn't exist, so Alice should get a 404, but others still
358 * get 401. Alice-via-NTLM is still authenticated, and so
359 * won't get prompts, and Alice-via-Basic knows at this point
360 * to send auth without it being requested, so also won't get
361 * prompts. But Bob/nobody will.
363 do_message (session, base_uri, "/alice/404",
364 !alice, bob_via_ntlm,
365 !alice, alice_via_basic,
366 alice ? SOUP_STATUS_NOT_FOUND :
367 SOUP_STATUS_UNAUTHORIZED);
369 /* 4. Should be exactly the same as #3, except the status code */
370 do_message (session, base_uri, "/alice",
371 !alice, bob_via_ntlm,
372 !alice, alice_via_basic,
373 alice ? SOUP_STATUS_OK :
374 SOUP_STATUS_UNAUTHORIZED);
376 /* 5. This path requires auth as Bob; Alice-via-NTLM will get
377 * an immediate 401 and not try to reauthenticate.
378 * Alice-via-Basic will get a 401 and then try to do Basic
379 * (and fail). Bob-via-NTLM will try to do NTLM right away and
382 do_message (session, base_uri, "/bob",
383 !bob_via_ntlm, bob_via_ntlm,
384 !bob_via_ntlm, alice_via_basic,
385 bob ? SOUP_STATUS_OK :
386 SOUP_STATUS_UNAUTHORIZED);
388 /* 6. Back to /alice. Somewhat the inverse of #5; Bob-via-NTLM
389 * will get an immediate 401 and not try again, Alice-via-NTLM
390 * will try to do NTLM right away and succeed. Alice-via-Basic
391 * still knows about this path, so will try Basic right away
394 do_message (session, base_uri, "/alice",
395 !alice_via_ntlm, alice_via_ntlm,
396 !alice_via_ntlm, alice_via_basic,
397 alice ? SOUP_STATUS_OK :
398 SOUP_STATUS_UNAUTHORIZED);
400 /* 7. Server accepts Basic auth from either user, but not NTLM.
401 * Since Bob-via-NTLM is unauthenticated at this point, he'll try
402 * NTLM before realizing that the server doesn't support it.
404 do_message (session, base_uri, "/basic",
407 user != NULL ? SOUP_STATUS_OK :
408 SOUP_STATUS_UNAUTHORIZED);
410 /* 8. Server accepts Basic or NTLM from either user.
411 * Alice-via-NTLM is still authenticated at this point from #6,
412 * and Bob-via-NTLM is authenticated from #7, so neither
413 * of them will do anything.
415 do_message (session, base_uri, "/either",
417 !use_ntlm, !use_ntlm && user != NULL,
418 user != NULL ? SOUP_STATUS_OK :
419 SOUP_STATUS_UNAUTHORIZED);
421 soup_test_session_abort_unref (session);
425 do_ntlm_tests (SoupURI *base_uri)
427 debug_printf (1, "Round 1: Non-NTLM Connection, no auth\n");
428 do_ntlm_round (base_uri, FALSE, NULL);
429 debug_printf (1, "Round 2: NTLM Connection, user=alice\n");
430 do_ntlm_round (base_uri, TRUE, "alice");
431 debug_printf (1, "Round 3: NTLM Connection, user=bob\n");
432 do_ntlm_round (base_uri, TRUE, "bob");
433 debug_printf (1, "Round 4: Non-NTLM Connection, user=alice\n");
434 do_ntlm_round (base_uri, FALSE, "alice");
438 main (int argc, char **argv)
442 GHashTable *connections;
445 test_init (argc, argv, NULL);
447 server = soup_test_server_new (FALSE);
448 connections = g_hash_table_new (NULL, NULL);
449 soup_server_add_handler (server, NULL,
450 server_callback, connections, NULL);
452 loop = g_main_loop_new (NULL, TRUE);
454 uri = soup_uri_new ("http://127.0.0.1/");
455 soup_uri_set_port (uri, soup_server_get_port (server));
459 g_main_loop_unref (loop);
461 soup_test_server_quit_unref (server);
463 g_hash_table_destroy (connections);