Finalize a SoupMD5Context and write out the digest in hex digits.
[platform/upstream/libsoup.git] / libsoup / soup-auth-digest.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-digest-offset: 8 -*- */
2 /*
3  * soup-auth-digest.c: HTTP Digest Authentication
4  *
5  * Copyright (C) 2001-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <stdio.h>
13 #include <string.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 #include "soup-auth-digest.h"
18 #include "soup-headers.h"
19 #include "soup-md5-utils.h"
20 #include "soup-message.h"
21 #include "soup-misc.h"
22 #include "soup-uri.h"
23
24 static void construct (SoupAuth *auth, const char *header);
25 static GSList *get_protection_space (SoupAuth *auth, const SoupUri *source_uri);
26 static const char *get_realm (SoupAuth *auth);
27 static void authenticate (SoupAuth *auth, const char *username, const char *password);
28 static gboolean is_authenticated (SoupAuth *auth);
29 static char *get_authorization (SoupAuth *auth, SoupMessage *msg);
30
31 typedef enum {
32         QOP_NONE     = 0,
33         QOP_AUTH     = 1 << 0,
34         QOP_AUTH_INT = 1 << 1
35 } QOPType;
36
37 typedef enum {
38         ALGORITHM_MD5      = 1 << 0,
39         ALGORITHM_MD5_SESS = 1 << 1
40 } AlgorithmType;
41
42 typedef struct {
43         char          *user;
44         guchar         hex_a1[33];
45
46         /* These are provided by the server */
47         char          *realm;
48         char          *nonce;
49         QOPType        qop_options;
50         AlgorithmType  algorithm;
51         char          *domain;
52
53         /* These are generated by the client */
54         char          *cnonce;
55         int            nc;
56         QOPType        qop;
57 } SoupAuthDigestPrivate;
58 #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate))
59
60 G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
61
62 static void
63 soup_auth_digest_init (SoupAuthDigest *digest)
64 {
65 }
66
67 static void
68 finalize (GObject *object)
69 {
70         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (object);
71
72         if (priv->user)
73                 g_free (priv->user);
74         if (priv->realm)
75                 g_free (priv->realm);
76         if (priv->nonce)
77                 g_free (priv->nonce);
78         if (priv->domain)
79                 g_free (priv->domain);
80         if (priv->cnonce)
81                 g_free (priv->cnonce);
82
83         G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
84 }
85
86 static void
87 soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
88 {
89         SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
90         GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
91
92         g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate));
93
94         auth_class->scheme_name = "Digest";
95
96         auth_class->get_protection_space = get_protection_space;
97         auth_class->get_realm = get_realm;
98         auth_class->construct = construct;
99         auth_class->authenticate = authenticate;
100         auth_class->is_authenticated = is_authenticated;
101         auth_class->get_authorization = get_authorization;
102
103         object_class->finalize = finalize;
104 }
105
106 typedef struct {
107         char *name;
108         guint type;
109 } DataType;
110
111 static DataType qop_types[] = {
112         { "auth",     QOP_AUTH     },
113         { "auth-int", QOP_AUTH_INT }
114 };
115
116 static DataType algorithm_types[] = {
117         { "MD5",      ALGORITHM_MD5      },
118         { "MD5-sess", ALGORITHM_MD5_SESS }
119 };
120
121 static guint
122 decode_data_type (DataType *dtype, const char *name)
123 {
124         int i;
125
126         if (!name)
127                 return 0;
128
129         for (i = 0; dtype[i].name; i++) {
130                 if (!g_strcasecmp (dtype[i].name, name))
131                         return dtype[i].type;
132         }
133
134         return 0;
135 }
136
137 static inline guint
138 decode_qop (const char *name)
139 {
140         return decode_data_type (qop_types, name);
141 }
142
143 static inline guint
144 decode_algorithm (const char *name)
145 {
146         return decode_data_type (algorithm_types, name);
147 }
148
149 static void
150 construct (SoupAuth *auth, const char *header)
151 {
152         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
153         GHashTable *tokens;
154         char *tmp, *ptr;
155
156         header += sizeof ("Digest");
157
158         tokens = soup_header_param_parse_list (header);
159         if (!tokens)
160                 return;
161
162         priv->nc = 1;
163         /* We're just going to do qop=auth for now */
164         priv->qop = QOP_AUTH;
165
166         priv->realm = soup_header_param_copy_token (tokens, "realm");
167         priv->domain = soup_header_param_copy_token (tokens, "domain");
168         priv->nonce = soup_header_param_copy_token (tokens, "nonce");
169
170         tmp = soup_header_param_copy_token (tokens, "qop");
171         ptr = tmp;
172
173         while (ptr && *ptr) {
174                 char *token;
175
176                 token = soup_header_param_decode_token ((char **)&ptr);
177                 if (token)
178                         priv->qop_options |= decode_qop (token);
179                 g_free (token);
180
181                 if (*ptr == ',')
182                         ptr++;
183         }
184         g_free (tmp);
185
186         tmp = soup_header_param_copy_token (tokens, "algorithm");
187         priv->algorithm = decode_algorithm (tmp);
188         g_free (tmp);
189
190         soup_header_param_destroy_hash (tokens);
191 }
192
193 static GSList *
194 get_protection_space (SoupAuth *auth, const SoupUri *source_uri)
195 {
196         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
197         GSList *space = NULL;
198         SoupUri *uri;
199         char **dvec, *d, *dir, *slash;
200         int dix;
201
202         if (!priv->domain || !*priv->domain) {
203                 /* If no domain directive, the protection space is the
204                  * whole server.
205                  */
206                 return g_slist_prepend (NULL, g_strdup (""));
207         }
208
209         dvec = g_strsplit (priv->domain, " ", 0);
210         for (dix = 0; dvec[dix] != NULL; dix++) {
211                 d = dvec[dix];
212                 if (*d == '/')
213                         dir = g_strdup (d);
214                 else {
215                         uri = soup_uri_new (d);
216                         if (uri && uri->protocol == source_uri->protocol &&
217                             uri->port == source_uri->port &&
218                             !strcmp (uri->host, source_uri->host))
219                                 dir = g_strdup (uri->path);
220                         else
221                                 dir = NULL;
222                         if (uri)
223                                 soup_uri_free (uri);
224                 }
225
226                 if (dir) {
227                         slash = strrchr (dir, '/');
228                         if (slash && !slash[1])
229                                 *slash = '\0';
230
231                         space = g_slist_prepend (space, dir);
232                 }
233         }
234         g_strfreev (dvec);
235
236         return space;
237 }
238
239 static const char *
240 get_realm (SoupAuth *auth)
241 {
242         return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->realm;
243 }
244
245 static void
246 authenticate (SoupAuth *auth, const char *username, const char *password)
247 {
248         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
249         SoupMD5Context ctx;
250         guchar d[16];
251         char *bgen;
252
253         g_return_if_fail (username != NULL);
254
255         bgen = g_strdup_printf ("%p:%lu:%lu",
256                                 auth,
257                                 (unsigned long) getpid (),
258                                 (unsigned long) time (0));
259         priv->cnonce = soup_base64_encode (bgen, strlen (bgen));
260         g_free (bgen);
261
262         priv->user = g_strdup (username);
263
264         /* compute A1 */
265         soup_md5_init (&ctx);
266
267         soup_md5_update (&ctx, username, strlen (username));
268
269         soup_md5_update (&ctx, ":", 1);
270         if (priv->realm) {
271                 soup_md5_update (&ctx, priv->realm,
272                                  strlen (priv->realm));
273         }
274
275         soup_md5_update (&ctx, ":", 1);
276         if (password)
277                 soup_md5_update (&ctx, password, strlen (password));
278
279         if (priv->algorithm == ALGORITHM_MD5_SESS) {
280                 soup_md5_final (&ctx, d);
281
282                 soup_md5_init (&ctx);
283                 soup_md5_update (&ctx, d, 16);
284                 soup_md5_update (&ctx, ":", 1);
285                 soup_md5_update (&ctx, priv->nonce,
286                                  strlen (priv->nonce));
287                 soup_md5_update (&ctx, ":", 1);
288                 soup_md5_update (&ctx, priv->cnonce,
289                                  strlen (priv->cnonce));
290         }
291
292         /* hexify A1 */
293         soup_md5_final_hex (&ctx, priv->hex_a1);
294 }
295
296 static gboolean
297 is_authenticated (SoupAuth *auth)
298 {
299         return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL;
300 }
301
302 static char *
303 compute_response (SoupAuthDigestPrivate *priv, SoupMessage *msg)
304 {
305         guchar hex_a2[33], o[33];
306         SoupMD5Context md5;
307         char *url;
308         const SoupUri *uri;
309
310         uri = soup_message_get_uri (msg);
311         g_return_val_if_fail (uri != NULL, NULL);
312         url = soup_uri_to_string (uri, TRUE);
313
314         /* compute A2 */
315         soup_md5_init (&md5);
316         soup_md5_update (&md5, msg->method, strlen (msg->method));
317         soup_md5_update (&md5, ":", 1);
318         soup_md5_update (&md5, url, strlen (url));
319
320         g_free (url);
321
322         if (priv->qop == QOP_AUTH_INT) {
323                 /* FIXME: Actually implement. Ugh. */
324                 soup_md5_update (&md5, ":", 1);
325                 soup_md5_update (&md5, "00000000000000000000000000000000", 32);
326         }
327
328         /* now hexify A2 */
329         soup_md5_final_hex (&md5, hex_a2);
330
331         /* compute KD */
332         soup_md5_init (&md5);
333         soup_md5_update (&md5, priv->hex_a1, 32);
334         soup_md5_update (&md5, ":", 1);
335         soup_md5_update (&md5, priv->nonce,
336                          strlen (priv->nonce));
337         soup_md5_update (&md5, ":", 1);
338
339         if (priv->qop) {
340                 char *tmp;
341
342                 tmp = g_strdup_printf ("%.8x", priv->nc);
343
344                 soup_md5_update (&md5, tmp, strlen (tmp));
345                 g_free (tmp);
346                 soup_md5_update (&md5, ":", 1);
347                 soup_md5_update (&md5, priv->cnonce,
348                                  strlen (priv->cnonce));
349                 soup_md5_update (&md5, ":", 1);
350
351                 if (priv->qop == QOP_AUTH)
352                         tmp = "auth";
353                 else if (priv->qop == QOP_AUTH_INT)
354                         tmp = "auth-int";
355                 else
356                         g_assert_not_reached ();
357
358                 soup_md5_update (&md5, tmp, strlen (tmp));
359                 soup_md5_update (&md5, ":", 1);
360         }
361
362         soup_md5_update (&md5, hex_a2, 32);
363         soup_md5_final_hex (&md5, o);
364
365         return g_strdup (o);
366 }
367
368 static char *
369 get_authorization (SoupAuth *auth, SoupMessage *msg)
370 {
371         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
372         char *response;
373         char *qop = NULL;
374         char *nc;
375         char *url;
376         char *out;
377         const SoupUri *uri;
378
379         uri = soup_message_get_uri (msg);
380         g_return_val_if_fail (uri != NULL, NULL);
381         url = soup_uri_to_string (uri, TRUE);
382
383         response = compute_response (priv, msg);
384
385         if (priv->qop == QOP_AUTH)
386                 qop = "auth";
387         else if (priv->qop == QOP_AUTH_INT)
388                 qop = "auth-int";
389         else
390                 g_assert_not_reached ();
391
392         nc = g_strdup_printf ("%.8x", priv->nc);
393
394         out = g_strdup_printf (
395                 "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", %s%s%s "
396                 "%s%s%s %s%s%s uri=\"%s\", response=\"%s\"",
397                 priv->user,
398                 priv->realm,
399                 priv->nonce,
400
401                 priv->qop ? "cnonce=\"" : "",
402                 priv->qop ? priv->cnonce : "",
403                 priv->qop ? "\"," : "",
404
405                 priv->qop ? "nc=" : "",
406                 priv->qop ? nc : "",
407                 priv->qop ? "," : "",
408
409                 priv->qop ? "qop=" : "",
410                 priv->qop ? qop : "",
411                 priv->qop ? "," : "",
412
413                 url,
414                 response);
415
416         g_free (response);
417         g_free (url);
418         g_free (nc);
419
420         priv->nc++;
421
422         return out;
423 }