1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-auth-digest.c: HTTP Digest Authentication
5 * Copyright (C) 2001-2003, Ximian, Inc.
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"
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);
35 /* These are provided by the server */
38 SoupAuthDigestQop qop_options;
39 SoupAuthDigestAlgorithm algorithm;
42 /* These are generated by the client */
45 SoupAuthDigestQop qop;
46 } SoupAuthDigestPrivate;
47 #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate))
49 static void recompute_hex_a1 (SoupAuthDigestPrivate *priv);
51 G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
54 soup_auth_digest_init (SoupAuthDigest *digest)
59 finalize (GObject *object)
61 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (object);
68 g_free (priv->domain);
70 g_free (priv->cnonce);
72 memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
73 memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
75 G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
79 soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
81 SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
82 GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
84 g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate));
86 auth_class->scheme_name = "Digest";
87 auth_class->strength = 5;
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;
95 object_class->finalize = finalize;
98 SoupAuthDigestAlgorithm
99 soup_auth_digest_parse_algorithm (const char *algorithm)
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;
110 soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm)
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");
121 soup_auth_digest_parse_qop (const char *qop)
123 GSList *qop_values, *iter;
124 SoupAuthDigestQop out = 0;
126 g_return_val_if_fail (qop != NULL, 0);
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;
135 soup_header_free_list (qop_values);
141 soup_auth_digest_get_qop (SoupAuthDigestQop qop)
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");
154 return g_string_free (out, FALSE);
158 update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params)
160 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
161 const char *stale, *qop;
165 g_free (priv->domain);
166 g_free (priv->nonce);
167 g_free (priv->opaque);
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"));
175 qop = g_hash_table_lookup (auth_params, "qop");
177 qop_options = soup_auth_digest_parse_qop (qop);
178 /* We only support auth */
179 if (!(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH))
181 priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH;
185 priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm"));
186 if (priv->algorithm == -1)
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);
195 g_free (priv->cnonce);
197 memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
198 memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
205 get_protection_space (SoupAuth *auth, SoupURI *source_uri)
207 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
208 GSList *space = NULL;
210 char **dvec, *d, *dir, *slash;
213 if (!priv->domain || !*priv->domain) {
214 /* If no domain directive, the protection space is the
217 return g_slist_prepend (NULL, g_strdup (""));
220 dvec = g_strsplit (priv->domain, " ", 0);
221 for (dix = 0; dvec[dix] != NULL; dix++) {
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);
238 slash = strrchr (dir, '/');
239 if (slash && !slash[1])
242 space = g_slist_prepend (space, dir);
251 soup_auth_digest_compute_hex_urp (const char *username,
253 const char *password,
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);
269 soup_auth_digest_compute_hex_a1 (const char *hex_urp,
270 SoupAuthDigestAlgorithm algorithm,
275 if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) {
276 /* In MD5, A1 is just user:realm:password, so hex_A1
279 /* You'd think you could say "sizeof (hex_a1)" here,
280 * but you'd be wrong.
282 memcpy (hex_a1, hex_urp, 33);
286 /* In MD5-sess, A1 is hex_urp:nonce:cnonce */
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);
300 recompute_hex_a1 (SoupAuthDigestPrivate *priv)
302 soup_auth_digest_compute_hex_a1 (priv->hex_urp,
310 authenticate (SoupAuth *auth, const char *username, const char *password)
312 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
315 /* Create client nonce */
316 bgen = g_strdup_printf ("%p:%lu:%lu",
318 (unsigned long) getpid (),
319 (unsigned long) time (0));
320 priv->cnonce = g_base64_encode ((guchar *)bgen, strlen (bgen));
323 priv->user = g_strdup (username);
325 /* compute "URP" (user:realm:password) */
326 soup_auth_digest_compute_hex_urp (username, auth->realm,
327 password ? password : "",
330 /* And compute A1 from that */
331 recompute_hex_a1 (priv);
335 is_authenticated (SoupAuth *auth)
337 return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL;
341 soup_auth_digest_compute_response (const char *method,
344 SoupAuthDigestQop qop,
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);
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);
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);
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);
383 g_checksum_update (checksum, (guchar *)hex_a2, 32);
384 strncpy (response, g_checksum_get_string (checksum), 33);
385 g_checksum_free (checksum);
389 authentication_info_cb (SoupMessage *msg, gpointer data)
391 SoupAuth *auth = data;
392 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
394 GHashTable *auth_params;
397 if (auth != soup_message_get_auth (msg))
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);
406 auth_params = soup_header_parse_param_list (header);
410 nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce"));
412 g_free (priv->nonce);
413 priv->nonce = nextnonce;
416 soup_header_free_param_list (auth_params);
420 get_authorization (SoupAuth *auth, SoupMessage *msg)
422 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
423 char response[33], *token;
424 char *url, *algorithm;
428 uri = soup_message_get_uri (msg);
429 g_return_val_if_fail (uri != NULL, NULL);
430 url = soup_uri_to_string (uri, TRUE);
432 soup_auth_digest_compute_response (msg->method, url, priv->hex_a1,
433 priv->qop, priv->nonce,
434 priv->cnonce, priv->nc,
437 out = g_string_new ("Digest ");
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);
450 g_string_append (out, ", ");
451 soup_header_g_string_append_param_quoted (out, "response", response);
454 g_string_append (out, ", ");
455 soup_header_g_string_append_param_quoted (out, "opaque", priv->opaque);
459 char *qop = soup_auth_digest_get_qop (priv->qop);
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",
472 token = g_string_free (out, FALSE);
474 soup_message_add_header_handler (msg,
476 soup_auth_is_for_proxy (auth) ?
477 "Proxy-Authentication-Info" :
478 "Authentication-Info",
479 G_CALLBACK (authentication_info_cb),