source: trunk/third/libsoup/libsoup/soup-server-auth.c @ 21108

Revision 21108, 10.3 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21107, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2/*
3 * soup-server-auth.c: Server-side authentication handling
4 *
5 * Copyright (C) 2001-2003, Ximian, Inc.
6 */
7
8#include <glib.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <time.h>
13
14#include "soup-server-auth.h"
15
16#include "soup-headers.h"
17#include "soup-md5-utils.h"
18#include "soup-misc.h"
19#include "soup-uri.h"
20
21typedef struct {
22        const gchar   *scheme;
23        SoupAuthType   type;
24        gint           strength;
25} AuthScheme;
26
27static AuthScheme known_auth_schemes [] = {
28        { "Basic",  SOUP_AUTH_TYPE_BASIC,  0 },
29        { "Digest", SOUP_AUTH_TYPE_DIGEST, 3 },
30        { NULL }
31};
32
33static SoupAuthType
34soup_auth_get_strongest_header (guint          auth_types,
35                                const GSList  *vals,
36                                gchar        **out_hdr)
37{
38        gchar *header = NULL;
39        AuthScheme *scheme = NULL, *iter;
40
41        g_return_val_if_fail (vals != NULL, 0);
42
43        if (!auth_types)
44                return 0;
45
46        while (vals) {
47                for (iter = known_auth_schemes; iter->scheme; iter++) {
48                        gchar *tryheader = vals->data;
49
50                        if ((iter->type & auth_types) &&
51                            !g_strncasecmp (tryheader,
52                                            iter->scheme,
53                                            strlen (iter->scheme))) {
54                                if (!scheme ||
55                                    scheme->strength < iter->strength) {
56                                        header = tryheader;
57                                        scheme = iter;
58                                }
59                                break;
60                        }
61                }
62
63                vals = vals->next;
64        }
65
66        if (!scheme)
67                return 0;
68
69        *out_hdr = header + strlen (scheme->scheme) + 1;
70        return scheme->type;
71}
72
73static void
74digest_hex (guchar *digest, guchar hex[33])
75{
76        guchar *s, *p;
77
78        /* lowercase hexify that bad-boy... */
79        for (s = digest, p = hex; p < hex + 32; s++, p += 2)
80                sprintf (p, "%.2x", *s);
81}
82
83static gboolean
84check_digest_passwd (SoupServerAuthDigest *digest,
85                     gchar                *passwd)
86{
87        SoupMD5Context ctx;
88        guchar d[16];
89        guchar hex_a1 [33], hex_a2[33], o[33];
90        gchar *tmp;
91
92        /* compute A1 */
93        soup_md5_init (&ctx);
94        soup_md5_update (&ctx, digest->user, strlen (digest->user));
95        soup_md5_update (&ctx, ":", 1);
96        soup_md5_update (&ctx, digest->realm, strlen (digest->realm));
97        soup_md5_update (&ctx, ":", 1);
98
99        if (passwd)
100                soup_md5_update (&ctx, passwd, strlen (passwd));
101
102        if (digest->algorithm == SOUP_ALGORITHM_MD5_SESS) {
103                soup_md5_final (&ctx, d);
104
105                soup_md5_init (&ctx);
106                soup_md5_update (&ctx, d, 16);
107                soup_md5_update (&ctx, ":", 1);
108                soup_md5_update (&ctx, digest->nonce, strlen (digest->nonce));
109                soup_md5_update (&ctx, ":", 1);
110                soup_md5_update (&ctx, digest->cnonce, strlen (digest->cnonce));
111        }
112
113        /* hexify A1 */
114        soup_md5_final (&ctx, d);
115        digest_hex (d, hex_a1);
116
117        /* compute A2 */
118        soup_md5_init (&ctx);
119        soup_md5_update (&ctx,
120                    digest->request_method,
121                    strlen (digest->request_method));
122        soup_md5_update (&ctx, ":", 1);
123        soup_md5_update (&ctx, digest->digest_uri, strlen (digest->digest_uri));
124
125        if (digest->integrity) {
126                /* FIXME: Actually implement. Ugh. */
127                soup_md5_update (&ctx, ":", 1);
128                soup_md5_update (&ctx, "00000000000000000000000000000000", 32);
129        }
130
131        /* hexify A2 */
132        soup_md5_final (&ctx, d);
133        digest_hex (d, hex_a2);
134
135        /* compute KD */
136        soup_md5_init (&ctx);
137        soup_md5_update (&ctx, hex_a1, 32);
138        soup_md5_update (&ctx, ":", 1);
139        soup_md5_update (&ctx, digest->nonce, strlen (digest->nonce));
140        soup_md5_update (&ctx, ":", 1);
141
142        tmp = g_strdup_printf ("%.8x", digest->nonce_count);
143        soup_md5_update (&ctx, tmp, strlen (tmp));
144        g_free (tmp);
145
146        soup_md5_update (&ctx, ":", 1);
147        soup_md5_update (&ctx, digest->cnonce, strlen (digest->cnonce));
148        soup_md5_update (&ctx, ":", 1);
149
150        if (digest->integrity)
151                tmp = "auth-int";
152        else
153                tmp = "auth";
154
155        soup_md5_update (&ctx, tmp, strlen (tmp));
156        soup_md5_update (&ctx, ":", 1);
157
158        soup_md5_update (&ctx, hex_a2, 32);
159        soup_md5_final (&ctx, d);
160
161        digest_hex (d, o);
162
163        return strcmp (o, digest->digest_response) == 0;
164}
165
166gboolean
167soup_server_auth_check_passwd (SoupServerAuth *auth,
168                               gchar          *passwd)
169{
170        g_return_val_if_fail (auth != NULL, TRUE);
171
172        switch (auth->type) {
173        case SOUP_AUTH_TYPE_BASIC:
174                if (passwd && auth->basic.passwd)
175                        return strcmp (auth->basic.passwd, passwd) == 0;
176                else
177                        return passwd == auth->basic.passwd;
178        case SOUP_AUTH_TYPE_DIGEST:
179                return check_digest_passwd (&auth->digest, passwd);
180        }
181
182        return FALSE;
183}
184
185const gchar *
186soup_server_auth_get_user (SoupServerAuth *auth)
187{
188        g_return_val_if_fail (auth != NULL, NULL);
189
190        switch (auth->type) {
191        case SOUP_AUTH_TYPE_BASIC:
192                return auth->basic.user;
193        case SOUP_AUTH_TYPE_DIGEST:
194                return auth->digest.user;
195        }
196
197        return NULL;
198}
199
200static gboolean
201parse_digest (SoupServerAuthContext *auth_ctx,
202              gchar                 *header,
203              SoupMessage           *msg,
204              SoupServerAuth        *out_auth)
205{
206        GHashTable *tokens;
207        gchar *user, *realm, *uri, *response;
208        gchar *nonce, *cnonce;
209        gint nonce_count;
210        gboolean integrity;
211
212        user = realm = uri = response = NULL;
213        nonce = cnonce = NULL;
214        nonce_count = 0;
215        integrity = FALSE;
216
217        tokens = soup_header_param_parse_list (header);
218        if (!tokens)
219                goto DIGEST_AUTH_FAIL;
220
221        /* Check uri */
222        {
223                SoupUri *dig_uri;
224                const SoupUri *req_uri;
225
226                uri = soup_header_param_copy_token (tokens, "uri");
227                if (!uri)
228                        goto DIGEST_AUTH_FAIL;
229
230                req_uri = soup_message_get_uri (msg);
231
232                dig_uri = soup_uri_new (uri);
233                if (dig_uri) {
234                        if (!soup_uri_equal (dig_uri, req_uri)) {
235                                soup_uri_free (dig_uri);
236                                goto DIGEST_AUTH_FAIL;
237                        }
238                        soup_uri_free (dig_uri);
239                } else {       
240                        char *req_path;
241
242                        req_path = soup_uri_to_string (req_uri, TRUE);
243                        if (strcmp (uri, req_path) != 0) {
244                                g_free (req_path);
245                                goto DIGEST_AUTH_FAIL;
246                        }
247                        g_free (req_path);
248                }
249        }
250
251        /* Check qop */
252        {
253                gchar *qop;
254                qop = soup_header_param_copy_token (tokens, "qop");
255                if (!qop)
256                        goto DIGEST_AUTH_FAIL;
257
258                if (!strcmp (qop, "auth-int")) {
259                        g_free (qop);
260                        integrity = TRUE;
261                } else if (auth_ctx->digest_info.force_integrity) {
262                        g_free (qop);
263                        goto DIGEST_AUTH_FAIL;
264                }
265        }                       
266
267        /* Check realm */
268        realm = soup_header_param_copy_token (tokens, "realm");
269        if (!realm && auth_ctx->digest_info.realm)
270                goto DIGEST_AUTH_FAIL;
271        else if (realm &&
272                 auth_ctx->digest_info.realm &&
273                 strcmp (realm, auth_ctx->digest_info.realm) != 0)
274                goto DIGEST_AUTH_FAIL;
275
276        /* Check username */
277        user = soup_header_param_copy_token (tokens, "username");
278        if (!user)
279                goto DIGEST_AUTH_FAIL;
280
281        /* Check nonce */
282        nonce = soup_header_param_copy_token (tokens, "nonce");
283        if (!nonce)
284                goto DIGEST_AUTH_FAIL;
285
286        /* Check nonce count */
287        {
288                gchar *nc;
289                nc = soup_header_param_copy_token (tokens, "nc");
290                if (!nc)
291                        goto DIGEST_AUTH_FAIL;
292
293                nonce_count = atoi (nc);
294                if (nonce_count <= 0) {
295                        g_free (nc);
296                        goto DIGEST_AUTH_FAIL;
297                }
298                g_free (nc);
299        }
300
301        cnonce = soup_header_param_copy_token (tokens, "cnonce");
302        if (!cnonce)
303                goto DIGEST_AUTH_FAIL;
304
305        response = soup_header_param_copy_token (tokens, "response");
306        if (!response)
307                goto DIGEST_AUTH_FAIL;
308
309        out_auth->digest.type            = SOUP_AUTH_TYPE_DIGEST;
310        out_auth->digest.digest_uri      = uri;
311        out_auth->digest.integrity       = integrity;
312        out_auth->digest.realm           = realm;
313        out_auth->digest.user            = user;
314        out_auth->digest.nonce           = nonce;
315        out_auth->digest.nonce_count     = nonce_count;
316        out_auth->digest.cnonce          = cnonce;
317        out_auth->digest.digest_response = response;
318        out_auth->digest.request_method  = msg->method;
319
320        soup_header_param_destroy_hash (tokens);
321
322        return TRUE;
323
324 DIGEST_AUTH_FAIL:
325        if (tokens)
326                soup_header_param_destroy_hash (tokens);
327
328        g_free (user);
329        g_free (realm);
330        g_free (nonce);
331        g_free (response);
332        g_free (cnonce);
333        g_free (uri);
334
335        return FALSE;
336}
337
338SoupServerAuth *
339soup_server_auth_new (SoupServerAuthContext *auth_ctx,
340                      const GSList          *auth_hdrs,
341                      SoupMessage           *msg)
342{
343        SoupServerAuth *ret;
344        SoupAuthType type;
345        gchar *header = NULL;
346
347        g_return_val_if_fail (auth_ctx != NULL, NULL);
348        g_return_val_if_fail (msg != NULL, NULL);
349
350        if (!auth_hdrs && auth_ctx->types) {
351                soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
352                return NULL;
353        }
354
355        type = soup_auth_get_strongest_header (auth_ctx->types,
356                                               auth_hdrs,
357                                               &header);
358
359        if (!type && auth_ctx->types) {
360                soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
361                return NULL;
362        }
363
364        ret = g_new0 (SoupServerAuth, 1);
365
366        switch (type) {
367        case SOUP_AUTH_TYPE_BASIC:
368                {
369                        gchar *userpass, *colon;
370                        gint len;
371
372                        userpass = soup_base64_decode (header, &len);
373                        if (!userpass)
374                                break;
375
376                        colon = strchr (userpass, ':');
377                        if (!colon) {
378                                g_free (userpass);
379                                break;
380                        }
381
382                        ret->basic.type = SOUP_AUTH_TYPE_BASIC;
383                        ret->basic.user = g_strndup (userpass,
384                                                     colon - userpass);
385                        ret->basic.passwd = g_strdup (colon + 1);
386
387                        g_free (userpass);
388
389                        return ret;
390                }
391        case SOUP_AUTH_TYPE_DIGEST:
392                if (parse_digest (auth_ctx, header, msg, ret))
393                        return ret;
394                break;
395        }
396
397        g_free (ret);
398
399        soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
400        return NULL;
401}
402
403void
404soup_server_auth_free (SoupServerAuth *auth)
405{
406        g_return_if_fail (auth != NULL);
407
408        switch (auth->type) {
409        case SOUP_AUTH_TYPE_BASIC:
410                g_free ((gchar *) auth->basic.user);
411                g_free ((gchar *) auth->basic.passwd);
412                break;
413        case SOUP_AUTH_TYPE_DIGEST:
414                g_free ((gchar *) auth->digest.realm);
415                g_free ((gchar *) auth->digest.user);
416                g_free ((gchar *) auth->digest.nonce);
417                g_free ((gchar *) auth->digest.cnonce);
418                g_free ((gchar *) auth->digest.digest_uri);
419                g_free ((gchar *) auth->digest.digest_response);
420                break;
421        }
422
423        g_free (auth);
424}
425
426void
427soup_server_auth_context_challenge (SoupServerAuthContext *auth_ctx,
428                                    SoupMessage           *msg,
429                                    gchar                 *header_name)
430{
431        if (auth_ctx->types & SOUP_AUTH_TYPE_BASIC) {
432                gchar *hdr;
433
434                hdr = g_strdup_printf ("Basic realm=\"%s\"",
435                                       auth_ctx->basic_info.realm);
436                soup_message_add_header (msg->response_headers,
437                                         header_name,
438                                         hdr);
439                g_free (hdr);
440        }
441
442        if (auth_ctx->types & SOUP_AUTH_TYPE_DIGEST) {
443                GString *str;
444
445                str = g_string_new ("Digest ");
446
447                if (auth_ctx->digest_info.realm)
448                        g_string_sprintfa (str,
449                                           "realm=\"%s\", ",
450                                           auth_ctx->digest_info.realm);
451
452                g_string_sprintfa (str,
453                                   "nonce=\"%lu%lu\", ",
454                                   (unsigned long) msg,
455                                   (unsigned long) time (0));
456
457                if (auth_ctx->digest_info.force_integrity)
458                        g_string_sprintfa (str, "qop=\"auth-int\", ");
459                else
460                        g_string_sprintfa (str, "qop=\"auth,auth-int\", ");
461
462                g_string_sprintfa (str, "algorithm=\"MD5,MD5-sess\"");
463
464                soup_message_add_header (msg->response_headers,
465                                         header_name,
466                                         str->str);
467                g_string_free (str, TRUE);
468        }
469}
Note: See TracBrowser for help on using the repository browser.