1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-digest-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-md5-utils.h"
20 #include "soup-message.h"
21 #include "soup-misc.h"
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);
38 ALGORITHM_MD5 = 1 << 0,
39 ALGORITHM_MD5_SESS = 1 << 1
46 /* These are provided by the server */
50 AlgorithmType algorithm;
53 /* These are generated by the client */
57 } SoupAuthDigestPrivate;
58 #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate))
60 G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
63 soup_auth_digest_init (SoupAuthDigest *digest)
68 finalize (GObject *object)
70 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (object);
79 g_free (priv->domain);
81 g_free (priv->cnonce);
83 G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
87 soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
89 SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
90 GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
92 g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate));
94 auth_class->scheme_name = "Digest";
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;
103 object_class->finalize = finalize;
111 static DataType qop_types[] = {
112 { "auth", QOP_AUTH },
113 { "auth-int", QOP_AUTH_INT }
116 static DataType algorithm_types[] = {
117 { "MD5", ALGORITHM_MD5 },
118 { "MD5-sess", ALGORITHM_MD5_SESS }
122 decode_data_type (DataType *dtype, const char *name)
129 for (i = 0; dtype[i].name; i++) {
130 if (!g_strcasecmp (dtype[i].name, name))
131 return dtype[i].type;
138 decode_qop (const char *name)
140 return decode_data_type (qop_types, name);
144 decode_algorithm (const char *name)
146 return decode_data_type (algorithm_types, name);
150 construct (SoupAuth *auth, const char *header)
152 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
156 header += sizeof ("Digest");
158 tokens = soup_header_param_parse_list (header);
163 /* We're just going to do qop=auth for now */
164 priv->qop = QOP_AUTH;
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");
170 tmp = soup_header_param_copy_token (tokens, "qop");
173 while (ptr && *ptr) {
176 token = soup_header_param_decode_token ((char **)&ptr);
178 priv->qop_options |= decode_qop (token);
186 tmp = soup_header_param_copy_token (tokens, "algorithm");
187 priv->algorithm = decode_algorithm (tmp);
190 soup_header_param_destroy_hash (tokens);
194 get_protection_space (SoupAuth *auth, const SoupUri *source_uri)
196 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
197 GSList *space = NULL;
199 char **dvec, *d, *dir, *slash;
202 if (!priv->domain || !*priv->domain) {
203 /* If no domain directive, the protection space is the
206 return g_slist_prepend (NULL, g_strdup (""));
209 dvec = g_strsplit (priv->domain, " ", 0);
210 for (dix = 0; dvec[dix] != NULL; dix++) {
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);
227 slash = strrchr (dir, '/');
228 if (slash && !slash[1])
231 space = g_slist_prepend (space, dir);
240 get_realm (SoupAuth *auth)
242 return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->realm;
246 authenticate (SoupAuth *auth, const char *username, const char *password)
248 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
253 g_return_if_fail (username != NULL);
255 bgen = g_strdup_printf ("%p:%lu:%lu",
257 (unsigned long) getpid (),
258 (unsigned long) time (0));
259 priv->cnonce = soup_base64_encode (bgen, strlen (bgen));
262 priv->user = g_strdup (username);
265 soup_md5_init (&ctx);
267 soup_md5_update (&ctx, username, strlen (username));
269 soup_md5_update (&ctx, ":", 1);
271 soup_md5_update (&ctx, priv->realm,
272 strlen (priv->realm));
275 soup_md5_update (&ctx, ":", 1);
277 soup_md5_update (&ctx, password, strlen (password));
279 if (priv->algorithm == ALGORITHM_MD5_SESS) {
280 soup_md5_final (&ctx, d);
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));
293 soup_md5_final_hex (&ctx, priv->hex_a1);
297 is_authenticated (SoupAuth *auth)
299 return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL;
303 compute_response (SoupAuthDigestPrivate *priv, SoupMessage *msg)
305 guchar hex_a2[33], o[33];
310 uri = soup_message_get_uri (msg);
311 g_return_val_if_fail (uri != NULL, NULL);
312 url = soup_uri_to_string (uri, TRUE);
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));
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);
329 soup_md5_final_hex (&md5, hex_a2);
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);
342 tmp = g_strdup_printf ("%.8x", priv->nc);
344 soup_md5_update (&md5, tmp, strlen (tmp));
346 soup_md5_update (&md5, ":", 1);
347 soup_md5_update (&md5, priv->cnonce,
348 strlen (priv->cnonce));
349 soup_md5_update (&md5, ":", 1);
351 if (priv->qop == QOP_AUTH)
353 else if (priv->qop == QOP_AUTH_INT)
356 g_assert_not_reached ();
358 soup_md5_update (&md5, tmp, strlen (tmp));
359 soup_md5_update (&md5, ":", 1);
362 soup_md5_update (&md5, hex_a2, 32);
363 soup_md5_final_hex (&md5, o);
369 get_authorization (SoupAuth *auth, SoupMessage *msg)
371 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
379 uri = soup_message_get_uri (msg);
380 g_return_val_if_fail (uri != NULL, NULL);
381 url = soup_uri_to_string (uri, TRUE);
383 response = compute_response (priv, msg);
385 if (priv->qop == QOP_AUTH)
387 else if (priv->qop == QOP_AUTH_INT)
390 g_assert_not_reached ();
392 nc = g_strdup_printf ("%.8x", priv->nc);
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\"",
401 priv->qop ? "cnonce=\"" : "",
402 priv->qop ? priv->cnonce : "",
403 priv->qop ? "\"," : "",
405 priv->qop ? "nc=" : "",
407 priv->qop ? "," : "",
409 priv->qop ? "qop=" : "",
410 priv->qop ? qop : "",
411 priv->qop ? "," : "",