/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-digest-offset: 8 -*- */ /* * soup-auth-digest.c: HTTP Digest Authentication * * Copyright (C) 2001-2002, Ximian, Inc. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "soup-auth-digest.h" #include "soup-headers.h" #include "soup-message.h" #include "soup-misc.h" #include "soup-private.h" #include "soup-uri.h" #include "md5-utils.h" static void parse (SoupAuth *auth, GHashTable *tokens); static GSList *get_protection_space (SoupAuth *auth, const SoupUri *source_uri); static void authenticate (SoupAuth *auth, const char *username, const char *password); static char *get_authorization (SoupAuth *auth, SoupMessage *msg); typedef enum { QOP_NONE = 0, QOP_AUTH = 1 << 0, QOP_AUTH_INT = 1 << 1 } QOPType; typedef enum { ALGORITHM_MD5 = 1 << 0, ALGORITHM_MD5_SESS = 1 << 1 } AlgorithmType; struct _SoupAuthDigestPrivate { char *user; guchar hex_a1[33]; /* These are provided by the server */ char *nonce; QOPType qop_options; AlgorithmType algorithm; char *domain; /* These are generated by the client */ char *cnonce; int nc; QOPType qop; }; #define PARENT_TYPE SOUP_TYPE_AUTH static SoupAuthClass *parent_class; static void init (GObject *object) { SoupAuthDigest *digest = SOUP_AUTH_DIGEST (object); digest->priv = g_new0 (SoupAuthDigestPrivate, 1); } static void finalize (GObject *object) { SoupAuthDigest *digest = SOUP_AUTH_DIGEST (object); if (digest->priv->user) g_free (digest->priv->user); if (digest->priv->nonce) g_free (digest->priv->nonce); if (digest->priv->domain) g_free (digest->priv->domain); if (digest->priv->cnonce) g_free (digest->priv->cnonce); g_free (digest->priv); G_OBJECT_CLASS (parent_class)->finalize (object); } static void class_init (GObjectClass *object_class) { SoupAuthClass *auth_class = SOUP_AUTH_CLASS (object_class); parent_class = g_type_class_ref (PARENT_TYPE); auth_class->scheme_name = "Digest"; auth_class->get_protection_space = get_protection_space; auth_class->parse = parse; auth_class->authenticate = authenticate; auth_class->get_authorization = get_authorization; object_class->finalize = finalize; } SOUP_MAKE_TYPE (soup_auth_digest, SoupAuthDigest, class_init, init, PARENT_TYPE) typedef struct { char *name; guint type; } DataType; static DataType qop_types[] = { { "auth", QOP_AUTH }, { "auth-int", QOP_AUTH_INT } }; static DataType algorithm_types[] = { { "MD5", ALGORITHM_MD5 }, { "MD5-sess", ALGORITHM_MD5_SESS } }; static guint decode_data_type (DataType *dtype, const char *name) { int i; if (!name) return 0; for (i = 0; dtype[i].name; i++) { if (!g_strcasecmp (dtype[i].name, name)) return dtype[i].type; } return 0; } static inline guint decode_qop (const char *name) { return decode_data_type (qop_types, name); } static inline guint decode_algorithm (const char *name) { return decode_data_type (algorithm_types, name); } static void parse (SoupAuth *auth, GHashTable *tokens) { SoupAuthDigest *digest = SOUP_AUTH_DIGEST (auth); const char *tmp, *ptr; if (!tokens) return; SOUP_AUTH_CLASS (parent_class)->parse (auth, tokens); digest->priv->nc = 1; /* We're just going to do qop=auth for now */ digest->priv->qop = QOP_AUTH; digest->priv->nonce = soup_header_param_copy_token (tokens, "nonce"); digest->priv->domain = soup_header_param_copy_token (tokens, "domain"); tmp = soup_header_param_copy_token (tokens, "qop"); ptr = tmp; while (ptr && *ptr) { char *token; token = soup_header_param_decode_token (&ptr); if (token) digest->priv->qop_options |= decode_qop (token); g_free (token); if (*ptr == ',') ptr++; } tmp = soup_header_param_copy_token (tokens, "algorithm"); digest->priv->algorithm = decode_algorithm (tmp); } static GSList * get_protection_space (SoupAuth *auth, const SoupUri *source_uri) { SoupAuthDigest *digest = SOUP_AUTH_DIGEST (auth); GSList *space = NULL; SoupUri *uri; char *domain, *d, *lasts; if (!digest->priv->domain) { /* If no domain directive, the protection space is the * whole server. */ uri = soup_uri_copy (source_uri); g_free (uri->path); g_free (uri->query); uri->path = uri->query = NULL; return g_slist_prepend (NULL, uri); } domain = g_strdup (digest->priv->domain); for (d = strtok_r (domain, " ", &lasts); d; d = strtok_r (NULL, " ", &lasts)) { uri = soup_uri_new_with_base (source_uri, d); space = g_slist_prepend (space, uri); } g_free (domain); return space; } static void digest_hex (guchar *digest, guchar hex[33]) { guchar *s, *p; /* lowercase hexify that bad-boy... */ for (s = digest, p = hex; p < hex + 32; s++, p += 2) sprintf (p, "%.2x", *s); } static void authenticate (SoupAuth *auth, const char *username, const char *password) { SoupAuthDigest *digest = SOUP_AUTH_DIGEST (auth); const char *realm; MD5Context ctx; guchar d[16]; char *bgen; g_return_if_fail (username != NULL); SOUP_AUTH_CLASS (parent_class)->authenticate (auth, username, password); bgen = g_strdup_printf ("%p:%lu:%lu", auth, (unsigned long) getpid (), (unsigned long) time (0)); digest->priv->cnonce = soup_base64_encode (bgen, strlen (bgen)); g_free (bgen); digest->priv->user = g_strdup (username); /* compute A1 */ md5_init (&ctx); md5_update (&ctx, username, strlen (username)); md5_update (&ctx, ":", 1); realm = soup_auth_get_realm (auth); if (realm) md5_update (&ctx, realm, strlen (realm)); md5_update (&ctx, ":", 1); if (password) md5_update (&ctx, password, strlen (password)); if (digest->priv->algorithm == ALGORITHM_MD5_SESS) { md5_final (&ctx, d); md5_init (&ctx); md5_update (&ctx, d, 16); md5_update (&ctx, ":", 1); md5_update (&ctx, digest->priv->nonce, strlen (digest->priv->nonce)); md5_update (&ctx, ":", 1); md5_update (&ctx, digest->priv->cnonce, strlen (digest->priv->cnonce)); } /* hexify A1 */ md5_final (&ctx, d); digest_hex (d, digest->priv->hex_a1); } static char * compute_response (SoupAuthDigest *digest, SoupMessage *msg) { guchar hex_a2[33], o[33]; guchar d[16]; MD5Context ctx; char *url; url = soup_uri_to_string (soup_message_get_uri (msg), TRUE); /* compute A2 */ md5_init (&ctx); md5_update (&ctx, msg->method, strlen (msg->method)); md5_update (&ctx, ":", 1); md5_update (&ctx, url, strlen (url)); g_free (url); if (digest->priv->qop == QOP_AUTH_INT) { /* FIXME: Actually implement. Ugh. */ md5_update (&ctx, ":", 1); md5_update (&ctx, "00000000000000000000000000000000", 32); } /* now hexify A2 */ md5_final (&ctx, d); digest_hex (d, hex_a2); /* compute KD */ md5_init (&ctx); md5_update (&ctx, digest->priv->hex_a1, 32); md5_update (&ctx, ":", 1); md5_update (&ctx, digest->priv->nonce, strlen (digest->priv->nonce)); md5_update (&ctx, ":", 1); if (digest->priv->qop) { char *tmp; tmp = g_strdup_printf ("%.8x", digest->priv->nc); md5_update (&ctx, tmp, strlen (tmp)); g_free (tmp); md5_update (&ctx, ":", 1); md5_update (&ctx, digest->priv->cnonce, strlen (digest->priv->cnonce)); md5_update (&ctx, ":", 1); if (digest->priv->qop == QOP_AUTH) tmp = "auth"; else if (digest->priv->qop == QOP_AUTH_INT) tmp = "auth-int"; else g_assert_not_reached (); md5_update (&ctx, tmp, strlen (tmp)); md5_update (&ctx, ":", 1); } md5_update (&ctx, hex_a2, 32); md5_final (&ctx, d); digest_hex (d, o); return g_strdup (o); } static char * get_authorization (SoupAuth *auth, SoupMessage *msg) { SoupAuthDigest *digest = (SoupAuthDigest *) auth; char *response; char *qop = NULL; char *nc; char *url; char *out; url = soup_uri_to_string (soup_message_get_uri (msg), TRUE); response = compute_response (digest, msg); if (digest->priv->qop == QOP_AUTH) qop = "auth"; else if (digest->priv->qop == QOP_AUTH_INT) qop = "auth-int"; else g_assert_not_reached (); nc = g_strdup_printf ("%.8x", digest->priv->nc); out = g_strdup_printf ( "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", %s%s%s " "%s%s%s %s%s%s uri=\"%s\", response=\"%s\"", digest->priv->user, soup_auth_get_realm (auth), digest->priv->nonce, digest->priv->qop ? "cnonce=\"" : "", digest->priv->qop ? digest->priv->cnonce : "", digest->priv->qop ? "\"," : "", digest->priv->qop ? "nc=" : "", digest->priv->qop ? nc : "", digest->priv->qop ? "," : "", digest->priv->qop ? "qop=" : "", digest->priv->qop ? qop : "", digest->priv->qop ? "," : "", url, response); g_free (response); g_free (url); g_free (nc); digest->priv->nc++; return out; }