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