1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Red Hat, Inc.
4  */
5 
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.
10  */
11 
12 #include "test-utils.h"
13 
14 typedef enum {
15 	NTLM_UNAUTHENTICATED,
16 	NTLM_RECEIVED_REQUEST,
17 	NTLM_SENT_CHALLENGE,
18 	NTLM_AUTHENTICATED_ALICE,
19 	NTLM_AUTHENTICATED_BOB
20 } NTLMServerState;
21 
22 static const char *state_name[] = {
23 	"unauth", "recv", "sent", "alice", "bob"
24 };
25 
26 #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA"
27 #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
28 
29 /*
30  * NTLMV1_CHALLENGE - does not have "Negotiate Target Info" nor "Negotiate NTLM2 Key flags"
31  * NTLMV2_CHALLENGE - "Negotiate Target Info" flag is set
32  * NTLMSSP_CHALLENGE - "Negotiate NTLM2 Key" flag is set
33  */
34 #define NTLMV1_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAgEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
35 #define NTLMV2_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
36 #define NTLMSSP_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAgkAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
37 
38 #define NTLM_RESPONSE_FLAGS_OFFSET 60
39 #define NTLM_FLAGS_REQUEST_TARGET 0x00000004
40 
41 #define NTLM_RESPONSE_USER(response) ((response)[86] == 'E' ? NTLM_AUTHENTICATED_ALICE : ((response)[86] == 'I' ? NTLM_AUTHENTICATED_BOB : NTLM_UNAUTHENTICATED))
42 
43 typedef struct {
44 	SoupServer *server;
45 	GHashTable *connections;
46 	SoupURI *uri;
47 	gboolean ntlmssp;
48 	gboolean ntlmv2;
49 } TestServer;
50 
51 static void
clear_state(gpointer connections,GObject * ex_connection)52 clear_state (gpointer connections, GObject *ex_connection)
53 {
54 	g_hash_table_remove (connections, ex_connection);
55 }
56 
57 static void
server_callback(SoupServer * server,SoupMessage * msg,const char * path,GHashTable * query,SoupClientContext * client,gpointer data)58 server_callback (SoupServer *server, SoupMessage *msg,
59 		 const char *path, GHashTable *query,
60 		 SoupClientContext *client, gpointer data)
61 {
62 	TestServer *ts = data;
63 	GSocket *socket;
64 	const char *auth;
65 	NTLMServerState state, required_user = 0;
66 	gboolean auth_required, not_found = FALSE;
67 	gboolean basic_allowed = TRUE, ntlm_allowed = TRUE;
68 
69 	if (msg->method != SOUP_METHOD_GET) {
70 		soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
71 		return;
72 	}
73 
74 	if (!strncmp (path, "/alice", 6))
75 		required_user = NTLM_AUTHENTICATED_ALICE;
76 	else if (!strncmp (path, "/bob", 4))
77 		required_user = NTLM_AUTHENTICATED_BOB;
78 	else if (!strncmp (path, "/either", 7))
79 		;
80 	else if (!strncmp (path, "/basic", 6))
81 		ntlm_allowed = FALSE;
82 	else if (!strncmp (path, "/noauth", 7))
83 		basic_allowed = ntlm_allowed = FALSE;
84 	auth_required = ntlm_allowed || basic_allowed;
85 
86 	if (strstr (path, "/404"))
87 		not_found = TRUE;
88 
89 	socket = soup_client_context_get_gsocket (client);
90 	state = GPOINTER_TO_INT (g_hash_table_lookup (ts->connections, socket));
91 	auth = soup_message_headers_get_one (msg->request_headers,
92 					     "Authorization");
93 
94 	if (auth) {
95 		if (!strncmp (auth, "NTLM ", 5)) {
96 			if (!strncmp (auth + 5, NTLM_REQUEST_START,
97 				      strlen (NTLM_REQUEST_START))) {
98 				state = NTLM_RECEIVED_REQUEST;
99 				/* If they start, they must finish, even if
100 				 * it was unnecessary.
101 				 */
102 				auth_required = ntlm_allowed = TRUE;
103 				basic_allowed = FALSE;
104 			} else if (state == NTLM_SENT_CHALLENGE &&
105 				   !strncmp (auth + 5, NTLM_RESPONSE_START,
106 					     strlen (NTLM_RESPONSE_START))) {
107 				state = NTLM_RESPONSE_USER (auth + 5);
108 			} else
109 				state = NTLM_UNAUTHENTICATED;
110 		} else if (basic_allowed && !strncmp (auth, "Basic ", 6)) {
111 			gsize len;
112 			char *decoded = (char *)g_base64_decode (auth + 6, &len);
113 
114 			if (!strncmp (decoded, "alice:password", len) &&
115 			    required_user != NTLM_AUTHENTICATED_BOB)
116 				auth_required = FALSE;
117 			else if (!strncmp (decoded, "bob:password", len) &&
118 				 required_user != NTLM_AUTHENTICATED_ALICE)
119 				auth_required = FALSE;
120 			g_free (decoded);
121 		}
122 	}
123 
124 	if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
125 	    (!required_user || required_user == state))
126 		auth_required = FALSE;
127 
128 	if (auth_required) {
129 		soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
130 
131 		if (basic_allowed && state != NTLM_RECEIVED_REQUEST) {
132 			soup_message_headers_append (msg->response_headers,
133 						     "WWW-Authenticate",
134 						     "Basic realm=\"ntlm-test\"");
135 		}
136 
137 		if (ntlm_allowed && state == NTLM_RECEIVED_REQUEST) {
138 			soup_message_headers_append (msg->response_headers,
139 						     "WWW-Authenticate",
140 						     ts->ntlmssp ? ("NTLM " NTLMSSP_CHALLENGE) : ts->ntlmv2 ? ("NTLM " NTLMV2_CHALLENGE) : ("NTLM " NTLMV1_CHALLENGE));
141 			state = NTLM_SENT_CHALLENGE;
142 		} else if (ntlm_allowed) {
143 			soup_message_headers_append (msg->response_headers,
144 						     "WWW-Authenticate", "NTLM");
145 			soup_message_headers_append (msg->response_headers,
146 						     "Connection", "close");
147 		}
148 	} else {
149 		if (not_found)
150 			soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
151 		else {
152 			soup_message_set_response (msg, "text/plain",
153 						   SOUP_MEMORY_STATIC,
154 						   "OK\r\n", 4);
155 			soup_message_set_status (msg, SOUP_STATUS_OK);
156 		}
157 	}
158 
159 	debug_printf (2, " (S:%s)", state_name[state]);
160 	g_hash_table_insert (ts->connections, socket, GINT_TO_POINTER (state));
161 	g_object_weak_ref (G_OBJECT (socket), clear_state, ts->connections);
162 }
163 
164 static void
setup_server(TestServer * ts,gconstpointer test_data)165 setup_server (TestServer *ts,
166 	      gconstpointer test_data)
167 {
168 	ts->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
169 	ts->connections = g_hash_table_new (NULL, NULL);
170 	ts->ntlmssp = FALSE;
171 	ts->ntlmv2 = FALSE;
172 	soup_server_add_handler (ts->server, NULL, server_callback, ts, NULL);
173 
174 	ts->uri = soup_test_server_get_uri (ts->server, "http", NULL);
175 }
176 
177 static void
setup_ntlmssp_server(TestServer * ts,gconstpointer test_data)178 setup_ntlmssp_server (TestServer *ts,
179 		      gconstpointer test_data)
180 {
181 	setup_server (ts, test_data);
182 	ts->ntlmssp = TRUE;
183 }
184 
185 static void
setup_ntlmv2_server(TestServer * ts,gconstpointer test_data)186 setup_ntlmv2_server (TestServer *ts,
187 		      gconstpointer test_data)
188 {
189 	setup_server (ts, test_data);
190 	ts->ntlmv2 = TRUE;
191 }
192 
193 static void
teardown_server(TestServer * ts,gconstpointer test_data)194 teardown_server (TestServer *ts,
195 		 gconstpointer test_data)
196 {
197 	soup_uri_free (ts->uri);
198 	soup_test_server_quit_unref (ts->server);
199 	g_hash_table_destroy (ts->connections);
200 }
201 
202 static gboolean authenticated_ntlm = FALSE;
203 
204 static void
authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user)205 authenticate (SoupSession *session, SoupMessage *msg,
206 	      SoupAuth *auth, gboolean retrying, gpointer user)
207 {
208 	if (!retrying) {
209 		soup_auth_authenticate (auth, user, "password");
210 		if (g_str_equal (soup_auth_get_scheme_name (auth), "NTLM"))
211 			authenticated_ntlm = TRUE;
212 	}
213 }
214 
215 typedef struct {
216 	gboolean got_ntlm_prompt;
217 	gboolean got_basic_prompt;
218 	gboolean sent_ntlm_request;
219 	gboolean got_ntlm_challenge;
220 	gboolean sent_ntlm_response;
221 	gboolean sent_basic_response;
222 } NTLMState;
223 
224 static void
prompt_check(SoupMessage * msg,gpointer user_data)225 prompt_check (SoupMessage *msg, gpointer user_data)
226 {
227 	NTLMState *state = user_data;
228 	const char *header;
229 
230 	header = soup_message_headers_get_list (msg->response_headers,
231 						"WWW-Authenticate");
232 	if (header && strstr (header, "Basic "))
233 		state->got_basic_prompt = TRUE;
234 	if (header && strstr (header, "NTLM") &&
235 	    (!strstr (header, NTLMV1_CHALLENGE) &&
236 	     !strstr (header, NTLMSSP_CHALLENGE) &&
237 	     !strstr (header, NTLMV2_CHALLENGE))) {
238 		state->got_ntlm_prompt = TRUE;
239 	}
240 }
241 
242 static void
challenge_check(SoupMessage * msg,gpointer user_data)243 challenge_check (SoupMessage *msg, gpointer user_data)
244 {
245 	NTLMState *state = user_data;
246 	const char *header;
247 
248 	header = soup_message_headers_get_list (msg->response_headers,
249 						"WWW-Authenticate");
250 	if (header && !strncmp (header, "NTLM ", 5))
251 		state->got_ntlm_challenge = TRUE;
252 }
253 
254 static void
request_check(SoupMessage * msg,gpointer user_data)255 request_check (SoupMessage *msg, gpointer user_data)
256 {
257 	NTLMState *state = user_data;
258 	const char *header;
259 
260 	header = soup_message_headers_get_one (msg->request_headers,
261 					       "Authorization");
262 	if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START,
263 				strlen ("NTLM " NTLM_REQUEST_START)))
264 		state->sent_ntlm_request = TRUE;
265 }
266 
267 static void
response_check(SoupMessage * msg,gpointer user_data)268 response_check (SoupMessage *msg, gpointer user_data)
269 {
270 	NTLMState *state = user_data;
271 	const char *header;
272 	guchar *ntlm_data;
273 	gsize ntlm_data_sz;
274 	gboolean request_target;
275 	guint32 flags;
276 	int nt_resp_sz;
277 
278 	header = soup_message_headers_get_one (msg->request_headers,
279 					       "Authorization");
280 	if (header && !strncmp (header, "NTLM " NTLM_RESPONSE_START,
281 				strlen ("NTLM " NTLM_RESPONSE_START)))
282 	{
283 		ntlm_data = g_base64_decode (header + 5, &ntlm_data_sz);
284 
285 		memcpy (&flags, ntlm_data + NTLM_RESPONSE_FLAGS_OFFSET, sizeof(flags));
286 		flags = GUINT_FROM_LE (flags);
287 		request_target = (flags & NTLM_FLAGS_REQUEST_TARGET) ? TRUE : FALSE;
288 		nt_resp_sz = ntlm_data[22] | ntlm_data[23] << 8;
289 
290 		/*
291 		 * If the "Request Target" flag is not set in response, it should return NTLMv1 or NTLM2 Session Response,
292 		 * they both should return exactly 24-byte NT response.
293 		 * If the "Request Target" flag is set, it should return NTLMv2 reponse,
294 		 * which has NT response always over 24 bytes.
295 		 */
296 		if ((!request_target && nt_resp_sz == 24) || (request_target && nt_resp_sz > 24))
297 		{
298 			state->sent_ntlm_response = TRUE;
299 		}
300 
301 		g_free (ntlm_data);
302 	}
303 	if (header && !strncmp (header, "Basic ", 6))
304 		state->sent_basic_response = TRUE;
305 }
306 
307 static void
do_message(SoupSession * session,SoupURI * base_uri,const char * path,gboolean get_ntlm_prompt,gboolean do_ntlm,gboolean get_basic_prompt,gboolean do_basic,guint status_code)308 do_message (SoupSession *session, SoupURI *base_uri, const char *path,
309 	    gboolean get_ntlm_prompt, gboolean do_ntlm,
310 	    gboolean get_basic_prompt, gboolean do_basic,
311 	    guint status_code)
312 {
313 	SoupURI *uri;
314 	SoupMessage *msg;
315 	NTLMState state = { FALSE, FALSE, FALSE, FALSE };
316 
317 	uri = soup_uri_new_with_base (base_uri, path);
318 	msg = soup_message_new_from_uri ("GET", uri);
319 	soup_uri_free (uri);
320 
321 	g_signal_connect (msg, "got_headers",
322 			  G_CALLBACK (prompt_check), &state);
323 	g_signal_connect (msg, "got_headers",
324 			  G_CALLBACK (challenge_check), &state);
325 	g_signal_connect (msg, "wrote-headers",
326 			  G_CALLBACK (request_check), &state);
327 	g_signal_connect (msg, "wrote-headers",
328 			  G_CALLBACK (response_check), &state);
329 
330 	soup_session_send_message (session, msg);
331 	debug_printf (1, "  %-10s -> ", path);
332 
333 	if (state.got_ntlm_prompt) {
334 		debug_printf (1, " NTLM_PROMPT");
335 		if (!get_ntlm_prompt)
336 			debug_printf (1, "???");
337 	} else if (get_ntlm_prompt)
338 		debug_printf (1, " no-ntlm-prompt???");
339 
340 	if (state.got_basic_prompt) {
341 		debug_printf (1, " BASIC_PROMPT");
342 		if (!get_basic_prompt)
343 			debug_printf (1, "???");
344 	} else if (get_basic_prompt)
345 		debug_printf (1, " no-basic-prompt???");
346 
347 	if (state.sent_ntlm_request) {
348 		debug_printf (1, " REQUEST");
349 		if (!do_ntlm)
350 			debug_printf (1, "???");
351 	} else if (do_ntlm)
352 		debug_printf (1, " no-request???");
353 
354 	if (state.got_ntlm_challenge) {
355 		debug_printf (1, " CHALLENGE");
356 		if (!do_ntlm)
357 			debug_printf (1, "???");
358 	} else if (do_ntlm)
359 		debug_printf (1, " no-challenge???");
360 
361 	if (state.sent_ntlm_response) {
362 		debug_printf (1, " NTLM_RESPONSE");
363 		if (!do_ntlm)
364 			debug_printf (1, "???");
365 	} else if (do_ntlm)
366 		debug_printf (1, " no-ntlm-response???");
367 
368 	if (state.sent_basic_response) {
369 		debug_printf (1, " BASIC_RESPONSE");
370 		if (!do_basic)
371 			debug_printf (1, "???");
372 	} else if (do_basic)
373 		debug_printf (1, " no-basic-response???");
374 
375 	debug_printf (1, " -> %s", msg->reason_phrase);
376 	if (msg->status_code != status_code)
377 		debug_printf (1, "???");
378 	debug_printf (1, "\n");
379 
380 	g_assert_true (state.got_ntlm_prompt == get_ntlm_prompt);
381 	g_assert_true (state.got_basic_prompt == get_basic_prompt);
382 	g_assert_true (state.sent_ntlm_request == do_ntlm);
383 	g_assert_true (state.got_ntlm_challenge == do_ntlm);
384 	g_assert_true (state.sent_ntlm_response == do_ntlm);
385 	g_assert_true (state.sent_basic_response == do_basic);
386 	soup_test_assert_message_status (msg, status_code);
387 
388 	g_object_unref (msg);
389 }
390 
391 static void
do_ntlm_round(SoupURI * base_uri,gboolean use_ntlm,const char * user,gboolean use_builtin_ntlm)392 do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm,
393 	       const char *user, gboolean use_builtin_ntlm)
394 {
395 	SoupSession *session;
396 	gboolean alice = !g_strcmp0 (user, "alice");
397 	gboolean bob = !g_strcmp0 (user, "bob");
398 	gboolean alice_via_ntlm = use_ntlm && alice;
399 	gboolean bob_via_ntlm = use_ntlm && bob;
400 	gboolean alice_via_basic = !use_ntlm && alice;
401 
402 	session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
403 
404 	if (user) {
405 		g_signal_connect (session, "authenticate",
406 				  G_CALLBACK (authenticate), (char *)user);
407 		if (use_ntlm && !use_builtin_ntlm)
408 			g_setenv ("NTLMUSER", user, TRUE);
409 	}
410 	if (use_ntlm) {
411 		SoupAuthManager *auth_manager;
412 		SoupAuth *ntlm;
413 
414 		soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
415 		auth_manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER));
416 		ntlm = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL);
417 		soup_auth_manager_use_auth (auth_manager, base_uri, ntlm);
418 		g_object_unref (ntlm);
419 	}
420 
421 	/* 1. Server doesn't request auth, so both get_ntlm_prompt and
422 	 * get_basic_prompt are both FALSE, and likewise do_basic. But
423 	 * if we're using NTLM we'll try that even without the server
424 	 * asking.
425 	 */
426 	authenticated_ntlm = FALSE;
427 	do_message (session, base_uri, "/noauth/",
428 		    FALSE, use_ntlm,
429 		    FALSE, FALSE,
430 		    SOUP_STATUS_OK);
431 
432 	soup_test_assert (authenticated_ntlm == (use_ntlm && use_builtin_ntlm),
433 			  "%s built-in NTLM support, but authenticate signal %s emitted\n",
434 			  use_builtin_ntlm ? "Using" : "Not using",
435 			  authenticated_ntlm ? "was" : "wasn't");
436 
437 	/* 2. Server requires auth as Alice, so it will request that
438 	 * if we didn't already authenticate the connection to her in
439 	 * the previous step. If we authenticated as Bob in the
440 	 * previous step, then we'll just immediately get a 401 here.
441 	 * So in no case will we see the client try to do_ntlm.
442 	 */
443 	do_message (session, base_uri, "/alice/",
444 		    !alice_via_ntlm, FALSE,
445 		    !alice_via_ntlm, alice_via_basic,
446 		    alice ? SOUP_STATUS_OK :
447 		    SOUP_STATUS_UNAUTHORIZED);
448 
449 	/* 3. Server still requires auth as Alice, but this URI
450 	 * doesn't exist, so Alice should get a 404, but others still
451 	 * get 401. Alice-via-NTLM is still authenticated, and so
452 	 * won't get prompts, and Alice-via-Basic knows at this point
453 	 * to send auth without it being requested, so also won't get
454 	 * prompts. But Bob/nobody will.
455 	 */
456 	do_message (session, base_uri, "/alice/404",
457 		    !alice, bob_via_ntlm,
458 		    !alice, alice_via_basic,
459 		    alice ? SOUP_STATUS_NOT_FOUND :
460 		    SOUP_STATUS_UNAUTHORIZED);
461 
462 	/* 4. Should be exactly the same as #3, except the status code */
463 	do_message (session, base_uri, "/alice/",
464 		    !alice, bob_via_ntlm,
465 		    !alice, alice_via_basic,
466 		    alice ? SOUP_STATUS_OK :
467 		    SOUP_STATUS_UNAUTHORIZED);
468 
469 	/* 5. This path requires auth as Bob; Alice-via-NTLM will get
470 	 * an immediate 401 and not try to reauthenticate.
471 	 * Alice-via-Basic will get a 401 and then try to do Basic
472 	 * (and fail). Bob-via-NTLM will try to do NTLM right away and
473 	 * succeed.
474 	 */
475 	do_message (session, base_uri, "/bob/",
476 		    !bob_via_ntlm, bob_via_ntlm,
477 		    !bob_via_ntlm, alice_via_basic,
478 		    bob ? SOUP_STATUS_OK :
479 		    SOUP_STATUS_UNAUTHORIZED);
480 
481 	/* 6. Back to /alice. Somewhat the inverse of #5; Bob-via-NTLM
482 	 * will get an immediate 401 and not try again, Alice-via-NTLM
483 	 * will try to do NTLM right away and succeed. Alice-via-Basic
484 	 * still knows about this path, so will try Basic right away
485 	 * and succeed.
486 	 */
487 	do_message (session, base_uri, "/alice/",
488 		    !alice_via_ntlm, alice_via_ntlm,
489 		    !alice_via_ntlm, alice_via_basic,
490 		    alice ? SOUP_STATUS_OK :
491 		    SOUP_STATUS_UNAUTHORIZED);
492 
493 	/* 7. Server accepts Basic auth from either user, but not NTLM.
494 	 * Since Bob-via-NTLM is unauthenticated at this point, he'll try
495 	 * NTLM before realizing that the server doesn't support it.
496 	 */
497 	do_message (session, base_uri, "/basic/",
498 		    FALSE, bob_via_ntlm,
499 		    TRUE, user != NULL,
500 		    user != NULL ? SOUP_STATUS_OK :
501 		    SOUP_STATUS_UNAUTHORIZED);
502 
503 	/* 8. Server accepts Basic or NTLM from either user.
504 	 * NTLM users will try NTLM without getting a prompt (their
505 	 * previous NTLM connections will have been closed by the 401
506 	 * from /basic). Non-NTLM users will be prompted for either.
507 	 */
508 	do_message (session, base_uri, "/either/",
509 		    !use_ntlm, use_ntlm,
510 		    !use_ntlm, !use_ntlm && user != NULL,
511 		    user != NULL ? SOUP_STATUS_OK :
512 		    SOUP_STATUS_UNAUTHORIZED);
513 
514 	soup_test_session_abort_unref (session);
515 }
516 
517 typedef enum {
518 	BUILTIN,
519 	WINBIND,
520 	FALLBACK
521 } NtlmType;
522 
523 typedef struct {
524 	const char *name, *user;
525 	gboolean conn_uses_ntlm;
526 	NtlmType ntlm_type;
527 } NtlmTest;
528 
529 static const NtlmTest ntlm_tests[] = {
530 	{ "/ntlm/builtin/none",   NULL,    FALSE, BUILTIN },
531 	{ "/ntlm/builtin/alice",  "alice", TRUE,  BUILTIN },
532 	{ "/ntlm/builtin/bob",    "bob",   TRUE,  BUILTIN },
533 	{ "/ntlm/builtin/basic",  "alice", FALSE, BUILTIN },
534 
535 	{ "/ntlm/winbind/none",   NULL,    FALSE, WINBIND },
536 	{ "/ntlm/winbind/alice",  "alice", TRUE,  WINBIND },
537 	{ "/ntlm/winbind/bob",    "bob",   TRUE,  WINBIND },
538 	{ "/ntlm/winbind/basic",  "alice", FALSE, WINBIND },
539 
540 	{ "/ntlm/fallback/none",  NULL,    FALSE, FALLBACK },
541 	{ "/ntlm/fallback/alice", "alice", TRUE,  FALLBACK },
542 	{ "/ntlm/fallback/bob",   "bob",   TRUE,  FALLBACK },
543 	{ "/ntlm/fallback/basic", "alice", FALSE, FALLBACK }
544 };
545 
546 static const NtlmTest ntlmssp_tests[] = {
547 	{ "/ntlm/ssp/none",  NULL,    FALSE, BUILTIN },
548 	{ "/ntlm/ssp/alice", "alice", TRUE,  BUILTIN },
549 	{ "/ntlm/ssp/bob",   "bob",   TRUE,  BUILTIN },
550 	{ "/ntlm/ssp/basic", "alice", FALSE, BUILTIN }
551 };
552 
553 static const NtlmTest ntlmv2_tests[] = {
554 	{ "/ntlm/v2/none",  NULL,    FALSE, BUILTIN },
555 	{ "/ntlm/v2/alice", "alice", TRUE,  BUILTIN },
556 	{ "/ntlm/v2/bob",   "bob",   TRUE,  BUILTIN },
557 	{ "/ntlm/v2/basic", "alice", FALSE, BUILTIN }
558 };
559 
560 static void
do_ntlm_test(TestServer * ts,gconstpointer data)561 do_ntlm_test (TestServer *ts,
562 	      gconstpointer data)
563 {
564 	const NtlmTest *test = data;
565 	gboolean use_builtin_ntlm = TRUE;
566 
567 	switch (test->ntlm_type) {
568 	case BUILTIN:
569 		/* Built-in NTLM auth support. (We set SOUP_NTLM_AUTH_DEBUG to
570 		 * an empty string to ensure that the built-in support is
571 		 * being used, even if /usr/bin/ntlm_auth is available.)
572 		 */
573 		g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE);
574 		break;
575 
576 	case WINBIND:
577 #ifndef USE_NTLM_AUTH
578 		g_test_skip ("/usr/bin/ntlm_auth is not available");
579 		return;
580 #endif
581 
582 		/* Samba winbind /usr/bin/ntlm_auth helper support (via a
583 		 * helper program that emulates its interface).
584 		 */
585 		g_setenv ("SOUP_NTLM_AUTH_DEBUG",
586 			  g_test_get_filename (G_TEST_BUILT, "ntlm-test-helper", NULL),
587 			  TRUE);
588 		g_unsetenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS");
589 		use_builtin_ntlm = FALSE;
590 		break;
591 
592 	case FALLBACK:
593 #ifndef USE_NTLM_AUTH
594 		g_test_skip ("/usr/bin/ntlm_auth is not available");
595 		return;
596 #endif
597 
598 		/* Support for when ntlm_auth is installed, but the user has
599 		 * no cached credentials (and thus we have to fall back to
600 		 * libsoup's built-in NTLM support).
601 		 */
602 		g_setenv ("SOUP_NTLM_AUTH_DEBUG",
603 			  g_test_get_filename (G_TEST_BUILT, "ntlm-test-helper", NULL),
604 			  TRUE);
605 		g_setenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS", "1", TRUE);
606 		break;
607 	}
608 
609 	do_ntlm_round (ts->uri, test->conn_uses_ntlm, test->user, use_builtin_ntlm);
610 }
611 
612 static void
retry_test_authenticate(SoupSession * session,SoupMessage * msg,SoupAuth * auth,gboolean retrying,gpointer user_data)613 retry_test_authenticate (SoupSession *session, SoupMessage *msg,
614 			 SoupAuth *auth, gboolean retrying,
615 			 gpointer user_data)
616 {
617 	gboolean *retried = user_data;
618 
619 	if (!retrying) {
620 		/* server_callback doesn't actually verify the password,
621 		 * only the username. So we pass an incorrect username
622 		 * rather than an incorrect password.
623 		 */
624 		soup_auth_authenticate (auth, "wrong", "password");
625 	} else if (!*retried) {
626 		soup_auth_authenticate (auth, "alice", "password");
627 		*retried = TRUE;
628 	}
629 }
630 
631 static void
do_retrying_test(TestServer * ts,gconstpointer data)632 do_retrying_test (TestServer *ts,
633 		  gconstpointer data)
634 {
635 	SoupSession *session;
636 	SoupMessage *msg;
637 	SoupURI *uri;
638 	gboolean retried = FALSE;
639 
640 	g_test_bug ("693222");
641 
642 	g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE);
643 
644 	debug_printf (1, "  /alice\n");
645 
646 	session = soup_test_session_new (SOUP_TYPE_SESSION,
647 					 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM,
648 					 NULL);
649 	g_signal_connect (session, "authenticate",
650 			  G_CALLBACK (retry_test_authenticate), &retried);
651 
652 	uri = soup_uri_new_with_base (ts->uri, "/alice");
653 	msg = soup_message_new_from_uri ("GET", uri);
654 	soup_uri_free (uri);
655 
656 	soup_session_send_message (session, msg);
657 
658 	g_assert_true (retried);
659 	soup_test_assert_message_status (msg, SOUP_STATUS_OK);
660 
661 	g_object_unref (msg);
662 
663 	soup_test_session_abort_unref (session);
664 
665 	debug_printf (1, "  /bob\n");
666 
667 	session = soup_test_session_new (SOUP_TYPE_SESSION,
668 					 SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM,
669 					 NULL);
670 	g_signal_connect (session, "authenticate",
671 			  G_CALLBACK (retry_test_authenticate), &retried);
672 	retried = FALSE;
673 
674 	uri = soup_uri_new_with_base (ts->uri, "/bob");
675 	msg = soup_message_new_from_uri ("GET", uri);
676 	soup_uri_free (uri);
677 
678 	soup_session_send_message (session, msg);
679 
680 	g_assert_true (retried);
681 	soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
682 
683 	g_object_unref (msg);
684 
685 	soup_test_session_abort_unref (session);
686 }
687 
688 int
main(int argc,char ** argv)689 main (int argc, char **argv)
690 {
691 	int i, ret;
692 
693 	test_init (argc, argv, NULL);
694 
695 	for (i = 0; i < G_N_ELEMENTS (ntlm_tests); i++) {
696 		g_test_add (ntlm_tests[i].name, TestServer, &ntlm_tests[i],
697 			    setup_server, do_ntlm_test, teardown_server);
698 	}
699 	for (i = 0; i < G_N_ELEMENTS (ntlmssp_tests); i++) {
700 		g_test_add (ntlmssp_tests[i].name, TestServer, &ntlmssp_tests[i],
701 			    setup_ntlmssp_server, do_ntlm_test, teardown_server);
702 	}
703 	for (i = 0; i < G_N_ELEMENTS (ntlmv2_tests); i++) {
704 		g_test_add (ntlmv2_tests[i].name, TestServer, &ntlmv2_tests[i],
705 			    setup_ntlmv2_server, do_ntlm_test, teardown_server);
706 	}
707 
708 	g_test_add ("/ntlm/retry", TestServer, NULL,
709 		    setup_server, do_retrying_test, teardown_server);
710 
711 	ret = g_test_run ();
712 
713 	test_cleanup ();
714 
715 	return ret;
716 }
717