Update to version 2.39.3
[profile/ivi/libsoup2.4.git] / tests / ntlm-test.c
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 <ctype.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21
22 #include <glib.h>
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>
28
29 #include "test-utils.h"
30
31 typedef enum {
32         NTLM_UNAUTHENTICATED,
33         NTLM_RECEIVED_REQUEST,
34         NTLM_SENT_CHALLENGE,
35         NTLM_AUTHENTICATED_ALICE,
36         NTLM_AUTHENTICATED_BOB
37 } NTLMServerState;
38
39 #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA"
40 #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA"
41
42 #define NTLM_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="
43
44 #define NTLM_RESPONSE_USER(response) ((response)[102] == 'E' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB)
45
46 static void
47 clear_state (gpointer connections, GObject *ex_connection)
48 {
49         g_hash_table_remove (connections, ex_connection);
50 }
51
52 static void
53 server_callback (SoupServer *server, SoupMessage *msg,
54                  const char *path, GHashTable *query,
55                  SoupClientContext *client, gpointer data)
56 {
57         GHashTable *connections = data;
58         SoupSocket *socket;
59         const char *auth;
60         NTLMServerState state, required_user = 0;
61         gboolean auth_required, not_found = FALSE;
62         gboolean basic_allowed = TRUE, ntlm_allowed = TRUE;
63
64         if (msg->method != SOUP_METHOD_GET) {
65                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
66                 return;
67         }
68
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))
74                 ;
75         else if (!strncmp (path, "/basic", 6))
76                 ntlm_allowed = FALSE;
77         else if (!strncmp (path, "/noauth", 7))
78                 basic_allowed = ntlm_allowed = FALSE;
79         auth_required = ntlm_allowed || basic_allowed;
80
81         if (strstr (path, "/404"))
82                 not_found = TRUE;
83
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,
87                                              "Authorization");
88
89         if (auth) {
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
95                                  * it was unnecessary.
96                                  */
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);
103                         } else
104                                 state = NTLM_UNAUTHENTICATED;
105                 } else if (basic_allowed && !strncmp (auth, "Basic ", 6)) {
106                         gsize len;
107                         char *decoded = (char *)g_base64_decode (auth + 6, &len);
108
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;
115                         g_free (decoded);
116                 }
117         }
118
119         if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
120             (!required_user || required_user == state))
121                 auth_required = FALSE;
122
123         if (auth_required) {
124                 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
125
126                 if (basic_allowed && state != NTLM_RECEIVED_REQUEST) {
127                         soup_message_headers_append (msg->response_headers,
128                                                      "WWW-Authenticate",
129                                                      "Basic realm=\"ntlm-test\"");
130                 }
131
132                 if (ntlm_allowed && state == NTLM_RECEIVED_REQUEST) {
133                         soup_message_headers_append (msg->response_headers,
134                                                      "WWW-Authenticate",
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");
142                 }
143         } else {
144                 if (not_found)
145                         soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
146                 else {
147                         soup_message_set_response (msg, "text/plain",
148                                                    SOUP_MEMORY_STATIC,
149                                                    "OK\r\n", 4);
150                         soup_message_set_status (msg, SOUP_STATUS_OK);
151                 }
152         }
153
154         g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
155         g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
156 }
157
158 static void
159 authenticate (SoupSession *session, SoupMessage *msg,
160               SoupAuth *auth, gboolean retrying, gpointer user)
161 {
162         if (!retrying)
163                 soup_auth_authenticate (auth, user, "password");
164 }
165
166 typedef struct {
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;
173 } NTLMState;
174
175 static void
176 prompt_check (SoupMessage *msg, gpointer user_data)
177 {
178         NTLMState *state = user_data;
179         const char *header;
180
181         header = soup_message_headers_get_list (msg->response_headers,
182                                                 "WWW-Authenticate");
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;
188 }
189
190 static void
191 challenge_check (SoupMessage *msg, gpointer user_data)
192 {
193         NTLMState *state = user_data;
194         const char *header;
195
196         header = soup_message_headers_get_list (msg->response_headers,
197                                                 "WWW-Authenticate");
198         if (header && !strncmp (header, "NTLM ", 5))
199                 state->got_ntlm_challenge = TRUE;
200 }
201
202 static void
203 request_check (SoupMessage *msg, gpointer user_data)
204 {
205         NTLMState *state = user_data;
206         const char *header;
207
208         header = soup_message_headers_get_one (msg->request_headers,
209                                                "Authorization");
210         if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START,
211                                 strlen ("NTLM " NTLM_REQUEST_START)))
212                 state->sent_ntlm_request = TRUE;
213 }
214
215 static void
216 response_check (SoupMessage *msg, gpointer user_data)
217 {
218         NTLMState *state = user_data;
219         const char *header;
220
221         header = soup_message_headers_get_one (msg->request_headers,
222                                                "Authorization");
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;
228 }
229
230 static void
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,
234             guint status_code)
235 {
236         SoupURI *uri;
237         SoupMessage *msg;
238         NTLMState state = { FALSE, FALSE, FALSE, FALSE };
239
240         uri = soup_uri_new_with_base (base_uri, path);
241         msg = soup_message_new_from_uri ("GET", uri);
242         soup_uri_free (uri);
243
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);
252
253         soup_session_send_message (session, msg);
254         debug_printf (1, "  %-10s -> ", path);
255
256         if (state.got_ntlm_prompt) {
257                 debug_printf (1, " NTLM_PROMPT");
258                 if (!get_ntlm_prompt) {
259                         debug_printf (1, "???");
260                         errors++;
261                 }
262         } else if (get_ntlm_prompt) {
263                 debug_printf (1, " no-ntlm-prompt???");
264                 errors++;
265         }
266
267         if (state.got_basic_prompt) {
268                 debug_printf (1, " BASIC_PROMPT");
269                 if (!get_basic_prompt) {
270                         debug_printf (1, "???");
271                         errors++;
272                 }
273         } else if (get_basic_prompt) {
274                 debug_printf (1, " no-basic-prompt???");
275                 errors++;
276         }
277
278         if (state.sent_ntlm_request) {
279                 debug_printf (1, " REQUEST");
280                 if (!do_ntlm) {
281                         debug_printf (1, "???");
282                         errors++;
283                 }
284         } else if (do_ntlm) {
285                 debug_printf (1, " no-request???");
286                 errors++;
287         }
288
289         if (state.got_ntlm_challenge) {
290                 debug_printf (1, " CHALLENGE");
291                 if (!do_ntlm) {
292                         debug_printf (1, "???");
293                         errors++;
294                 }
295         } else if (do_ntlm) {
296                 debug_printf (1, " no-challenge???");
297                 errors++;
298         }
299
300         if (state.sent_ntlm_response) {
301                 debug_printf (1, " NTLM_RESPONSE");
302                 if (!do_ntlm) {
303                         debug_printf (1, "???");
304                         errors++;
305                 }
306         } else if (do_ntlm) {
307                 debug_printf (1, " no-ntlm-response???");
308                 errors++;
309         }
310
311         if (state.sent_basic_response) {
312                 debug_printf (1, " BASIC_RESPONSE");
313                 if (!do_basic) {
314                         debug_printf (1, "???");
315                         errors++;
316                 }
317         } else if (do_basic) {
318                 debug_printf (1, " no-basic-response???");
319                 errors++;
320         }
321
322         debug_printf (1, " -> %s", msg->reason_phrase);
323         if (msg->status_code != status_code) {
324                 debug_printf (1, "???");
325                 errors++;
326         }
327         debug_printf (1, "\n");
328
329         g_object_unref (msg);
330 }
331
332 static void
333 do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user)
334 {
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;
341
342         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
343         if (use_ntlm)
344                 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
345
346         if (user) {
347                 g_signal_connect (session, "authenticate",
348                                   G_CALLBACK (authenticate), (char *)user);
349         }
350
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
354          * asking.
355          */
356         do_message (session, base_uri, "/noauth",
357                     FALSE, use_ntlm,
358                     FALSE, FALSE,
359                     SOUP_STATUS_OK);
360
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.
366          */
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);
372
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.
379          */
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);
385
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);
392
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
397          * succeed.
398          */
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);
404
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
409          * and succeed.
410          */
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);
416
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.
420          */
421         do_message (session, base_uri, "/basic",
422                     FALSE, bob_via_ntlm,
423                     TRUE, user != NULL,
424                     user != NULL ? SOUP_STATUS_OK :
425                     SOUP_STATUS_UNAUTHORIZED);
426
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.
431          */
432         do_message (session, base_uri, "/either",
433                     !use_ntlm, FALSE,
434                     !use_ntlm, !use_ntlm && user != NULL,
435                     user != NULL ? SOUP_STATUS_OK :
436                     SOUP_STATUS_UNAUTHORIZED);
437
438         soup_test_session_abort_unref (session);
439 }
440
441 static void
442 do_ntlm_tests (SoupURI *base_uri)
443 {
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");
452 }
453
454 int
455 main (int argc, char **argv)
456 {
457         GMainLoop *loop;
458         SoupServer *server;
459         GHashTable *connections;
460         SoupURI *uri;
461
462         test_init (argc, argv, NULL);
463
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);
468
469         loop = g_main_loop_new (NULL, TRUE);
470
471         uri = soup_uri_new ("http://127.0.0.1/");
472         soup_uri_set_port (uri, soup_server_get_port (server));
473         do_ntlm_tests (uri);
474         soup_uri_free (uri);
475
476         g_main_loop_unref (loop);
477
478         soup_test_server_quit_unref (server);
479         test_cleanup ();
480         g_hash_table_destroy (connections);
481
482         return errors != 0;
483 }