Git init
[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)[87] == 'h' ? 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 = FALSE, not_found = FALSE;
62         gboolean basic_allowed = FALSE, ntlm_allowed = FALSE;
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                 auth_required = TRUE;
71                 ntlm_allowed = TRUE;
72                 required_user = NTLM_AUTHENTICATED_ALICE;
73         } else if (!strncmp (path, "/bob", 4)) {
74                 auth_required = TRUE;
75                 ntlm_allowed = TRUE;
76                 required_user = NTLM_AUTHENTICATED_BOB;
77         } else if (!strncmp (path, "/either", 7)) {
78                 auth_required = TRUE;
79                 ntlm_allowed = basic_allowed = TRUE;
80         } else if (!strncmp (path, "/basic", 6)) {
81                 auth_required = TRUE;
82                 basic_allowed = TRUE;
83         }
84
85         if (strstr (path, "/404"))
86                 not_found = TRUE;
87
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,
91                                              "Authorization");
92
93         if (auth) {
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);
105                         } else
106                                 state = NTLM_UNAUTHENTICATED;
107                 } else if (!strncmp (auth, "Basic ", 6) && basic_allowed) {
108                         gsize len;
109                         char *decoded = (char *)g_base64_decode (auth + 6, &len);
110
111                         if (!strncmp (decoded, "alice:password", len) ||
112                             !strncmp (decoded, "bob:password", len))
113                                 auth_required = FALSE;
114                         g_free (decoded);
115                 }
116         }
117
118         if (ntlm_allowed && state > NTLM_SENT_CHALLENGE &&
119             (!required_user || required_user == state))
120                 auth_required = FALSE;
121
122         if (auth_required) {
123                 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
124
125                 if (basic_allowed) {
126                         soup_message_headers_append (msg->response_headers,
127                                                      "WWW-Authenticate",
128                                                      "Basic realm=\"ntlm-test\"");
129                 }
130
131                 if (state == NTLM_RECEIVED_REQUEST) {
132                         soup_message_headers_append (msg->response_headers,
133                                                      "WWW-Authenticate",
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");
141                 }
142         } else {
143                 if (not_found)
144                         soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
145                 else {
146                         soup_message_set_response (msg, "text/plain",
147                                                    SOUP_MEMORY_STATIC,
148                                                    "OK\r\n", 4);
149                         soup_message_set_status (msg, SOUP_STATUS_OK);
150                 }
151         }
152
153         g_hash_table_insert (connections, socket, GINT_TO_POINTER (state));
154         g_object_weak_ref (G_OBJECT (socket), clear_state, connections);
155 }
156
157 static void
158 authenticate (SoupSession *session, SoupMessage *msg,
159               SoupAuth *auth, gboolean retrying, gpointer user)
160 {
161         soup_auth_authenticate (auth, user, "password");
162 }
163
164 typedef struct {
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;
171 } NTLMState;
172
173 static void
174 prompt_check (SoupMessage *msg, gpointer user_data)
175 {
176         NTLMState *state = user_data;
177         const char *header;
178
179         header = soup_message_headers_get_list (msg->response_headers,
180                                                 "WWW-Authenticate");
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;
187         }
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 = use_ntlm && !strcmp (user, "alice");
337         gboolean bob = use_ntlm && !strcmp (user, "bob");
338
339         g_return_if_fail (use_ntlm || !alice);
340
341         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
342         if (use_ntlm)
343                 soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
344
345         if (user) {
346                 g_signal_connect (session, "authenticate",
347                                   G_CALLBACK (authenticate), (char *)user);
348         }
349
350         do_message (session, base_uri, "/noauth",
351                     FALSE, use_ntlm,
352                     FALSE, FALSE,
353                     SOUP_STATUS_OK);
354         do_message (session, base_uri, "/alice",
355                     !use_ntlm || bob, FALSE,
356                     FALSE, FALSE,
357                     alice ? SOUP_STATUS_OK :
358                     SOUP_STATUS_UNAUTHORIZED);
359         do_message (session, base_uri, "/alice/404",
360                     !use_ntlm, bob,
361                     FALSE, FALSE,
362                     alice ? SOUP_STATUS_NOT_FOUND :
363                     SOUP_STATUS_UNAUTHORIZED);
364         do_message (session, base_uri, "/alice",
365                     !use_ntlm, bob,
366                     FALSE, FALSE,
367                     alice ? SOUP_STATUS_OK :
368                     SOUP_STATUS_UNAUTHORIZED);
369         do_message (session, base_uri, "/bob",
370                     !use_ntlm || alice, bob,
371                     FALSE, FALSE,
372                     bob ? SOUP_STATUS_OK :
373                     SOUP_STATUS_UNAUTHORIZED);
374         do_message (session, base_uri, "/alice",
375                     !use_ntlm || bob, alice,
376                     FALSE, FALSE,
377                     alice ? SOUP_STATUS_OK :
378                     SOUP_STATUS_UNAUTHORIZED);
379         do_message (session, base_uri, "/basic",
380                     FALSE, bob,
381                     TRUE, user != NULL,
382                     user != NULL ? SOUP_STATUS_OK :
383                     SOUP_STATUS_UNAUTHORIZED);
384         do_message (session, base_uri, "/either",
385                     !use_ntlm, FALSE,
386                     !use_ntlm, !use_ntlm && user != NULL,
387                     user != NULL ? SOUP_STATUS_OK :
388                     SOUP_STATUS_UNAUTHORIZED);
389
390         soup_test_session_abort_unref (session);
391 }
392
393 static void
394 do_ntlm_tests (SoupURI *base_uri)
395 {
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");
404 }
405
406 int
407 main (int argc, char **argv)
408 {
409         GMainLoop *loop;
410         SoupServer *server;
411         GHashTable *connections;
412         SoupURI *uri;
413
414         test_init (argc, argv, NULL);
415
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);
420
421         loop = g_main_loop_new (NULL, TRUE);
422
423         uri = soup_uri_new ("http://127.0.0.1/");
424         soup_uri_set_port (uri, soup_server_get_port (server));
425         do_ntlm_tests (uri);
426         soup_uri_free (uri);
427
428         g_main_loop_unref (loop);
429
430         soup_test_server_quit_unref (server);
431         test_cleanup ();
432         g_hash_table_destroy (connections);
433
434         return errors != 0;
435 }