--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth.c: HTTP Authentication schemes (basic and digest)
+ *
+ * Authors:
+ * Joe Shaw (joe@ximian.com)
+ * Jeffrey Steadfast (fejj@ximian.com)
+ *
+ * Copyright (C) 2001, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "md5-utils.h"
+#include "soup-context.h"
+#include "soup-message.h"
+#include "soup-private.h"
+
+typedef enum {
+ SOUP_AUTH_BASIC,
+ SOUP_AUTH_DIGEST
+} SoupAuthType;
+
+typedef char *(*SoupAuthFunc) (SoupAuth *auth, SoupMessage *message);
+
+typedef void (*SoupAuthFreeFn) (SoupAuth *auth);
+
+struct _SoupAuth {
+ SoupContext *context;
+ SoupAuthType type;
+ SoupAuthFunc auth_func;
+ SoupAuthFreeFn free_func;
+};
+
+typedef struct {
+ SoupAuth auth;
+ char *realm;
+} SoupAuthBasic;
+
+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;
+
+typedef struct {
+ SoupAuth auth;
+
+ /* These are provided by the server */
+ char *realm;
+ char *nonce;
+ QOPType qop_options;
+ AlgorithmType algorithm;
+ gboolean stale;
+
+ /* These are generated by the client */
+ char *cnonce;
+ int nc;
+ QOPType qop;
+} SoupAuthDigest;
+
+static char *
+basic_auth_func (SoupAuth *auth, SoupMessage *message)
+{
+ const SoupUri *uri;
+ char *user_pass;
+ char *base64;
+ char *out;
+
+ g_return_val_if_fail (auth->context, NULL);
+
+ uri = soup_context_get_uri (auth->context);
+
+ user_pass = g_strdup_printf ("%s:%s", uri->user, uri->passwd);
+ base64 = soup_base64_encode (user_pass, strlen (user_pass));
+ g_free (user_pass);
+
+ out = g_strdup_printf ("Basic %s", base64);
+ g_free (base64);
+
+ return out;
+}
+
+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 char *
+compute_response (SoupAuthDigest *digest, SoupMessage *msg)
+{
+ SoupAuth *auth = (SoupAuth *) digest;
+ const SoupUri *uri;
+ guchar hex_a1[33], hex_a2[33], o[33];
+ guchar d[16];
+ MD5Context ctx;
+
+ uri = soup_context_get_uri (auth->context);
+
+ /* compute A1 */
+ md5_init (&ctx);
+ md5_update (&ctx, uri->user, strlen (uri->user));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, digest->realm, strlen (digest->realm));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, uri->passwd, strlen (uri->passwd));
+
+ if (digest->algorithm == ALGORITHM_MD5_SESS) {
+ md5_final (&ctx, d);
+
+ md5_init (&ctx);
+ md5_update (&ctx, d, 16);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, digest->nonce, strlen (digest->nonce));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, digest->cnonce, strlen (digest->cnonce));
+ }
+
+ /* hexify A1 */
+ md5_final (&ctx, d);
+ digest_hex (d, hex_a1);
+
+ /* compute A2 */
+ md5_init (&ctx);
+ md5_update (&ctx, msg->method, strlen (msg->method));
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, uri->path, strlen (uri->path));
+
+ if (digest->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, hex_a1, 32);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, digest->nonce, strlen (digest->nonce));
+ md5_update (&ctx, ":", 1);
+
+ if (digest->qop) {
+ char *tmp;
+
+ tmp = g_strdup_printf ("%.8x", digest->nc);
+
+ md5_update (&ctx, tmp, strlen (tmp));
+ g_free (tmp);
+ md5_update (&ctx, ":", 1);
+ md5_update (&ctx, digest->cnonce, strlen (digest->cnonce));
+ md5_update (&ctx, ":", 1);
+
+ if (digest->qop == QOP_AUTH)
+ tmp = "auth";
+ else if (digest->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 *
+digest_auth_func (SoupAuth *auth, SoupMessage *message)
+{
+ SoupAuthDigest *digest = (SoupAuthDigest *) auth;
+ const SoupUri *uri;
+ char *response;
+ char *qop = NULL;
+ char *nc;
+ char *out;
+
+ g_return_val_if_fail (auth->context, NULL);
+ g_return_val_if_fail (message, NULL);
+
+ response = compute_response (digest, message);
+
+ if (digest->qop == QOP_AUTH)
+ qop = "auth";
+ else if (digest->qop == QOP_AUTH_INT)
+ qop = "auth-int";
+ else
+ g_assert_not_reached ();
+
+ uri = soup_context_get_uri (auth->context);
+
+ nc = g_strdup_printf ("%.8x", digest->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\"",
+ uri->user,
+ digest->realm,
+ digest->nonce,
+
+ digest->qop ? "cnonce=\"" : "",
+ digest->qop ? digest->cnonce : "",
+ digest->qop ? "\"," : "",
+
+ digest->qop ? "nc=" : "",
+ digest->qop ? nc : "",
+ digest->qop ? "," : "",
+
+ digest->qop ? "qop=" : "",
+ digest->qop ? qop : "",
+ digest->qop ? "," : "",
+
+ uri->path,
+ response);
+
+ g_free (nc);
+
+ digest->nc++;
+
+ return out;
+}
+
+static void
+decode_lwsp (char **in)
+{
+ char *inptr = *in;
+
+ while (isspace (*inptr))
+ inptr++;
+
+ *in = inptr;
+}
+
+static char *
+decode_quoted_string (char **in)
+{
+ char *inptr = *in;
+ char *out = NULL, *outptr;
+ int outlen;
+ int c;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ char *intmp;
+ int skip = 0;
+
+ /* first, calc length */
+ inptr++;
+ intmp = inptr;
+ while ( (c = *intmp++) && c != '"') {
+ if (c == '\\' && *intmp) {
+ intmp++;
+ skip++;
+ }
+ }
+
+ outlen = intmp - inptr - skip;
+ out = outptr = g_malloc (outlen + 1);
+
+ while ( (c = *inptr++) && c != '"') {
+ if (c == '\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ *outptr = 0;
+ }
+
+ *in = inptr;
+
+ return out;
+}
+
+static char *
+decode_token (char **in)
+{
+ char *inptr = *in;
+ char *start;
+
+ decode_lwsp (&inptr);
+ start = inptr;
+
+ while (*inptr && *inptr != '=' && *inptr != ',')
+ inptr++;
+
+ if (inptr > start) {
+ *in = inptr;
+ return g_strndup (start, inptr - start);
+ }
+ else
+ return NULL;
+}
+
+static char *
+decode_value (char **in)
+{
+ char *inptr = *in;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"')
+ return decode_quoted_string (in);
+ else
+ return decode_token (in);
+}
+
+static GHashTable *
+parse_param_list (const char *header)
+{
+ GHashTable *params = g_hash_table_new (
+ soup_str_case_hash, soup_str_case_equal);
+ char *ptr;
+ gboolean added = FALSE;
+
+ ptr = (char *) header;
+ while (ptr && *ptr) {
+ char *name;
+ char *value;
+
+ name = decode_token (&ptr);
+ if (*ptr == '=') {
+ ptr++;
+ value = decode_value (&ptr);
+ g_hash_table_insert (params, name, value);
+ added = TRUE;
+ }
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ if (!added) {
+ g_hash_table_destroy (params);
+ params = NULL;
+ }
+
+ return params;
+}
+
+static void
+destroy_param_hash_elements (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ g_free (value);
+}
+
+static void
+destroy_param_hash (GHashTable *table)
+{
+ g_hash_table_foreach (table, destroy_param_hash_elements, NULL);
+ g_hash_table_destroy (table);
+}
+
+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;
+
+ 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 char *
+copy_token_if_exists (GHashTable *tokens, char *t)
+{
+ char *data;
+
+ g_return_val_if_fail (tokens, NULL);
+ g_return_val_if_fail (t, NULL);
+
+ if ( (data = g_hash_table_lookup (tokens, t)))
+ return g_strdup (data);
+ else
+ return NULL;
+}
+
+static void
+soup_auth_basic_parse_header (SoupAuth *auth, const char *header)
+{
+ SoupAuthBasic *basic = (SoupAuthBasic *) auth;
+ GHashTable *tokens = parse_param_list (header);
+
+ g_return_if_fail (tokens);
+
+ basic->realm = copy_token_if_exists (tokens, "realm");
+
+ destroy_param_hash (tokens);
+}
+
+static void
+soup_auth_digest_parse_header (SoupAuth *auth, const char *header)
+{
+ SoupAuthDigest *digest = (SoupAuthDigest *) auth;
+ GHashTable *tokens = parse_param_list (header);
+ char *tmp, *ptr;
+
+ g_return_if_fail (tokens);
+
+ digest->realm = copy_token_if_exists (tokens, "realm");
+ digest->nonce = copy_token_if_exists (tokens, "nonce");
+
+ tmp = copy_token_if_exists (tokens, "qop");
+ ptr = tmp;
+
+ while (ptr && *ptr) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ digest->qop_options |= decode_qop (token);
+ g_free (token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ g_free (tmp);
+
+ tmp = copy_token_if_exists (tokens, "stale");
+
+ if (tmp && g_strcasecmp (tmp, "true") == 0)
+ digest->stale = TRUE;
+ else
+ digest->stale = FALSE;
+
+ g_free (tmp);
+
+ tmp = copy_token_if_exists (tokens, "algorithm");
+ digest->algorithm = decode_algorithm (tmp);
+ g_free (tmp);
+
+ destroy_param_hash (tokens);
+}
+
+static void
+basic_free (SoupAuth *auth)
+{
+ SoupAuthBasic *basic = (SoupAuthBasic *) auth;
+
+ g_free (basic->realm);
+ g_free (basic);
+}
+
+static SoupAuth *
+soup_auth_new_basic (void)
+{
+ SoupAuthBasic *basic;
+ SoupAuth *auth;
+
+ basic = g_new0 (SoupAuthBasic, 1);
+ auth = (SoupAuth *) basic;
+ auth->type = SOUP_AUTH_BASIC;
+ auth->auth_func = basic_auth_func;
+ auth->free_func = basic_free;
+
+ return auth;
+}
+
+static void
+digest_free (SoupAuth *auth)
+{
+ SoupAuthDigest *digest = (SoupAuthDigest *) auth;
+
+ g_free (digest->realm);
+ g_free (digest->nonce);
+ g_free (digest->cnonce);
+ g_free (digest);
+}
+
+static SoupAuth *
+soup_auth_new_digest (void)
+{
+ SoupAuthDigest *digest;
+ SoupAuth *auth;
+ char *bgen;
+
+ digest = g_new0 (SoupAuthDigest, 1);
+ auth = (SoupAuth *) digest;
+ auth->type = SOUP_AUTH_DIGEST;
+ auth->auth_func = digest_auth_func;
+ auth->free_func = digest_free;
+
+ bgen = g_strdup_printf ("%p:%lu:%lu",
+ auth,
+ (unsigned long) getpid (),
+ (unsigned long) time (0));
+ digest->cnonce = soup_base64_encode (bgen, strlen (bgen));
+ digest->nc = 1;
+ /* We're just going to do qop=auth for now */
+ digest->qop = QOP_AUTH;
+
+ return auth;
+}
+
+SoupAuth *
+soup_auth_new_from_header (SoupContext *context, const char *header)
+{
+ SoupAuth *auth;
+
+ if (g_strncasecmp (header, "Basic", 5) == 0) {
+ auth = soup_auth_new_basic ();
+ soup_auth_basic_parse_header (auth, header + 6);
+ }
+ else if (g_strncasecmp (header, "Digest", 6) == 0) {
+ auth = soup_auth_new_digest ();
+ soup_auth_digest_parse_header (auth, header + 7);
+ }
+ else {
+ g_warning ("Authentication type not supported");
+ return NULL;
+ }
+
+ auth->context = context;
+
+ return auth;
+}
+
+gchar *
+soup_auth_authorize (SoupAuth *auth, SoupMessage *msg)
+{
+ g_return_val_if_fail (auth != NULL, NULL);
+ g_return_val_if_fail (msg != NULL, NULL);
+
+ return auth->auth_func (auth, msg);
+}
+
+void
+soup_auth_free (SoupAuth *auth)
+{
+ g_return_if_fail (auth != NULL);
+
+ auth->free_func (auth);
+}
+
+gboolean
+soup_auth_invalidates_prior (SoupAuth *auth)
+{
+ g_return_val_if_fail (auth != NULL, FALSE);
+
+ switch (auth->type) {
+ case SOUP_AUTH_DIGEST:
+ return ((SoupAuthDigest *) auth)->stale;
+ case SOUP_AUTH_BASIC:
+ default:
+ return FALSE;
+ }
+}
*/
#include "soup-message.h"
+#include "soup-misc.h"
#include "soup-context.h"
#include "soup-private.h"
#include "soup-transfer.h"
+static SoupErrorCode
+authorize_handler (SoupMessage *msg, gboolean proxy)
+{
+ const char *auth_header;
+ SoupAuth *auth;
+ SoupContext *ctx;
+
+ ctx = proxy ? soup_get_proxy () : msg->context;
+
+ auth_header =
+ soup_message_get_response_header (
+ msg,
+ proxy ? "Proxy-Authenticate" : "WWW-Authenticate");
+ if (!auth_header) return SOUP_ERROR_CANT_AUTHENTICATE;
+
+ auth = soup_auth_new_from_header (ctx, auth_header);
+ if (!auth) return SOUP_ERROR_MALFORMED_HEADER;
+
+ if (ctx->auth) {
+ if (soup_auth_invalidates_prior (auth))
+ soup_auth_free (ctx->auth);
+ else {
+ soup_auth_free (auth);
+ return SOUP_ERROR_CANT_AUTHENTICATE;
+ }
+ }
+
+ ctx->auth = auth;
+
+ if (msg->priv->req_header) {
+ g_string_free (msg->priv->req_header, TRUE);
+ msg->priv->req_header = NULL;
+ }
+
+ soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
+
+ return SOUP_ERROR_NONE;
+}
+
/**
* soup_message_new:
* @context: a %SoupContext for the destination endpoint.
{
SoupMessage *ret;
- g_return_val_if_fail(context, NULL);
+ g_return_val_if_fail (context, NULL);
ret = g_new0 (SoupMessage, 1);
ret->priv = g_new0 (SoupMessagePrivate, 1);
soup_context_ref (context);
+ /*
+ * Add a 401 (Authorization Required) response code handler if the
+ * context URI has a login user name.
+ */
+ if (soup_context_get_uri (context)->user)
+ soup_message_add_response_code_handler (
+ ret,
+ 401,
+ SOUP_HANDLER_POST_BODY,
+ (SoupHandlerFn) authorize_handler,
+ GINT_TO_POINTER (FALSE));
+
+ /*
+ * Always add a 407 (Proxy-Authorization Required) handler, in case the
+ * proxy is reset after message creation.
+ */
+ soup_message_add_response_code_handler (
+ ret,
+ 407,
+ SOUP_HANDLER_POST_BODY,
+ (SoupHandlerFn) authorize_handler,
+ GINT_TO_POINTER (TRUE));
+
return ret;
}
g_free (old_value);
}
- g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
+ if (value)
+ g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
}
/**
* @name: header name.
* @value: header value.
*
- * Adds a new transport header to be sent on an outgoing request.
+ * Adds a new transport header to be sent on an outgoing request. Passing a NULL
+ * @value will remove the header name supplied.
*/
void
soup_message_set_request_header (SoupMessage *req,
* @name: header name.
* @value: header value.
*
- * Adds a new transport header to be sent on an outgoing response.
+ * Adds a new transport header to be sent on an outgoing response. Passing a
+ * NULL @value will remove the header name supplied.
*/
void
soup_message_set_response_header (SoupMessage *req,
}
static SoupErrorCode
-soup_message_redirect (SoupMessage *msg, gpointer user_data)
+redirect_handler (SoupMessage *msg, gpointer user_data)
{
const gchar *new_url;
return SOUP_ERROR_NONE;
new_url = soup_message_get_response_header (msg, "Location");
+
if (new_url) {
- soup_context_unref (msg->context);
- msg->context = soup_context_get (new_url);
+ SoupContext *new_ctx = soup_context_get (new_url);
+ if (!new_ctx) return SOUP_ERROR_MALFORMED_HEADER;
- if (!msg->context) return SOUP_ERROR_MALFORMED_HEADER;
+ soup_context_unref (msg->context);
+ msg->context = new_ctx;
soup_message_queue (msg,
msg->priv->callback,
soup_message_add_header_handler (msg,
"Location",
SOUP_HANDLER_PRE_BODY,
- soup_message_redirect,
+ redirect_handler,
NULL);
else if (REMOVED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
soup_message_remove_handler (msg,
- soup_message_redirect,
+ redirect_handler,
NULL);
msg->priv->msg_flags = flags;