ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / http-authn.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* http-authn.c - Basic authentication handling for HTTP
3
4    Copyright (C) 2001 Eazel, Inc.
5
6    The Gnome Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The Gnome Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the Gnome Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.
20
21    Authors: 
22                  Michael Fleming <mfleming@eazel.com>
23 */
24
25 #include <config.h>
26 #include "http-authn.h"
27
28 #include "http-method.h"
29 #include <glib/ghash.h>
30 #include <glib/gmessages.h>
31 #include <glib/gstring.h>
32 #include <glib/gstrfuncs.h>
33 #include <glib/gthread.h>
34 #include <libgnomevfs/gnome-vfs-method.h>
35 #include <libgnomevfs/gnome-vfs-utils.h>
36 #include <stdio.h>
37 #include <string.h>
38
39 /* Authentication session information
40  * Key: host:port/path
41  * Value: Header line including <cr><lf>
42  */
43
44 static GHashTable * gl_authn_table = NULL;
45 static GMutex *gl_mutex = NULL;
46
47 void
48 http_authn_init (void)
49 {
50         gl_authn_table = g_hash_table_new (g_str_hash, g_str_equal);
51         gl_mutex = g_mutex_new ();
52 }
53
54 static void /* GHFunc */
55 hfunc_free_string (gpointer key, gpointer value, gpointer user_data)
56 {
57         g_free (key);
58         g_free (value);
59 }
60
61 void
62 http_authn_shutdown (void)
63 {
64         g_hash_table_foreach (gl_authn_table, hfunc_free_string, NULL);
65         g_hash_table_destroy (gl_authn_table);
66         gl_authn_table = NULL;
67
68         g_mutex_free (gl_mutex);
69         gl_mutex = NULL;
70 }
71
72 /* watch it here: # and ? may be legal in the authority field of a URI
73  * (eg: username:password)
74  * so don't feed the authority field in here
75  */
76 static char *
77 strip_uri_query_and_fragment (const char *in)
78 {
79         char *ret;
80         char *separator;
81
82         ret = g_strdup (in);
83
84         separator = strchr (ret, '#'); 
85
86         if (separator) {
87                 *separator = '\0';
88         }
89
90         separator = strchr (ret, '?');
91
92         if (separator) {
93                 *separator = '\0';
94         }
95
96         ret = g_realloc (ret, strlen (ret) + 1);
97         return ret;
98 }
99
100 static char *
101 http_authn_get_key_string_from_uri (GnomeVFSURI *uri)
102 {
103         size_t len;
104         char *uri_string;
105         char *uri_string_canonical;
106         char *ret;
107
108         uri_string = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_USER_NAME 
109                                                    | GNOME_VFS_URI_HIDE_PASSWORD 
110                                                    | GNOME_VFS_URI_HIDE_FRAGMENT_IDENTIFIER);
111
112         uri_string_canonical = gnome_vfs_make_uri_canonical (uri_string);
113         
114         ret = strip_uri_query_and_fragment (uri_string_canonical);
115
116         /* strip any trailing / */
117         len = strlen (ret);
118         if (ret [len-1] == '/') {
119                 ret [len-1] = '\0';
120         }
121
122         g_free (uri_string_canonical);
123         uri_string_canonical = NULL;
124         g_free (uri_string);
125         uri_string = NULL;
126
127         return ret;
128 }
129
130 char *
131 http_authn_session_get_header_for_uri (GnomeVFSURI *uri)
132 {
133         char *key_string;
134         char *marker;
135         char *authn_header;
136         char *ret;
137
138         key_string = http_authn_get_key_string_from_uri (uri);
139
140         g_mutex_lock (gl_mutex);
141
142         ret = NULL;
143         while (NULL != strrchr (key_string, '/')) {
144                 authn_header = (char *) g_hash_table_lookup (gl_authn_table, key_string);
145                 if (authn_header != NULL) {
146                         ret = g_strdup (authn_header);
147                         break;
148                 }
149
150                 /* Try the next level up in the heirarchy */
151                 marker = strrchr (key_string, '/');
152                 *marker = '\0';
153         }
154
155         g_mutex_unlock (gl_mutex);
156
157         g_free (key_string);
158         key_string = NULL;
159         
160         return ret;
161 }
162
163 void
164 http_authn_session_add_credentials (GnomeVFSURI *uri, const char *username, const char *password)
165 {
166         char *key_string;
167         char *orig_key;
168         char *orig_value;
169         char *credentials;
170         char *credentials_encoded;
171         
172         g_return_if_fail (uri != NULL);
173
174         key_string = http_authn_get_key_string_from_uri (uri);
175
176         credentials = credentials_encoded = NULL;
177
178         if (username != NULL) {
179                 credentials = g_strdup_printf ("%s:%s", username, 
180                                                  password == NULL
181                                                         ? "" : password);
182                 credentials_encoded = http_util_base64 (credentials);
183         }
184
185         g_mutex_lock (gl_mutex);
186
187         if (g_hash_table_lookup_extended (gl_authn_table, key_string, (gpointer) &orig_key, (gpointer) &orig_value)) {
188                 g_hash_table_remove (gl_authn_table, orig_key);
189                 g_free (orig_key);
190                 orig_key = NULL;
191                 g_free (orig_value);
192                 orig_value = NULL;
193         }
194
195         if (credentials_encoded != NULL) {
196                 g_hash_table_insert (gl_authn_table, key_string, g_strdup_printf ("Authorization: Basic %s\r\n", credentials_encoded)); 
197                 key_string = NULL;
198         }
199         
200         g_mutex_unlock (gl_mutex);
201
202         g_free (key_string);
203         g_free (credentials);
204         g_free (credentials_encoded);
205 }
206
207
208 static gint /* GCompareFunc */
209 http_authn_glist_find_header (gconstpointer a, gconstpointer b)
210 {
211         if ( NULL != a && NULL != b) {
212                 return g_ascii_strncasecmp ( (const char *)a, (const char *)b, strlen((const char *)b));
213         } else {
214                 return -1;
215         }
216 }
217
218 /* "quoted-string" rfc 2616 section 2.1 */
219 /* tolerant of lack of quotes (switches to space-delimited) */
220 static char *
221 http_authn_parse_quoted_string (const char *in, const char **p_out)
222 {
223         gboolean quote_next;
224         gboolean space_delimited;
225         GString *unquoted;
226         char *ret;
227
228         ret = NULL;
229
230         if (p_out != NULL) {
231                 *p_out = NULL;
232         }
233
234         if (*in != '"') {
235                 space_delimited = TRUE;
236         } else {
237                 space_delimited = FALSE;
238                 in++;
239         }
240
241         quote_next = FALSE;
242         unquoted = g_string_new ("");
243         for (; *in != '\0' 
244                && (space_delimited 
245                         ? (*in != ' ' && *in != '\t') 
246                         : (*in != '"' || quote_next))
247              ; in++) {
248                 if (!quote_next && *in == '\\') {
249                         quote_next = TRUE;
250                 } else {
251                         quote_next = FALSE;
252                         g_string_append_c (unquoted, *in);
253                 }
254         }
255
256         if (p_out != NULL) {
257                 if (*in != '\0') {
258                         *p_out = in+1;
259                 } else {
260                         *p_out = in;
261                 }
262         }
263
264         ret = unquoted->str;
265         g_string_free (unquoted, FALSE);
266
267         return ret;
268 }
269
270 gboolean
271 http_authn_parse_response_header_basic (enum AuthnHeaderType type, GList *response_headers, /* OUT */ char **p_realm)
272 {
273         const char *header_name;
274         char *header;
275         const char *marker;
276         GList *node;
277         gboolean ret;
278
279         g_return_val_if_fail (p_realm != NULL, FALSE);
280
281         *p_realm = NULL;
282         ret = FALSE;
283         
284         switch (type) {
285         case AuthnHeader_WWW:
286                 header_name = "WWW-Authenticate:";
287                 break;
288         
289         case AuthnHeader_Proxy:
290                 header_name = "Proxy-Authenticate:";
291                 break;
292         default:
293                 g_return_val_if_fail (FALSE, FALSE);
294         }
295
296         /* Apparently, there can be more than one authentication header
297          * (see RFC 2617 section 1.2 paragraph beginning with "Note:"
298          */
299         node = g_list_find_custom (response_headers, (gpointer) header_name, http_authn_glist_find_header);
300         for (; node != NULL 
301              ; node = g_list_find_custom (g_list_next (node), (gpointer) header_name, http_authn_glist_find_header)) {
302
303                 header = (char *)node->data;
304
305                 /* skip through the header name */
306                 marker = strchr (header, (unsigned char)':');
307
308                 if (marker == NULL) {
309                         continue;
310                 }
311
312                 marker++;
313
314                 /* skip to the auth-scheme */
315                 for (; *marker != '\0' 
316                         && (*marker == ' ' || *marker == '\t') 
317                      ; marker++);
318
319                 if (0 != g_ascii_strncasecmp ("Basic", marker, strlen ("Basic"))) {
320                         continue;
321                 }
322
323                 marker += strlen ("Basic");
324
325                 while (*marker != '\0') {
326                         for (; *marker != '\0' 
327                                 && (*marker == ' ' || *marker == '\t' || *marker == ',') 
328                              ; marker++);
329
330                         if (0 == g_ascii_strncasecmp ("realm=", marker, strlen ("realm="))) {
331                                 marker += strlen ("realm=");
332                                 *p_realm = http_authn_parse_quoted_string (marker, &marker);
333                                 break;
334                         }
335                 }
336
337                 if (*p_realm == NULL) {
338                         *p_realm = strdup ("");
339                 }
340
341                 ret = TRUE;
342                 break;
343         }
344
345         return ret;
346 }
347
348
349 char *
350 http_authn_get_header_for_uri (GnomeVFSURI *uri)
351 {
352         char *result;
353         GnomeVFSToplevelURI *toplevel_uri;
354
355         toplevel_uri = gnome_vfs_uri_get_toplevel (uri);
356
357         result = NULL;
358         
359         /* If authn info was passed in the URI, then default to that */
360         if(toplevel_uri != NULL && toplevel_uri->user_name) {
361                 gchar *raw;
362                 gchar *enc;
363
364                 raw = g_strdup_printf("%s:%s", toplevel_uri->user_name,
365                                 toplevel_uri->password?toplevel_uri->password:"");
366
367                 enc = http_util_base64(raw);
368                 
369                 result = g_strdup_printf("Authorization: Basic %s\r\n", enc);
370                 g_free(enc);
371                 g_free(raw);
372         } else {
373                 result = http_authn_session_get_header_for_uri ((GnomeVFSURI *)toplevel_uri);
374         }
375
376         return result;
377 }
378
379 #define VERIFY_STRING_RESULT(function, expected) \
380         G_STMT_START {                                                                                  \
381                 char *result = function;                                                                \
382                 if (!((result == NULL && expected == NULL)                                              \
383                       || (result != NULL && expected != NULL && strcmp (result, (char *)expected) == 0))) {     \
384                         test_failed ("%s:%s:%s: returned '%s' expected '%s'", __FILE__, __LINE__, #function, result, expected); \
385                 }                                                                                       \
386         } G_STMT_END
387
388 static gboolean at_least_one_test_failed = FALSE;
389
390 static void
391 test_failed (const char *format, ...)
392 {
393         va_list arguments;
394         char *message;
395
396         va_start (arguments, format);
397         message = g_strdup_vprintf (format, arguments);
398         va_end (arguments);
399
400         fprintf (stderr, "test failed: %s\n", message);
401         at_least_one_test_failed = TRUE;
402 }
403
404 static void
405 test_parse_header (guint line, enum AuthnHeaderType type, const char *realm_expected, gboolean result_expected, ...)
406 {
407         va_list arguments;
408         GList *header_list;
409         const char *header;
410         char *realm;
411         gboolean result;
412         
413         va_start (arguments, result_expected);
414
415         header_list = NULL;
416         
417         for (header = va_arg (arguments, const char *) 
418              ; header != NULL
419              ; header = va_arg (arguments, const char *)) {
420
421                 header_list = g_list_prepend (header_list, (gpointer)header);
422         }
423
424         header_list = g_list_reverse (header_list);
425         va_end (arguments);
426
427         result = http_authn_parse_response_header_basic (type, header_list, &realm);
428
429         if (! (result == result_expected
430                && ((realm == NULL && realm_expected == NULL) 
431                     || (realm != NULL && realm_expected != NULL 
432                         && 0 == strcmp (realm, realm_expected))))) {
433
434                 test_failed ("%s:%u:http_authn_parse_response_header_basic failed, expected (%d,%s) but got (%d, %s)\n",
435                         __FILE__, line, result_expected, realm_expected, result, realm);
436         }
437 }
438
439 /* Hook for testing code to flush credentials */
440 void
441 http_authentication_test_flush_credentials (void);
442
443 void
444 http_authentication_test_flush_credentials (void)
445 {
446         g_hash_table_foreach (gl_authn_table, hfunc_free_string, NULL);
447         g_hash_table_destroy (gl_authn_table);
448         gl_authn_table = g_hash_table_new (g_str_hash, g_str_equal);
449 }
450
451 gboolean
452 http_authn_self_test (void)
453 {
454         GnomeVFSURI *uri;
455
456         at_least_one_test_failed = FALSE;
457
458         fprintf (stderr, "self-test: http-authn\n");
459         
460         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar"), "/foo/bar");
461         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar?query"), "/foo/bar");
462         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar?query#fragment"), "/foo/bar");
463         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("/foo/bar#fragment"), "/foo/bar");
464         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("#fragment"), "");
465         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query#fragment"), "");
466         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query"), "");
467         VERIFY_STRING_RESULT (strip_uri_query_and_fragment ("?query#fragment"), "");
468
469         uri = gnome_vfs_uri_new ("http://host/path");
470         VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host/path");
471         gnome_vfs_uri_unref (uri);
472
473         /* FIXME I think make_uri_canonical should remove default ports */
474         uri = gnome_vfs_uri_new ("http://host:80/path");
475         VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
476         gnome_vfs_uri_unref (uri);
477
478         uri = gnome_vfs_uri_new ("http://host:80/path/");
479         VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
480         gnome_vfs_uri_unref (uri);
481
482         uri = gnome_vfs_uri_new ("http://user:pass@host:80/path/?query#foo");
483         VERIFY_STRING_RESULT (http_authn_get_key_string_from_uri (uri), "http://host:80/path");
484         gnome_vfs_uri_unref (uri);
485
486         VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"quoted string\"", NULL), "quoted string");
487         VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"quoted\\\"str\\\\ing\"", NULL), "quoted\"str\\ing");
488         VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("unquoted-string", NULL), "unquoted-string");
489         VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("\"\"", NULL), "");
490         VERIFY_STRING_RESULT (http_authn_parse_quoted_string ("", NULL), "");
491
492         test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Basic realm=\"realm\"", NULL);
493         test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Digest crap=\"crap\"", "WWW-Authenticate: Basic realm=\"realm\"", NULL);
494         test_parse_header (__LINE__, AuthnHeader_WWW, "realm" , TRUE, "WWW-Authenticate: Basic realm=\"realm\"", "WWW-Authenticate: Digest crap=\"crap\"", NULL);
495         test_parse_header (__LINE__, AuthnHeader_WWW, "" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"crap\"", NULL);
496         test_parse_header (__LINE__, AuthnHeader_WWW, NULL , FALSE, "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"crap\"", NULL);
497
498         test_parse_header (__LINE__, AuthnHeader_Proxy, "realm" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "Proxy-Authenticate: Basic realm=\"realm\"", NULL);
499         test_parse_header (__LINE__, AuthnHeader_Proxy, "realm" , TRUE, "WWW-Authenticate: Basic", "WWW-Authenticate: Digest crap=\"crap\"", "proxy-authenticate: basic Realm=\"realm\"", NULL);
500
501         uri = gnome_vfs_uri_new ("http://host/path/");
502
503         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
504
505         http_authn_session_add_credentials (uri, "myuser", "mypasswd");
506
507         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
508                 g_strconcat ("Authorization: Basic ", http_util_base64("myuser:mypasswd"), "\r\n", NULL));
509
510         gnome_vfs_uri_unref (uri);
511         uri = gnome_vfs_uri_new ("http://host/path/?query#foo");
512
513         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), 
514                 g_strconcat ("Authorization: Basic ", http_util_base64("myuser:mypasswd"), "\r\n", NULL));
515
516         http_authn_session_add_credentials (uri, "newuser", "newpasswd");
517
518         gnome_vfs_uri_unref (uri);
519         uri = gnome_vfs_uri_new ("http://host/path");
520
521         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
522                 g_strconcat ("Authorization: Basic ", http_util_base64("newuser:newpasswd"), "\r\n", NULL));
523
524         gnome_vfs_uri_unref (uri);
525         uri = gnome_vfs_uri_new ("http://host/");
526
527         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
528
529         gnome_vfs_uri_unref (uri);
530         uri = gnome_vfs_uri_new ("http://user:passwd@host/path");
531
532         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri),
533                 g_strconcat ("Authorization: Basic ", http_util_base64("user:passwd"), "\r\n", NULL));
534
535         gnome_vfs_uri_unref (uri);
536         uri = gnome_vfs_uri_new ("http://anotherhost/path");
537
538         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
539
540         http_authn_session_add_credentials (uri, "newuser", "newpasswd");
541         http_authn_session_add_credentials (uri, NULL, NULL);
542
543         VERIFY_STRING_RESULT (http_authn_get_header_for_uri (uri), NULL);
544
545         return !at_least_one_test_failed;
546 }