soup-auth-digest: fix a leak on re-auth
[platform/upstream/libsoup.git] / libsoup / soup-auth-digest.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-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 <string.h>
13
14 #include "soup-auth-digest.h"
15 #include "soup.h"
16 #include "soup-message-private.h"
17
18 #ifdef G_OS_WIN32
19 #include <process.h>
20 #endif
21
22 typedef struct {
23         char                    *user;
24         char                     hex_urp[33];
25         char                     hex_a1[33];
26
27         /* These are provided by the server */
28         char                    *nonce;
29         char                    *opaque;
30         SoupAuthDigestQop        qop_options;
31         SoupAuthDigestAlgorithm  algorithm;
32         char                    *domain;
33
34         /* These are generated by the client */
35         char                    *cnonce;
36         int                      nc;
37         SoupAuthDigestQop        qop;
38 } SoupAuthDigestPrivate;
39 #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate))
40
41 static void recompute_hex_a1 (SoupAuthDigestPrivate *priv);
42
43 /**
44  * SOUP_TYPE_AUTH_DIGEST:
45  *
46  * A #GType corresponding to HTTP "Digest" authentication.
47  * #SoupSessions support this by default; if you want to disable
48  * support for it, call soup_session_remove_feature_by_type(),
49  * passing %SOUP_TYPE_AUTH_DIGEST.
50  *
51  * Since: 2.34
52  */
53
54 G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
55
56 static void
57 soup_auth_digest_init (SoupAuthDigest *digest)
58 {
59 }
60
61 static void
62 soup_auth_digest_finalize (GObject *object)
63 {
64         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (object);
65
66         g_free (priv->user);
67         g_free (priv->nonce);
68         g_free (priv->domain);
69         g_free (priv->cnonce);
70
71         memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
72         memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
73
74         G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
75 }
76
77 SoupAuthDigestAlgorithm
78 soup_auth_digest_parse_algorithm (const char *algorithm)
79 {
80         if (!algorithm || !g_ascii_strcasecmp (algorithm, "MD5"))
81                 return SOUP_AUTH_DIGEST_ALGORITHM_MD5;
82         else if (!g_ascii_strcasecmp (algorithm, "MD5-sess"))
83                 return SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS;
84         else
85                 return -1;
86 }
87
88 char *
89 soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm)
90 {
91         if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5)
92                 return g_strdup ("MD5");
93         else if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS)
94                 return g_strdup ("MD5-sess");
95         else
96                 return NULL;
97 }
98
99 SoupAuthDigestQop
100 soup_auth_digest_parse_qop (const char *qop)
101 {
102         GSList *qop_values, *iter;
103         SoupAuthDigestQop out = 0;
104
105         g_return_val_if_fail (qop != NULL, 0);
106
107         qop_values = soup_header_parse_list (qop);
108         for (iter = qop_values; iter; iter = iter->next) {
109                 if (!g_ascii_strcasecmp (iter->data, "auth"))
110                         out |= SOUP_AUTH_DIGEST_QOP_AUTH;
111                 else if (!g_ascii_strcasecmp (iter->data, "auth-int"))
112                         out |= SOUP_AUTH_DIGEST_QOP_AUTH_INT;
113         }
114         soup_header_free_list (qop_values);
115
116         return out;
117 }
118
119 char *
120 soup_auth_digest_get_qop (SoupAuthDigestQop qop)
121 {
122         GString *out;
123
124         out = g_string_new (NULL);
125         if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
126                 g_string_append (out, "auth");
127         if (qop & SOUP_AUTH_DIGEST_QOP_AUTH_INT) {
128                 if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
129                         g_string_append (out, ",");
130                 g_string_append (out, "auth-int");
131         }
132
133         return g_string_free (out, FALSE);
134 }
135
136 static gboolean
137 soup_auth_digest_update (SoupAuth *auth, SoupMessage *msg,
138                          GHashTable *auth_params)
139 {
140         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
141         const char *stale, *qop;
142         guint qop_options;
143         gboolean ok = TRUE;
144
145         g_free (priv->domain);
146         g_free (priv->nonce);
147         g_free (priv->opaque);
148
149         priv->nc = 1;
150
151         priv->domain = g_strdup (g_hash_table_lookup (auth_params, "domain"));
152         priv->nonce = g_strdup (g_hash_table_lookup (auth_params, "nonce"));
153         priv->opaque = g_strdup (g_hash_table_lookup (auth_params, "opaque"));
154
155         qop = g_hash_table_lookup (auth_params, "qop");
156         if (qop) {
157                 qop_options = soup_auth_digest_parse_qop (qop);
158                 /* We only support auth */
159                 if (!(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH))
160                         ok = FALSE;
161                 priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH;
162         } else
163                 priv->qop = 0;
164
165         priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm"));
166         if (priv->algorithm == -1)
167                 ok = FALSE;
168
169         stale = g_hash_table_lookup (auth_params, "stale");
170         if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp)
171                 recompute_hex_a1 (priv);
172         else {
173                 g_free (priv->user);
174                 priv->user = NULL;
175                 g_free (priv->cnonce);
176                 priv->cnonce = NULL;
177                 memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
178                 memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
179         }
180
181         return ok;
182 }
183
184 static GSList *
185 soup_auth_digest_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
186 {
187         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
188         GSList *space = NULL;
189         SoupURI *uri;
190         char **dvec, *d, *dir, *slash;
191         int dix;
192
193         if (!priv->domain || !*priv->domain) {
194                 /* If no domain directive, the protection space is the
195                  * whole server.
196                  */
197                 return g_slist_prepend (NULL, g_strdup (""));
198         }
199
200         dvec = g_strsplit (priv->domain, " ", 0);
201         for (dix = 0; dvec[dix] != NULL; dix++) {
202                 d = dvec[dix];
203                 if (*d == '/')
204                         dir = g_strdup (d);
205                 else {
206                         uri = soup_uri_new (d);
207                         if (uri && uri->scheme == source_uri->scheme &&
208                             uri->port == source_uri->port &&
209                             !strcmp (uri->host, source_uri->host))
210                                 dir = g_strdup (uri->path);
211                         else
212                                 dir = NULL;
213                         if (uri)
214                                 soup_uri_free (uri);
215                 }
216
217                 if (dir) {
218                         slash = strrchr (dir, '/');
219                         if (slash && !slash[1])
220                                 *slash = '\0';
221
222                         space = g_slist_prepend (space, dir);
223                 }
224         }
225         g_strfreev (dvec);
226
227         return space;
228 }
229
230 void
231 soup_auth_digest_compute_hex_urp (const char *username,
232                                   const char *realm,
233                                   const char *password,
234                                   char        hex_urp[33])
235 {
236         GChecksum *checksum;
237
238         checksum = g_checksum_new (G_CHECKSUM_MD5);
239         g_checksum_update (checksum, (guchar *)username, strlen (username));
240         g_checksum_update (checksum, (guchar *)":", 1);
241         g_checksum_update (checksum, (guchar *)realm, strlen (realm));
242         g_checksum_update (checksum, (guchar *)":", 1);
243         g_checksum_update (checksum, (guchar *)password, strlen (password));
244         strncpy (hex_urp, g_checksum_get_string (checksum), 33);
245         g_checksum_free (checksum);
246 }
247
248 void
249 soup_auth_digest_compute_hex_a1 (const char              *hex_urp,
250                                  SoupAuthDigestAlgorithm  algorithm,
251                                  const char              *nonce,
252                                  const char              *cnonce,
253                                  char                     hex_a1[33])
254 {
255         if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) {
256                 /* In MD5, A1 is just user:realm:password, so hex_A1
257                  * is just hex_urp.
258                  */
259                 /* You'd think you could say "sizeof (hex_a1)" here,
260                  * but you'd be wrong.
261                  */
262                 memcpy (hex_a1, hex_urp, 33);
263         } else {
264                 GChecksum *checksum;
265
266                 /* In MD5-sess, A1 is hex_urp:nonce:cnonce */
267
268                 checksum = g_checksum_new (G_CHECKSUM_MD5);
269                 g_checksum_update (checksum, (guchar *)hex_urp, strlen (hex_urp));
270                 g_checksum_update (checksum, (guchar *)":", 1);
271                 g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
272                 g_checksum_update (checksum, (guchar *)":", 1);
273                 g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
274                 strncpy (hex_a1, g_checksum_get_string (checksum), 33);
275                 g_checksum_free (checksum);
276         }
277 }
278
279 static void
280 recompute_hex_a1 (SoupAuthDigestPrivate *priv)
281 {
282         soup_auth_digest_compute_hex_a1 (priv->hex_urp,
283                                          priv->algorithm,
284                                          priv->nonce,
285                                          priv->cnonce,
286                                          priv->hex_a1);
287 }
288
289 static void
290 soup_auth_digest_authenticate (SoupAuth *auth, const char *username,
291                                const char *password)
292 {
293         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
294         char *bgen;
295
296         g_clear_pointer (&priv->cnonce, g_free);
297         g_clear_pointer (&priv->user, g_free);
298
299         /* Create client nonce */
300         bgen = g_strdup_printf ("%p:%lu:%lu",
301                                 auth,
302                                 (unsigned long) getpid (),
303                                 (unsigned long) time (0));
304         priv->cnonce = g_base64_encode ((guchar *)bgen, strlen (bgen));
305         g_free (bgen);
306
307         priv->user = g_strdup (username);
308
309         /* compute "URP" (user:realm:password) */
310         soup_auth_digest_compute_hex_urp (username, auth->realm,
311                                           password ? password : "",
312                                           priv->hex_urp);
313
314         /* And compute A1 from that */
315         recompute_hex_a1 (priv);
316 }
317
318 static gboolean
319 soup_auth_digest_is_authenticated (SoupAuth *auth)
320 {
321         return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL;
322 }
323
324 void
325 soup_auth_digest_compute_response (const char        *method,
326                                    const char        *uri,
327                                    const char        *hex_a1,
328                                    SoupAuthDigestQop  qop,
329                                    const char        *nonce,
330                                    const char        *cnonce,
331                                    int                nc,
332                                    char               response[33])
333 {
334         char hex_a2[33];
335         GChecksum *checksum;
336
337         /* compute A2 */
338         checksum = g_checksum_new (G_CHECKSUM_MD5);
339         g_checksum_update (checksum, (guchar *)method, strlen (method));
340         g_checksum_update (checksum, (guchar *)":", 1);
341         g_checksum_update (checksum, (guchar *)uri, strlen (uri));
342         strncpy (hex_a2, g_checksum_get_string (checksum), 33);
343         g_checksum_free (checksum);
344
345         /* compute KD */
346         checksum = g_checksum_new (G_CHECKSUM_MD5);
347         g_checksum_update (checksum, (guchar *)hex_a1, strlen (hex_a1));
348         g_checksum_update (checksum, (guchar *)":", 1);
349         g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
350         g_checksum_update (checksum, (guchar *)":", 1);
351
352         if (qop) {
353                 char tmp[9];
354
355                 g_snprintf (tmp, 9, "%.8x", nc);
356                 g_checksum_update (checksum, (guchar *)tmp, strlen (tmp));
357                 g_checksum_update (checksum, (guchar *)":", 1);
358                 g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
359                 g_checksum_update (checksum, (guchar *)":", 1);
360
361                 if (!(qop & SOUP_AUTH_DIGEST_QOP_AUTH))
362                         g_warn_if_reached ();
363                 g_checksum_update (checksum, (guchar *)"auth", strlen ("auth"));
364                 g_checksum_update (checksum, (guchar *)":", 1);
365         }
366
367         g_checksum_update (checksum, (guchar *)hex_a2, 32);
368         strncpy (response, g_checksum_get_string (checksum), 33);
369         g_checksum_free (checksum);
370 }
371
372 static void
373 authentication_info_cb (SoupMessage *msg, gpointer data)
374 {
375         SoupAuth *auth = data;
376         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
377         const char *header;
378         GHashTable *auth_params;
379         char *nextnonce;
380
381         if (auth != soup_message_get_auth (msg))
382                 return;
383
384         header = soup_message_headers_get_one (msg->response_headers,
385                                                soup_auth_is_for_proxy (auth) ?
386                                                "Proxy-Authentication-Info" :
387                                                "Authentication-Info");
388         g_return_if_fail (header != NULL);
389
390         auth_params = soup_header_parse_param_list (header);
391         if (!auth_params)
392                 return;
393
394         nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce"));
395         if (nextnonce) {
396                 g_free (priv->nonce);
397                 priv->nonce = nextnonce;
398         }
399
400         soup_header_free_param_list (auth_params);
401 }
402
403 static char *
404 soup_auth_digest_get_authorization (SoupAuth *auth, SoupMessage *msg)
405 {
406         SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
407         char response[33], *token;
408         char *url, *algorithm;
409         GString *out;
410         SoupURI *uri;
411
412         uri = soup_message_get_uri (msg);
413         g_return_val_if_fail (uri != NULL, NULL);
414         url = soup_uri_to_string (uri, TRUE);
415
416         soup_auth_digest_compute_response (msg->method, url, priv->hex_a1,
417                                            priv->qop, priv->nonce,
418                                            priv->cnonce, priv->nc,
419                                            response);
420
421         out = g_string_new ("Digest ");
422
423         soup_header_g_string_append_param_quoted (out, "username", priv->user);
424         g_string_append (out, ", ");
425         soup_header_g_string_append_param_quoted (out, "realm", auth->realm);
426         g_string_append (out, ", ");
427         soup_header_g_string_append_param_quoted (out, "nonce", priv->nonce);
428         g_string_append (out, ", ");
429         soup_header_g_string_append_param_quoted (out, "uri", url);
430         g_string_append (out, ", ");
431         algorithm = soup_auth_digest_get_algorithm (priv->algorithm);
432         g_string_append_printf (out, "algorithm=%s", algorithm);
433         g_free (algorithm);
434         g_string_append (out, ", ");
435         soup_header_g_string_append_param_quoted (out, "response", response);
436
437         if (priv->opaque) {
438                 g_string_append (out, ", ");
439                 soup_header_g_string_append_param_quoted (out, "opaque", priv->opaque);
440         }
441
442         if (priv->qop) {
443                 char *qop = soup_auth_digest_get_qop (priv->qop);
444
445                 g_string_append (out, ", ");
446                 soup_header_g_string_append_param_quoted (out, "cnonce", priv->cnonce);
447                 g_string_append_printf (out, ", nc=%.8x, qop=%s",
448                                         priv->nc, qop);
449                 g_free (qop);
450         }
451
452         g_free (url);
453
454         priv->nc++;
455
456         token = g_string_free (out, FALSE);
457
458         soup_message_add_header_handler (msg,
459                                          "got_headers",
460                                          soup_auth_is_for_proxy (auth) ?
461                                          "Proxy-Authentication-Info" :
462                                          "Authentication-Info",
463                                          G_CALLBACK (authentication_info_cb),
464                                          auth);
465         return token;
466 }
467
468 static void
469 soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
470 {
471         SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
472         GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
473
474         g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate));
475
476         auth_class->scheme_name = "Digest";
477         auth_class->strength = 5;
478
479         auth_class->get_protection_space = soup_auth_digest_get_protection_space;
480         auth_class->update = soup_auth_digest_update;
481         auth_class->authenticate = soup_auth_digest_authenticate;
482         auth_class->is_authenticated = soup_auth_digest_is_authenticated;
483         auth_class->get_authorization = soup_auth_digest_get_authorization;
484
485         object_class->finalize = soup_auth_digest_finalize;
486 }