summaryrefslogtreecommitdiff
path: root/libsoup/auth
diff options
context:
space:
mode:
authorPatrick Griffis <pgriffis@igalia.com>2020-03-10 13:58:38 -0700
committerPatrick Griffis <pgriffis@igalia.com>2020-09-19 15:41:24 -0700
commit5dcc1a9cffb351eec1e882b82c4dcf08145fa280 (patch)
tree0a08ae0d46408914453892d26fff7f9e410587a2 /libsoup/auth
parent1b3b31371f01af8b6081e5358273b4b28ff3489b (diff)
downloadlibsoup-5dcc1a9cffb351eec1e882b82c4dcf08145fa280.tar.gz
Reorganize source tree
Diffstat (limited to 'libsoup/auth')
-rw-r--r--libsoup/auth/soup-auth-basic.c145
-rw-r--r--libsoup/auth/soup-auth-basic.h27
-rw-r--r--libsoup/auth/soup-auth-digest.c490
-rw-r--r--libsoup/auth/soup-auth-digest.h64
-rw-r--r--libsoup/auth/soup-auth-domain-basic.c344
-rw-r--r--libsoup/auth/soup-auth-domain-basic.h59
-rw-r--r--libsoup/auth/soup-auth-domain-digest.c450
-rw-r--r--libsoup/auth/soup-auth-domain-digest.h63
-rw-r--r--libsoup/auth/soup-auth-domain.c635
-rw-r--r--libsoup/auth/soup-auth-domain.h110
-rw-r--r--libsoup/auth/soup-auth-manager.c862
-rw-r--r--libsoup/auth/soup-auth-manager.h49
-rw-r--r--libsoup/auth/soup-auth-negotiate.c622
-rw-r--r--libsoup/auth/soup-auth-negotiate.h28
-rw-r--r--libsoup/auth/soup-auth-ntlm.c1513
-rw-r--r--libsoup/auth/soup-auth-ntlm.h27
-rw-r--r--libsoup/auth/soup-auth.c554
-rw-r--r--libsoup/auth/soup-auth.h130
-rw-r--r--libsoup/auth/soup-connection-auth.c190
-rw-r--r--libsoup/auth/soup-connection-auth.h55
20 files changed, 6417 insertions, 0 deletions
diff --git a/libsoup/auth/soup-auth-basic.c b/libsoup/auth/soup-auth-basic.c
new file mode 100644
index 00000000..5f1e7186
--- /dev/null
+++ b/libsoup/auth/soup-auth-basic.c
@@ -0,0 +1,145 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-basic.c: HTTP Basic Authentication
+ *
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-basic.h"
+#include "soup.h"
+
+typedef struct {
+ char *token;
+} SoupAuthBasicPrivate;
+
+/**
+ * SOUP_TYPE_AUTH_BASIC:
+ *
+ * A #GType corresponding to HTTP "Basic" authentication.
+ * #SoupSessions support this by default; if you want to disable
+ * support for it, call soup_session_remove_feature_by_type(),
+ * passing %SOUP_TYPE_AUTH_BASIC.
+ *
+ * Since: 2.34
+ */
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthBasic, soup_auth_basic, SOUP_TYPE_AUTH)
+
+static void
+soup_auth_basic_init (SoupAuthBasic *basic)
+{
+}
+
+static void
+soup_auth_basic_finalize (GObject *object)
+{
+ SoupAuthBasicPrivate *priv = soup_auth_basic_get_instance_private (SOUP_AUTH_BASIC (object));
+
+ g_free (priv->token);
+
+ G_OBJECT_CLASS (soup_auth_basic_parent_class)->finalize (object);
+}
+
+static gboolean
+soup_auth_basic_update (SoupAuth *auth, SoupMessage *msg,
+ GHashTable *auth_params)
+{
+ SoupAuthBasicPrivate *priv = soup_auth_basic_get_instance_private (SOUP_AUTH_BASIC (auth));
+
+ /* If we're updating a pre-existing auth, the
+ * username/password must be bad now, so forget it.
+ * Other than that, there's nothing to do here.
+ */
+ if (priv->token) {
+ memset (priv->token, 0, strlen (priv->token));
+ g_free (priv->token);
+ priv->token = NULL;
+ }
+
+ return TRUE;
+}
+
+static GSList *
+soup_auth_basic_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
+{
+ char *space, *p;
+
+ space = g_strdup (source_uri->path);
+
+ /* Strip filename component */
+ p = strrchr (space, '/');
+ if (p == space && p[1])
+ p[1] = '\0';
+ else if (p && p[1])
+ *p = '\0';
+
+ return g_slist_prepend (NULL, space);
+}
+
+static void
+soup_auth_basic_authenticate (SoupAuth *auth, const char *username,
+ const char *password)
+{
+ SoupAuthBasicPrivate *priv = soup_auth_basic_get_instance_private (SOUP_AUTH_BASIC (auth));
+ char *user_pass, *user_pass_latin1;
+ int len;
+
+ user_pass = g_strdup_printf ("%s:%s", username, password);
+ user_pass_latin1 = g_convert (user_pass, -1, "ISO-8859-1", "UTF-8",
+ NULL, NULL, NULL);
+ if (user_pass_latin1) {
+ memset (user_pass, 0, strlen (user_pass));
+ g_free (user_pass);
+ user_pass = user_pass_latin1;
+ }
+ len = strlen (user_pass);
+
+ if (priv->token) {
+ memset (priv->token, 0, strlen (priv->token));
+ g_free (priv->token);
+ }
+ priv->token = g_base64_encode ((guchar *)user_pass, len);
+
+ memset (user_pass, 0, len);
+ g_free (user_pass);
+}
+
+static gboolean
+soup_auth_basic_is_authenticated (SoupAuth *auth)
+{
+ SoupAuthBasicPrivate *priv = soup_auth_basic_get_instance_private (SOUP_AUTH_BASIC (auth));
+
+ return priv->token != NULL;
+}
+
+static char *
+soup_auth_basic_get_authorization (SoupAuth *auth, SoupMessage *msg)
+{
+ SoupAuthBasicPrivate *priv = soup_auth_basic_get_instance_private (SOUP_AUTH_BASIC (auth));
+
+ return g_strdup_printf ("Basic %s", priv->token);
+}
+
+static void
+soup_auth_basic_class_init (SoupAuthBasicClass *auth_basic_class)
+{
+ SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_basic_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_basic_class);
+
+ auth_class->scheme_name = "Basic";
+ auth_class->strength = 1;
+
+ auth_class->update = soup_auth_basic_update;
+ auth_class->get_protection_space = soup_auth_basic_get_protection_space;
+ auth_class->authenticate = soup_auth_basic_authenticate;
+ auth_class->is_authenticated = soup_auth_basic_is_authenticated;
+ auth_class->get_authorization = soup_auth_basic_get_authorization;
+
+ object_class->finalize = soup_auth_basic_finalize;
+}
diff --git a/libsoup/auth/soup-auth-basic.h b/libsoup/auth/soup-auth-basic.h
new file mode 100644
index 00000000..b7936871
--- /dev/null
+++ b/libsoup/auth/soup-auth-basic.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003, Ximian, Inc.
+ */
+
+#ifndef __SOUP_AUTH_BASIC_H__
+#define __SOUP_AUTH_BASIC_H__ 1
+
+#include "soup-auth.h"
+
+#define SOUP_AUTH_BASIC(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_BASIC, SoupAuthBasic))
+#define SOUP_AUTH_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_BASIC, SoupAuthBasicClass))
+#define SOUP_IS_AUTH_BASIC(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_BASIC))
+#define SOUP_IS_AUTH_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_BASIC))
+#define SOUP_AUTH_BASIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_BASIC, SoupAuthBasicClass))
+
+typedef struct {
+ SoupAuth parent;
+
+} SoupAuthBasic;
+
+typedef struct {
+ SoupAuthClass parent_class;
+
+} SoupAuthBasicClass;
+
+#endif /* __SOUP_AUTH_BASIC_H__ */
diff --git a/libsoup/auth/soup-auth-digest.c b/libsoup/auth/soup-auth-digest.c
new file mode 100644
index 00000000..e8ba9903
--- /dev/null
+++ b/libsoup/auth/soup-auth-digest.c
@@ -0,0 +1,490 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-digest.c: HTTP Digest Authentication
+ *
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-digest.h"
+#include "soup.h"
+#include "soup-message-private.h"
+
+#ifdef G_OS_WIN32
+#include <process.h>
+#endif
+
+typedef struct {
+ char *user;
+ char hex_urp[33];
+ char hex_a1[33];
+
+ /* These are provided by the server */
+ char *nonce;
+ char *opaque;
+ SoupAuthDigestQop qop_options;
+ SoupAuthDigestAlgorithm algorithm;
+ char *domain;
+
+ /* These are generated by the client */
+ char *cnonce;
+ int nc;
+ SoupAuthDigestQop qop;
+} SoupAuthDigestPrivate;
+
+static void recompute_hex_a1 (SoupAuthDigestPrivate *priv);
+
+/**
+ * SOUP_TYPE_AUTH_DIGEST:
+ *
+ * A #GType corresponding to HTTP "Digest" authentication.
+ * #SoupSessions support this by default; if you want to disable
+ * support for it, call soup_session_remove_feature_by_type(),
+ * passing %SOUP_TYPE_AUTH_DIGEST.
+ *
+ * Since: 2.34
+ */
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
+
+static void
+soup_auth_digest_init (SoupAuthDigest *digest)
+{
+}
+
+static void
+soup_auth_digest_finalize (GObject *object)
+{
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (SOUP_AUTH_DIGEST (object));
+
+ g_free (priv->user);
+ g_free (priv->nonce);
+ g_free (priv->domain);
+ g_free (priv->cnonce);
+
+ memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
+ memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
+
+ G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
+}
+
+SoupAuthDigestAlgorithm
+soup_auth_digest_parse_algorithm (const char *algorithm)
+{
+ if (!algorithm || !g_ascii_strcasecmp (algorithm, "MD5"))
+ return SOUP_AUTH_DIGEST_ALGORITHM_MD5;
+ else if (!g_ascii_strcasecmp (algorithm, "MD5-sess"))
+ return SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS;
+ else
+ return -1;
+}
+
+char *
+soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm)
+{
+ if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5)
+ return g_strdup ("MD5");
+ else if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS)
+ return g_strdup ("MD5-sess");
+ else
+ return NULL;
+}
+
+SoupAuthDigestQop
+soup_auth_digest_parse_qop (const char *qop)
+{
+ GSList *qop_values, *iter;
+ SoupAuthDigestQop out = 0;
+
+ g_return_val_if_fail (qop != NULL, 0);
+
+ qop_values = soup_header_parse_list (qop);
+ for (iter = qop_values; iter; iter = iter->next) {
+ if (!g_ascii_strcasecmp (iter->data, "auth"))
+ out |= SOUP_AUTH_DIGEST_QOP_AUTH;
+ else if (!g_ascii_strcasecmp (iter->data, "auth-int"))
+ out |= SOUP_AUTH_DIGEST_QOP_AUTH_INT;
+ }
+ soup_header_free_list (qop_values);
+
+ return out;
+}
+
+char *
+soup_auth_digest_get_qop (SoupAuthDigestQop qop)
+{
+ GString *out;
+
+ out = g_string_new (NULL);
+ if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
+ g_string_append (out, "auth");
+ if (qop & SOUP_AUTH_DIGEST_QOP_AUTH_INT) {
+ if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
+ g_string_append (out, ",");
+ g_string_append (out, "auth-int");
+ }
+
+ return g_string_free (out, FALSE);
+}
+
+static gboolean
+soup_auth_digest_update (SoupAuth *auth, SoupMessage *msg,
+ GHashTable *auth_params)
+{
+ SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+ const char *stale, *qop;
+ guint qop_options;
+ gboolean ok = TRUE;
+
+ g_free (priv->domain);
+ g_free (priv->nonce);
+ g_free (priv->opaque);
+
+ priv->nc = 1;
+
+ priv->domain = g_strdup (g_hash_table_lookup (auth_params, "domain"));
+ priv->nonce = g_strdup (g_hash_table_lookup (auth_params, "nonce"));
+ priv->opaque = g_strdup (g_hash_table_lookup (auth_params, "opaque"));
+
+ qop = g_hash_table_lookup (auth_params, "qop");
+ if (qop) {
+ qop_options = soup_auth_digest_parse_qop (qop);
+ /* We only support auth */
+ if (!(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH))
+ ok = FALSE;
+ priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH;
+ } else
+ priv->qop = 0;
+
+ priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm"));
+ if (priv->algorithm == -1)
+ ok = FALSE;
+
+ stale = g_hash_table_lookup (auth_params, "stale");
+ if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp)
+ recompute_hex_a1 (priv);
+ else {
+ g_free (priv->user);
+ priv->user = NULL;
+ g_free (priv->cnonce);
+ priv->cnonce = NULL;
+ memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
+ memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
+ }
+
+ return ok;
+}
+
+static GSList *
+soup_auth_digest_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
+{
+ SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+ GSList *space = NULL;
+ SoupURI *uri;
+ char **dvec, *d, *dir, *slash;
+ int dix;
+
+ if (!priv->domain || !*priv->domain) {
+ /* If no domain directive, the protection space is the
+ * whole server.
+ */
+ return g_slist_prepend (NULL, g_strdup (""));
+ }
+
+ dvec = g_strsplit (priv->domain, " ", 0);
+ for (dix = 0; dvec[dix] != NULL; dix++) {
+ d = dvec[dix];
+ if (*d == '/')
+ dir = g_strdup (d);
+ else {
+ uri = soup_uri_new (d);
+ if (uri && uri->scheme == source_uri->scheme &&
+ uri->port == source_uri->port &&
+ !strcmp (uri->host, source_uri->host))
+ dir = g_strdup (uri->path);
+ else
+ dir = NULL;
+ if (uri)
+ soup_uri_free (uri);
+ }
+
+ if (dir) {
+ slash = strrchr (dir, '/');
+ if (slash && !slash[1])
+ *slash = '\0';
+
+ space = g_slist_prepend (space, dir);
+ }
+ }
+ g_strfreev (dvec);
+
+ return space;
+}
+
+void
+soup_auth_digest_compute_hex_urp (const char *username,
+ const char *realm,
+ const char *password,
+ char hex_urp[33])
+{
+ GChecksum *checksum;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *)username, strlen (username));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)realm, strlen (realm));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)password, strlen (password));
+ strncpy (hex_urp, g_checksum_get_string (checksum), 33);
+ g_checksum_free (checksum);
+}
+
+void
+soup_auth_digest_compute_hex_a1 (const char *hex_urp,
+ SoupAuthDigestAlgorithm algorithm,
+ const char *nonce,
+ const char *cnonce,
+ char hex_a1[33])
+{
+ if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) {
+ /* In MD5, A1 is just user:realm:password, so hex_A1
+ * is just hex_urp.
+ */
+ /* You'd think you could say "sizeof (hex_a1)" here,
+ * but you'd be wrong.
+ */
+ memcpy (hex_a1, hex_urp, 33);
+ } else {
+ GChecksum *checksum;
+
+ /* In MD5-sess, A1 is hex_urp:nonce:cnonce */
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *)hex_urp, strlen (hex_urp));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
+ strncpy (hex_a1, g_checksum_get_string (checksum), 33);
+ g_checksum_free (checksum);
+ }
+}
+
+static void
+recompute_hex_a1 (SoupAuthDigestPrivate *priv)
+{
+ soup_auth_digest_compute_hex_a1 (priv->hex_urp,
+ priv->algorithm,
+ priv->nonce,
+ priv->cnonce,
+ priv->hex_a1);
+}
+
+static void
+soup_auth_digest_authenticate (SoupAuth *auth, const char *username,
+ const char *password)
+{
+ SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+ char *bgen;
+
+ g_clear_pointer (&priv->cnonce, g_free);
+ g_clear_pointer (&priv->user, g_free);
+
+ /* Create client nonce */
+ bgen = g_strdup_printf ("%p:%lu:%lu",
+ auth,
+ (unsigned long) getpid (),
+ (unsigned long) time (0));
+ priv->cnonce = g_base64_encode ((guchar *)bgen, strlen (bgen));
+ g_free (bgen);
+
+ priv->user = g_strdup (username);
+
+ /* compute "URP" (user:realm:password) */
+ soup_auth_digest_compute_hex_urp (username, auth->realm,
+ password ? password : "",
+ priv->hex_urp);
+
+ /* And compute A1 from that */
+ recompute_hex_a1 (priv);
+}
+
+static gboolean
+soup_auth_digest_is_authenticated (SoupAuth *auth)
+{
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (SOUP_AUTH_DIGEST (auth));
+
+ return priv->cnonce != NULL;
+}
+
+void
+soup_auth_digest_compute_response (const char *method,
+ const char *uri,
+ const char *hex_a1,
+ SoupAuthDigestQop qop,
+ const char *nonce,
+ const char *cnonce,
+ int nc,
+ char response[33])
+{
+ char hex_a2[33];
+ GChecksum *checksum;
+
+ /* compute A2 */
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *)method, strlen (method));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)uri, strlen (uri));
+ memcpy (hex_a2, g_checksum_get_string (checksum), sizeof (char) * 33);
+ g_checksum_free (checksum);
+
+ /* compute KD */
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (guchar *)hex_a1, strlen (hex_a1));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
+ g_checksum_update (checksum, (guchar *)":", 1);
+
+ if (qop) {
+ char tmp[9];
+
+ g_snprintf (tmp, 9, "%.8x", nc);
+ g_checksum_update (checksum, (guchar *)tmp, strlen (tmp));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
+ g_checksum_update (checksum, (guchar *)":", 1);
+
+ if (!(qop & SOUP_AUTH_DIGEST_QOP_AUTH))
+ g_warn_if_reached ();
+ g_checksum_update (checksum, (guchar *)"auth", strlen ("auth"));
+ g_checksum_update (checksum, (guchar *)":", 1);
+ }
+
+ g_checksum_update (checksum, (guchar *)hex_a2, 32);
+ memcpy (response, g_checksum_get_string (checksum), sizeof (char) * 33);
+ g_checksum_free (checksum);
+}
+
+static void
+authentication_info_cb (SoupMessage *msg, gpointer data)
+{
+ SoupAuth *auth = data;
+ SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+ const char *header;
+ GHashTable *auth_params;
+ char *nextnonce;
+
+ if (auth != soup_message_get_auth (msg))
+ return;
+
+ header = soup_message_headers_get_one (msg->response_headers,
+ soup_auth_is_for_proxy (auth) ?
+ "Proxy-Authentication-Info" :
+ "Authentication-Info");
+ g_return_if_fail (header != NULL);
+
+ auth_params = soup_header_parse_param_list (header);
+ if (!auth_params)
+ return;
+
+ nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce"));
+ if (nextnonce) {
+ g_free (priv->nonce);
+ priv->nonce = nextnonce;
+ }
+
+ soup_header_free_param_list (auth_params);
+}
+
+static char *
+soup_auth_digest_get_authorization (SoupAuth *auth, SoupMessage *msg)
+{
+ SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
+ SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
+ char response[33], *token;
+ char *url, *algorithm;
+ GString *out;
+ SoupURI *uri;
+
+ uri = soup_message_get_uri (msg);
+ g_return_val_if_fail (uri != NULL, NULL);
+ url = soup_uri_to_string (uri, TRUE);
+
+ soup_auth_digest_compute_response (msg->method, url, priv->hex_a1,
+ priv->qop, priv->nonce,
+ priv->cnonce, priv->nc,
+ response);
+
+ out = g_string_new ("Digest ");
+
+ soup_header_g_string_append_param_quoted (out, "username", priv->user);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "realm", auth->realm);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "nonce", priv->nonce);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "uri", url);
+ g_string_append (out, ", ");
+ algorithm = soup_auth_digest_get_algorithm (priv->algorithm);
+ g_string_append_printf (out, "algorithm=%s", algorithm);
+ g_free (algorithm);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "response", response);
+
+ if (priv->opaque) {
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "opaque", priv->opaque);
+ }
+
+ if (priv->qop) {
+ char *qop = soup_auth_digest_get_qop (priv->qop);
+
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param_quoted (out, "cnonce", priv->cnonce);
+ g_string_append_printf (out, ", nc=%.8x, qop=%s",
+ priv->nc, qop);
+ g_free (qop);
+ }
+
+ g_free (url);
+
+ priv->nc++;
+
+ token = g_string_free (out, FALSE);
+
+ soup_message_add_header_handler (msg,
+ "got_headers",
+ soup_auth_is_for_proxy (auth) ?
+ "Proxy-Authentication-Info" :
+ "Authentication-Info",
+ G_CALLBACK (authentication_info_cb),
+ auth);
+ return token;
+}
+
+static void
+soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
+{
+ SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
+
+ auth_class->scheme_name = "Digest";
+ auth_class->strength = 5;
+
+ auth_class->get_protection_space = soup_auth_digest_get_protection_space;
+ auth_class->update = soup_auth_digest_update;
+ auth_class->authenticate = soup_auth_digest_authenticate;
+ auth_class->is_authenticated = soup_auth_digest_is_authenticated;
+ auth_class->get_authorization = soup_auth_digest_get_authorization;
+
+ object_class->finalize = soup_auth_digest_finalize;
+}
diff --git a/libsoup/auth/soup-auth-digest.h b/libsoup/auth/soup-auth-digest.h
new file mode 100644
index 00000000..59fd748e
--- /dev/null
+++ b/libsoup/auth/soup-auth-digest.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003, Ximian, Inc.
+ */
+
+#ifndef __SOUP_AUTH_DIGEST_H__
+#define __SOUP_AUTH_DIGEST_H__ 1
+
+#include "soup-auth.h"
+
+#define SOUP_AUTH_DIGEST(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigest))
+#define SOUP_AUTH_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestClass))
+#define SOUP_IS_AUTH_DIGEST(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_DIGEST))
+#define SOUP_IS_AUTH_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_DIGEST))
+#define SOUP_AUTH_DIGEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestClass))
+
+typedef struct {
+ SoupAuth parent;
+
+} SoupAuthDigest;
+
+typedef struct {
+ SoupAuthClass parent_class;
+
+} SoupAuthDigestClass;
+
+/* Utility routines (also used by SoupAuthDomainDigest) */
+
+typedef enum {
+ SOUP_AUTH_DIGEST_ALGORITHM_NONE,
+ SOUP_AUTH_DIGEST_ALGORITHM_MD5,
+ SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS
+} SoupAuthDigestAlgorithm;
+
+typedef enum {
+ SOUP_AUTH_DIGEST_QOP_AUTH = 1 << 0,
+ SOUP_AUTH_DIGEST_QOP_AUTH_INT = 1 << 1
+} SoupAuthDigestQop;
+
+SoupAuthDigestAlgorithm soup_auth_digest_parse_algorithm (const char *algorithm);
+char *soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm);
+
+SoupAuthDigestQop soup_auth_digest_parse_qop (const char *qop);
+char *soup_auth_digest_get_qop (SoupAuthDigestQop qop);
+
+void soup_auth_digest_compute_hex_urp (const char *username,
+ const char *realm,
+ const char *password,
+ char hex_urp[33]);
+void soup_auth_digest_compute_hex_a1 (const char *hex_urp,
+ SoupAuthDigestAlgorithm algorithm,
+ const char *nonce,
+ const char *cnonce,
+ char hex_a1[33]);
+void soup_auth_digest_compute_response (const char *method,
+ const char *uri,
+ const char *hex_a1,
+ SoupAuthDigestQop qop,
+ const char *nonce,
+ const char *cnonce,
+ int nc,
+ char response[33]);
+
+#endif /* __SOUP_AUTH_DIGEST_H__ */
diff --git a/libsoup/auth/soup-auth-domain-basic.c b/libsoup/auth/soup-auth-domain-basic.c
new file mode 100644
index 00000000..e15f37f3
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain-basic.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain-basic.c: HTTP Basic Authentication (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-domain-basic.h"
+#include "soup.h"
+
+/**
+ * SECTION:soup-auth-domain-basic
+ * @short_description: Server-side "Basic" authentication
+ *
+ * #SoupAuthDomainBasic handles the server side of HTTP "Basic" (ie,
+ * cleartext password) authentication.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_AUTH_CALLBACK,
+ PROP_AUTH_DATA,
+
+ LAST_PROP
+};
+
+typedef struct {
+ SoupAuthDomainBasicAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+} SoupAuthDomainBasicPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDomainBasic, soup_auth_domain_basic, SOUP_TYPE_AUTH_DOMAIN)
+
+static void
+soup_auth_domain_basic_init (SoupAuthDomainBasic *basic)
+{
+}
+
+static void
+soup_auth_domain_basic_finalize (GObject *object)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_basic_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_basic_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_basic_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * soup_auth_domain_basic_new:
+ * @optname1: name of first option, or %NULL
+ * @...: option name/value pairs
+ *
+ * Creates a #SoupAuthDomainBasic. You must set the
+ * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
+ * returned with the authentication challenge to the client. Other
+ * parameters are optional.
+ *
+ * Return value: the new #SoupAuthDomain
+ **/
+SoupAuthDomain *
+soup_auth_domain_basic_new (const char *optname1, ...)
+{
+ SoupAuthDomain *domain;
+ va_list ap;
+
+ va_start (ap, optname1);
+ domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_BASIC,
+ optname1, ap);
+ va_end (ap);
+
+ g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
+
+ return domain;
+}
+
+/**
+ * SoupAuthDomainBasicAuthCallback:
+ * @domain: (type SoupAuthDomainBasic): the domain
+ * @msg: the message being authenticated
+ * @username: the username provided by the client
+ * @password: the password provided by the client
+ * @user_data: the data passed to soup_auth_domain_basic_set_auth_callback()
+ *
+ * Callback used by #SoupAuthDomainBasic for authentication purposes.
+ * The application should verify that @username and @password and valid
+ * and return %TRUE or %FALSE.
+ *
+ * If you are maintaining your own password database (rather than
+ * using the password to authenticate against some other system like
+ * PAM or a remote server), you should make sure you know what you are
+ * doing. In particular, don't store cleartext passwords, or
+ * easily-computed hashes of cleartext passwords, even if you don't
+ * care that much about the security of your server, because users
+ * will frequently use the same password for multiple sites, and so
+ * compromising any site with a cleartext (or easily-cracked) password
+ * database may give attackers access to other more-interesting sites
+ * as well.
+ *
+ * Return value: %TRUE if @username and @password are valid
+ **/
+
+/**
+ * soup_auth_domain_basic_set_auth_callback:
+ * @domain: (type SoupAuthDomainBasic): the domain
+ * @callback: the callback
+ * @user_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @user_data when @domain
+ * is destroyed
+ *
+ * Sets the callback that @domain will use to authenticate incoming
+ * requests. For each request containing authorization, @domain will
+ * invoke the callback, and then either accept or reject the request
+ * based on @callback's return value.
+ *
+ * You can also set the auth callback by setting the
+ * %SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK and
+ * %SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA properties, which can also be
+ * used to set the callback at construct time.
+ **/
+void
+soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainBasicAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = callback;
+ priv->auth_data = user_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA);
+}
+
+static void
+pw_free (char *pw)
+{
+ memset (pw, 0, strlen (pw));
+ g_free (pw);
+}
+
+static gboolean
+parse_basic (SoupMessage *msg, const char *header,
+ char **username, char **password)
+{
+ char *decoded, *colon;
+ gsize len, plen;
+
+ if (!header || (strncmp (header, "Basic ", 6) != 0))
+ return FALSE;
+
+ decoded = (char *)g_base64_decode (header + 6, &len);
+ if (!decoded)
+ return FALSE;
+
+ colon = memchr (decoded, ':', len);
+ if (!colon) {
+ pw_free (decoded);
+ return FALSE;
+ }
+ *colon = '\0';
+ plen = len - (colon - decoded) - 1;
+
+ *password = g_strndup (colon + 1, plen);
+ memset (colon + 1, 0, plen);
+ *username = decoded;
+ return TRUE;
+}
+
+static char *
+soup_auth_domain_basic_accepts (SoupAuthDomain *domain, SoupMessage *msg,
+ const char *header)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
+ char *username, *password;
+ gboolean ok = FALSE;
+
+ if (!parse_basic (msg, header, &username, &password))
+ return NULL;
+
+ if (priv->auth_callback) {
+ ok = priv->auth_callback (domain, msg, username, password,
+ priv->auth_data);
+ } else {
+ ok = soup_auth_domain_try_generic_auth_callback (
+ domain, msg, username);
+ }
+
+ pw_free (password);
+
+ if (ok)
+ return username;
+ else {
+ g_free (username);
+ return NULL;
+ }
+}
+
+static char *
+soup_auth_domain_basic_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ GString *challenge;
+
+ challenge = g_string_new ("Basic ");
+ soup_header_g_string_append_param (challenge, "realm", soup_auth_domain_get_realm (domain));
+ return g_string_free (challenge, FALSE);
+}
+
+static gboolean
+soup_auth_domain_basic_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ const char *header;
+ char *msg_username, *msg_password;
+ gboolean ok;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ "Authorization");
+ if (!parse_basic (msg, header, &msg_username, &msg_password))
+ return FALSE;
+
+ ok = (!strcmp (username, msg_username) &&
+ !strcmp (password, msg_password));
+ g_free (msg_username);
+ pw_free (msg_password);
+
+ return ok;
+}
+
+static void
+soup_auth_domain_basic_class_init (SoupAuthDomainBasicClass *basic_class)
+{
+ SoupAuthDomainClass *auth_domain_class =
+ SOUP_AUTH_DOMAIN_CLASS (basic_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (basic_class);
+
+ auth_domain_class->accepts = soup_auth_domain_basic_accepts;
+ auth_domain_class->challenge = soup_auth_domain_basic_challenge;
+ auth_domain_class->check_password = soup_auth_domain_basic_check_password;
+
+ object_class->finalize = soup_auth_domain_basic_finalize;
+ object_class->set_property = soup_auth_domain_basic_set_property;
+ object_class->get_property = soup_auth_domain_basic_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomainBasic:auth-callback property.
+ * (The #SoupAuthDomainBasicAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainBasic:auth-callback: (type SoupAuthDomainBasicAuthCallback)
+ *
+ * The #SoupAuthDomainBasicAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK,
+ "Authentication callback",
+ "Password-checking callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomainBasic:auth-data property.
+ * (The data to pass to the #SoupAuthDomainBasicAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainBasic:auth-data:
+ *
+ * The data to pass to the #SoupAuthDomainBasicAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to authentication callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
diff --git a/libsoup/auth/soup-auth-domain-basic.h b/libsoup/auth/soup-auth-domain-basic.h
new file mode 100644
index 00000000..79ee6163
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain-basic.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifndef __SOUP_AUTH_DOMAIN_BASIC_H__
+#define __SOUP_AUTH_DOMAIN_BASIC_H__ 1
+
+#include "soup-auth-domain.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN_BASIC (soup_auth_domain_basic_get_type ())
+#define SOUP_AUTH_DOMAIN_BASIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasic))
+#define SOUP_AUTH_DOMAIN_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasicClass))
+#define SOUP_IS_AUTH_DOMAIN_BASIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC))
+#define SOUP_IS_AUTH_DOMAIN_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC))
+#define SOUP_AUTH_DOMAIN_BASIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasicClass))
+
+typedef struct {
+ SoupAuthDomain parent;
+
+} SoupAuthDomainBasic;
+
+typedef struct {
+ SoupAuthDomainClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved1) (void);
+ void (*_libsoup_reserved2) (void);
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+} SoupAuthDomainBasicClass;
+
+#define SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK "auth-callback"
+#define SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA "auth-data"
+
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_domain_basic_get_type (void);
+
+SOUP_AVAILABLE_IN_2_4
+SoupAuthDomain *soup_auth_domain_basic_new (const char *optname1,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef gboolean (*SoupAuthDomainBasicAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainBasicAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify);
+
+G_END_DECLS
+
+#endif /* __SOUP_AUTH_DOMAIN_BASIC_H__ */
diff --git a/libsoup/auth/soup-auth-domain-digest.c b/libsoup/auth/soup-auth-domain-digest.c
new file mode 100644
index 00000000..b69b5829
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain-digest.c
@@ -0,0 +1,450 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain-digest.c: HTTP Digest Authentication (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "soup-auth-domain-digest.h"
+#include "soup.h"
+#include "soup-auth-digest.h"
+
+/**
+ * SECTION:soup-auth-domain-digest
+ * @short_description: Server-side "Digest" authentication
+ *
+ * #SoupAuthDomainDigest handles the server side of HTTP "Digest"
+ * authentication.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_AUTH_CALLBACK,
+ PROP_AUTH_DATA,
+
+ LAST_PROP
+};
+
+typedef struct {
+ SoupAuthDomainDigestAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+
+} SoupAuthDomainDigestPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
+
+static void
+soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
+{
+}
+
+static void
+soup_auth_domain_digest_finalize (GObject *object)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_digest_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_digest_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * soup_auth_domain_digest_new:
+ * @optname1: name of first option, or %NULL
+ * @...: option name/value pairs
+ *
+ * Creates a #SoupAuthDomainDigest. You must set the
+ * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
+ * returned with the authentication challenge to the client. Other
+ * parameters are optional.
+ *
+ * Return value: the new #SoupAuthDomain
+ **/
+SoupAuthDomain *
+soup_auth_domain_digest_new (const char *optname1, ...)
+{
+ SoupAuthDomain *domain;
+ va_list ap;
+
+ va_start (ap, optname1);
+ domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
+ optname1, ap);
+ va_end (ap);
+
+ g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
+
+ return domain;
+}
+
+/**
+ * SoupAuthDomainDigestAuthCallback:
+ * @domain: (type SoupAuthDomainDigest): the domain
+ * @msg: the message being authenticated
+ * @username: the username provided by the client
+ * @user_data: the data passed to soup_auth_domain_digest_set_auth_callback()
+ *
+ * Callback used by #SoupAuthDomainDigest for authentication purposes.
+ * The application should look up @username in its password database,
+ * and return the corresponding encoded password (see
+ * soup_auth_domain_digest_encode_password()).
+ *
+ * Return value: (nullable): the encoded password, or %NULL if
+ * @username is not a valid user. @domain will free the password when
+ * it is done with it.
+ **/
+
+/**
+ * soup_auth_domain_digest_set_auth_callback:
+ * @domain: (type SoupAuthDomainDigest): the domain
+ * @callback: the callback
+ * @user_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @user_data when @domain
+ * is destroyed
+ *
+ * Sets the callback that @domain will use to authenticate incoming
+ * requests. For each request containing authorization, @domain will
+ * invoke the callback, and then either accept or reject the request
+ * based on @callback's return value.
+ *
+ * You can also set the auth callback by setting the
+ * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK and
+ * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA properties, which can also be
+ * used to set the callback at construct time.
+ **/
+void
+soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainDigestAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = callback;
+ priv->auth_data = user_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA);
+}
+
+static gboolean
+check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
+ GHashTable *params, const char *username,
+ const char *hex_urp)
+{
+ const char *uri, *qop, *realm, *msg_username;
+ const char *nonce, *nc, *cnonce, *response;
+ char hex_a1[33], computed_response[33];
+ int nonce_count;
+ SoupURI *dig_uri, *req_uri;
+
+ msg_username = g_hash_table_lookup (params, "username");
+ if (!msg_username || strcmp (msg_username, username) != 0)
+ return FALSE;
+
+ /* Check uri */
+ uri = g_hash_table_lookup (params, "uri");
+ if (!uri)
+ return FALSE;
+
+ req_uri = soup_message_get_uri (msg);
+ dig_uri = soup_uri_new (uri);
+ if (dig_uri) {
+ if (!soup_uri_equal (dig_uri, req_uri)) {
+ soup_uri_free (dig_uri);
+ return FALSE;
+ }
+ soup_uri_free (dig_uri);
+ } else {
+ char *req_path;
+ char *dig_path;
+
+ req_path = soup_uri_to_string (req_uri, TRUE);
+ dig_path = soup_uri_decode (uri);
+
+ if (strcmp (dig_path, req_path) != 0) {
+ g_free (req_path);
+ g_free (dig_path);
+ return FALSE;
+ }
+ g_free (req_path);
+ g_free (dig_path);
+ }
+
+ /* Check qop; we only support "auth" for now */
+ qop = g_hash_table_lookup (params, "qop");
+ if (!qop || strcmp (qop, "auth") != 0)
+ return FALSE;
+
+ /* Check realm */
+ realm = g_hash_table_lookup (params, "realm");
+ if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
+ return FALSE;
+
+ nonce = g_hash_table_lookup (params, "nonce");
+ if (!nonce)
+ return FALSE;
+ nc = g_hash_table_lookup (params, "nc");
+ if (!nc)
+ return FALSE;
+ nonce_count = strtoul (nc, NULL, 16);
+ if (nonce_count <= 0)
+ return FALSE;
+ cnonce = g_hash_table_lookup (params, "cnonce");
+ if (!cnonce)
+ return FALSE;
+ response = g_hash_table_lookup (params, "response");
+ if (!response)
+ return FALSE;
+
+ soup_auth_digest_compute_hex_a1 (hex_urp,
+ SOUP_AUTH_DIGEST_ALGORITHM_MD5,
+ nonce, cnonce, hex_a1);
+ soup_auth_digest_compute_response (msg->method, uri,
+ hex_a1,
+ SOUP_AUTH_DIGEST_QOP_AUTH,
+ nonce, cnonce, nonce_count,
+ computed_response);
+ return strcmp (response, computed_response) == 0;
+}
+
+static char *
+soup_auth_domain_digest_accepts (SoupAuthDomain *domain, SoupMessage *msg,
+ const char *header)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
+ GHashTable *params;
+ const char *username;
+ gboolean accept = FALSE;
+ char *ret_user;
+
+ if (strncmp (header, "Digest ", 7) != 0)
+ return NULL;
+
+ params = soup_header_parse_param_list (header + 7);
+ if (!params)
+ return NULL;
+
+ username = g_hash_table_lookup (params, "username");
+ if (!username) {
+ soup_header_free_param_list (params);
+ return NULL;
+ }
+
+ if (priv->auth_callback) {
+ char *hex_urp;
+
+ hex_urp = priv->auth_callback (domain, msg, username,
+ priv->auth_data);
+ if (hex_urp) {
+ accept = check_hex_urp (domain, msg, params,
+ username, hex_urp);
+ g_free (hex_urp);
+ } else
+ accept = FALSE;
+ } else {
+ accept = soup_auth_domain_try_generic_auth_callback (
+ domain, msg, username);
+ }
+
+ ret_user = accept ? g_strdup (username) : NULL;
+ soup_header_free_param_list (params);
+ return ret_user;
+}
+
+static char *
+soup_auth_domain_digest_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ GString *str;
+
+ str = g_string_new ("Digest ");
+ soup_header_g_string_append_param_quoted (str, "realm", soup_auth_domain_get_realm (domain));
+ g_string_append_printf (str, ", nonce=\"%lu%lu\"",
+ (unsigned long) msg,
+ (unsigned long) time (0));
+ g_string_append_printf (str, ", qop=\"auth\"");
+ g_string_append_printf (str, ", algorithm=MD5");
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * soup_auth_domain_digest_encode_password:
+ * @username: a username
+ * @realm: an auth realm name
+ * @password: the password for @username in @realm
+ *
+ * Encodes the username/realm/password triplet for Digest
+ * authentication. (That is, it returns a stringified MD5 hash of
+ * @username, @realm, and @password concatenated together). This is
+ * the form that is needed as the return value of
+ * #SoupAuthDomainDigest's auth handler.
+ *
+ * For security reasons, you should store the encoded hash, rather
+ * than storing the cleartext password itself and calling this method
+ * only when you need to verify it. This way, if your server is
+ * compromised, the attackers will not gain access to cleartext
+ * passwords which might also be usable at other sites. (Note also
+ * that the encoded password returned by this method is identical to
+ * the encoded password stored in an Apache .htdigest file.)
+ *
+ * Return value: the encoded password
+ **/
+char *
+soup_auth_domain_digest_encode_password (const char *username,
+ const char *realm,
+ const char *password)
+{
+ char hex_urp[33];
+
+ soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
+ return g_strdup (hex_urp);
+}
+
+static gboolean
+soup_auth_domain_digest_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ const char *header;
+ GHashTable *params;
+ const char *msg_username;
+ char hex_urp[33];
+ gboolean accept;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ "Authorization");
+ if (!header || (strncmp (header, "Digest ", 7) != 0))
+ return FALSE;
+
+ params = soup_header_parse_param_list (header + 7);
+ if (!params)
+ return FALSE;
+
+ msg_username = g_hash_table_lookup (params, "username");
+ if (!msg_username || strcmp (msg_username, username) != 0) {
+ soup_header_free_param_list (params);
+ return FALSE;
+ }
+
+ soup_auth_digest_compute_hex_urp (username,
+ soup_auth_domain_get_realm (domain),
+ password, hex_urp);
+ accept = check_hex_urp (domain, msg, params, username, hex_urp);
+ soup_header_free_param_list (params);
+ return accept;
+}
+
+static void
+soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
+{
+ SoupAuthDomainClass *auth_domain_class =
+ SOUP_AUTH_DOMAIN_CLASS (digest_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
+
+ auth_domain_class->accepts = soup_auth_domain_digest_accepts;
+ auth_domain_class->challenge = soup_auth_domain_digest_challenge;
+ auth_domain_class->check_password = soup_auth_domain_digest_check_password;
+
+ object_class->finalize = soup_auth_domain_digest_finalize;
+ object_class->set_property = soup_auth_domain_digest_set_property;
+ object_class->get_property = soup_auth_domain_digest_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomainDigest:auth-callback property.
+ * (The #SoupAuthDomainDigestAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainDigest:auth-callback: (type SoupAuthDomainDigestAuthCallback)
+ *
+ * The #SoupAuthDomainDigestAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK,
+ "Authentication callback",
+ "Password-finding callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomainDigest:auth-callback property.
+ * (The #SoupAuthDomainDigestAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainDigest:auth-data:
+ *
+ * The data to pass to the #SoupAuthDomainDigestAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to authentication callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
diff --git a/libsoup/auth/soup-auth-domain-digest.h b/libsoup/auth/soup-auth-domain-digest.h
new file mode 100644
index 00000000..5fbe6c94
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain-digest.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifndef __SOUP_AUTH_DOMAIN_DIGEST_H__
+#define __SOUP_AUTH_DOMAIN_DIGEST_H__ 1
+
+#include "soup-auth-domain.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN_DIGEST (soup_auth_domain_digest_get_type ())
+#define SOUP_AUTH_DOMAIN_DIGEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigest))
+#define SOUP_AUTH_DOMAIN_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestClass))
+#define SOUP_IS_AUTH_DOMAIN_DIGEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST))
+#define SOUP_IS_AUTH_DOMAIN_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST))
+#define SOUP_AUTH_DOMAIN_DIGEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestClass))
+
+typedef struct {
+ SoupAuthDomain parent;
+
+} SoupAuthDomainDigest;
+
+typedef struct {
+ SoupAuthDomainClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved1) (void);
+ void (*_libsoup_reserved2) (void);
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+} SoupAuthDomainDigestClass;
+
+#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK "auth-callback"
+#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA "auth-data"
+
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_domain_digest_get_type (void);
+
+SOUP_AVAILABLE_IN_2_4
+SoupAuthDomain *soup_auth_domain_digest_new (const char *optname1,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef char * (*SoupAuthDomainDigestAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainDigestAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify);
+
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_domain_digest_encode_password (const char *username,
+ const char *realm,
+ const char *password);
+
+G_END_DECLS
+
+#endif /* __SOUP_AUTH_DOMAIN_DIGEST_H__ */
diff --git a/libsoup/auth/soup-auth-domain.c b/libsoup/auth/soup-auth-domain.c
new file mode 100644
index 00000000..8785c474
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain.c
@@ -0,0 +1,635 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain.c: HTTP Authentication Domain (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-domain.h"
+#include "soup.h"
+#include "soup-path-map.h"
+
+/**
+ * SECTION:soup-auth-domain
+ * @short_description: Server-side authentication
+ * @see_also: #SoupServer
+ *
+ * A #SoupAuthDomain manages authentication for all or part of a
+ * #SoupServer. To make a server require authentication, first create
+ * an appropriate subclass of #SoupAuthDomain, and then add it to the
+ * server with soup_server_add_auth_domain().
+ *
+ * In order for an auth domain to have any effect, you must add one or
+ * more paths to it (via soup_auth_domain_add_path() or the
+ * %SOUP_AUTH_DOMAIN_ADD_PATH property). To require authentication for
+ * all ordinary requests, add the path "/". (Note that this does not
+ * include the special "*" URI (eg, "OPTIONS *"), which must be added
+ * as a separate path if you want to cover it.)
+ *
+ * If you need greater control over which requests should and
+ * shouldn't be authenticated, add paths covering everything you
+ * <emphasis>might</emphasis> want authenticated, and then use a
+ * filter (soup_auth_domain_set_filter()) to bypass authentication for
+ * those requests that don't need it.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_REALM,
+ PROP_PROXY,
+ PROP_ADD_PATH,
+ PROP_REMOVE_PATH,
+ PROP_FILTER,
+ PROP_FILTER_DATA,
+ PROP_GENERIC_AUTH_CALLBACK,
+ PROP_GENERIC_AUTH_DATA,
+
+ LAST_PROP
+};
+
+typedef struct {
+ char *realm;
+ gboolean proxy;
+ SoupPathMap *paths;
+
+ SoupAuthDomainFilter filter;
+ gpointer filter_data;
+ GDestroyNotify filter_dnotify;
+
+ SoupAuthDomainGenericAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+
+} SoupAuthDomainPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT)
+
+static void
+soup_auth_domain_init (SoupAuthDomain *domain)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ priv->paths = soup_path_map_new (NULL);
+}
+
+static void
+soup_auth_domain_finalize (GObject *object)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (SOUP_AUTH_DOMAIN (object));
+
+ g_free (priv->realm);
+ soup_path_map_free (priv->paths);
+
+ if (priv->filter_dnotify)
+ priv->filter_dnotify (priv->filter_data);
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object);
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (auth_domain);
+
+ switch (prop_id) {
+ case PROP_REALM:
+ g_free (priv->realm);
+ priv->realm = g_value_dup_string (value);
+ break;
+ case PROP_PROXY:
+ priv->proxy = g_value_get_boolean (value);
+ break;
+ case PROP_ADD_PATH:
+ soup_auth_domain_add_path (auth_domain,
+ g_value_get_string (value));
+ break;
+ case PROP_REMOVE_PATH:
+ soup_auth_domain_remove_path (auth_domain,
+ g_value_get_string (value));
+ break;
+ case PROP_FILTER:
+ priv->filter = g_value_get_pointer (value);
+ break;
+ case PROP_FILTER_DATA:
+ if (priv->filter_dnotify) {
+ priv->filter_dnotify (priv->filter_data);
+ priv->filter_dnotify = NULL;
+ }
+ priv->filter_data = g_value_get_pointer (value);
+ break;
+ case PROP_GENERIC_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_GENERIC_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (SOUP_AUTH_DOMAIN (object));
+
+ switch (prop_id) {
+ case PROP_REALM:
+ g_value_set_string (value, priv->realm);
+ break;
+ case PROP_PROXY:
+ g_value_set_boolean (value, priv->proxy);
+ break;
+ case PROP_FILTER:
+ g_value_set_pointer (value, priv->filter);
+ break;
+ case PROP_FILTER_DATA:
+ g_value_set_pointer (value, priv->filter_data);
+ break;
+ case PROP_GENERIC_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_GENERIC_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class);
+
+ object_class->finalize = soup_auth_domain_finalize;
+ object_class->set_property = soup_auth_domain_set_property;
+ object_class->get_property = soup_auth_domain_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_REALM:
+ *
+ * Alias for the #SoupAuthDomain:realm property. (The realm of
+ * this auth domain.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REALM,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_REALM,
+ "Realm",
+ "The realm of this auth domain",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_PROXY:
+ *
+ * Alias for the #SoupAuthDomain:proxy property. (Whether or
+ * not this is a proxy auth domain.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_PROXY,
+ g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY,
+ "Proxy",
+ "Whether or not this is a proxy auth domain",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_ADD_PATH:
+ *
+ * Alias for the #SoupAuthDomain:add-path property. (Shortcut
+ * for calling soup_auth_domain_add_path().)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_ADD_PATH,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH,
+ "Add a path",
+ "Add a path covered by this auth domain",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_REMOVE_PATH:
+ *
+ * Alias for the #SoupAuthDomain:remove-path property.
+ * (Shortcut for calling soup_auth_domain_remove_path().)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REMOVE_PATH,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH,
+ "Remove a path",
+ "Remove a path covered by this auth domain",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_FILTER:
+ *
+ * Alias for the #SoupAuthDomain:filter property. (The
+ * #SoupAuthDomainFilter for the domain.)
+ **/
+ /**
+ * SoupAuthDomain:filter: (type SoupAuthDomainFilter)
+ *
+ * The #SoupAuthDomainFilter for the domain
+ */
+ g_object_class_install_property (
+ object_class, PROP_FILTER,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER,
+ "Filter",
+ "A filter for deciding whether or not to require authentication",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_FILTER_DATA:
+ *
+ * Alias for the #SoupAuthDomain:filter-data property. (Data
+ * to pass to the #SoupAuthDomainFilter.)
+ **/
+ /**
+ * SoupAuthDomain:generic-auth-callback: (type SoupAuthDomainGenericAuthCallback)
+ *
+ * The #SoupAuthDomainGenericAuthCallback for the domain
+ */
+ g_object_class_install_property (
+ object_class, PROP_FILTER_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA,
+ "Filter data",
+ "Data to pass to filter",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomain:generic-auth-callback property.
+ * (The #SoupAuthDomainGenericAuthCallback.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_GENERIC_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK,
+ "Generic authentication callback",
+ "An authentication callback that can be used with any SoupAuthDomain subclass",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomain:generic-auth-data property.
+ * (The data to pass to the #SoupAuthDomainGenericAuthCallback.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_GENERIC_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to auth callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * soup_auth_domain_add_path:
+ * @domain: a #SoupAuthDomain
+ * @path: the path to add to @domain
+ *
+ * Adds @path to @domain, such that requests under @path on @domain's
+ * server will require authentication (unless overridden by
+ * soup_auth_domain_remove_path() or soup_auth_domain_set_filter()).
+ *
+ * You can also add paths by setting the %SOUP_AUTH_DOMAIN_ADD_PATH
+ * property, which can also be used to add one or more paths at
+ * construct time.
+ **/
+void
+soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ /* "" should not match "*" */
+ if (!*path)
+ path = "/";
+
+ soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE));
+}
+
+/**
+ * soup_auth_domain_remove_path:
+ * @domain: a #SoupAuthDomain
+ * @path: the path to remove from @domain
+ *
+ * Removes @path from @domain, such that requests under @path on
+ * @domain's server will NOT require authentication.
+ *
+ * This is not simply an undo-er for soup_auth_domain_add_path(); it
+ * can be used to "carve out" a subtree that does not require
+ * authentication inside a hierarchy that does. Note also that unlike
+ * with soup_auth_domain_add_path(), this cannot be overridden by
+ * adding a filter, as filters can only bypass authentication that
+ * would otherwise be required, not require it where it would
+ * otherwise be unnecessary.
+ *
+ * You can also remove paths by setting the
+ * %SOUP_AUTH_DOMAIN_REMOVE_PATH property, which can also be used to
+ * remove one or more paths at construct time.
+ **/
+void
+soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ /* "" should not match "*" */
+ if (!*path)
+ path = "/";
+
+ soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE));
+}
+
+/**
+ * SoupAuthDomainFilter:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ * @user_data: the data passed to soup_auth_domain_set_filter()
+ *
+ * The prototype for a #SoupAuthDomain filter; see
+ * soup_auth_domain_set_filter() for details.
+ *
+ * Return value: %TRUE if @msg requires authentication, %FALSE if not.
+ **/
+
+/**
+ * soup_auth_domain_set_filter:
+ * @domain: a #SoupAuthDomain
+ * @filter: the auth filter for @domain
+ * @filter_data: data to pass to @filter
+ * @dnotify: destroy notifier to free @filter_data when @domain
+ * is destroyed
+ *
+ * Adds @filter as an authentication filter to @domain. The filter
+ * gets a chance to bypass authentication for certain requests that
+ * would otherwise require it. Eg, it might check the message's path
+ * in some way that is too complicated to do via the other methods, or
+ * it might check the message's method, and allow GETs but not PUTs.
+ *
+ * The filter function returns %TRUE if the request should still
+ * require authentication, or %FALSE if authentication is unnecessary
+ * for this request.
+ *
+ * To help prevent security holes, your filter should return %TRUE by
+ * default, and only return %FALSE under specifically-tested
+ * circumstances, rather than the other way around. Eg, in the example
+ * above, where you want to authenticate PUTs but not GETs, you should
+ * check if the method is GET and return %FALSE in that case, and then
+ * return %TRUE for all other methods (rather than returning %TRUE for
+ * PUT and %FALSE for all other methods). This way if it turned out
+ * (now or later) that some paths supported additional methods besides
+ * GET and PUT, those methods would default to being NOT allowed for
+ * unauthenticated users.
+ *
+ * You can also set the filter by setting the %SOUP_AUTH_DOMAIN_FILTER
+ * and %SOUP_AUTH_DOMAIN_FILTER_DATA properties, which can also be
+ * used to set the filter at construct time.
+ **/
+void
+soup_auth_domain_set_filter (SoupAuthDomain *domain,
+ SoupAuthDomainFilter filter,
+ gpointer filter_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->filter_dnotify)
+ priv->filter_dnotify (priv->filter_data);
+
+ priv->filter = filter;
+ priv->filter_data = filter_data;
+ priv->filter_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA);
+}
+
+/**
+ * soup_auth_domain_get_realm:
+ * @domain: a #SoupAuthDomain
+ *
+ * Gets the realm name associated with @domain
+ *
+ * Return value: @domain's realm
+ **/
+const char *
+soup_auth_domain_get_realm (SoupAuthDomain *domain)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ return priv->realm;
+}
+
+/**
+ * SoupAuthDomainGenericAuthCallback:
+ * @domain: a #SoupAuthDomain
+ * @msg: the #SoupMessage being authenticated
+ * @username: the username from @msg
+ * @user_data: the data passed to
+ * soup_auth_domain_set_generic_auth_callback()
+ *
+ * The prototype for a #SoupAuthDomain generic authentication callback.
+ *
+ * The callback should look up the user's password, call
+ * soup_auth_domain_check_password(), and use the return value from
+ * that method as its own return value.
+ *
+ * In general, for security reasons, it is preferable to use the
+ * auth-domain-specific auth callbacks (eg,
+ * #SoupAuthDomainBasicAuthCallback and
+ * #SoupAuthDomainDigestAuthCallback), because they don't require
+ * keeping a cleartext password database. Most users will use the same
+ * password for many different sites, meaning if any site with a
+ * cleartext password database is compromised, accounts on other
+ * servers might be compromised as well. For many of the cases where
+ * #SoupServer is used, this is not really relevant, but it may still
+ * be worth considering.
+ *
+ * Return value: %TRUE if @msg is authenticated, %FALSE if not.
+ **/
+
+/**
+ * soup_auth_domain_set_generic_auth_callback:
+ * @domain: a #SoupAuthDomain
+ * @auth_callback: the auth callback
+ * @auth_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @auth_data when @domain
+ * is destroyed
+ *
+ * Sets @auth_callback as an authentication-handling callback for
+ * @domain. Whenever a request comes in to @domain which cannot be
+ * authenticated via a domain-specific auth callback (eg,
+ * #SoupAuthDomainDigestAuthCallback), the generic auth callback
+ * will be invoked. See #SoupAuthDomainGenericAuthCallback for information
+ * on what the callback should do.
+ **/
+void
+soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainGenericAuthCallback auth_callback,
+ gpointer auth_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = auth_callback;
+ priv->auth_data = auth_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA);
+}
+
+gboolean
+soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->auth_callback)
+ return priv->auth_callback (domain, msg, username, priv->auth_data);
+ else
+ return FALSE;
+}
+
+/**
+ * soup_auth_domain_check_password:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ * @username: a username
+ * @password: a password
+ *
+ * Checks if @msg authenticates to @domain via @username and
+ * @password. This would normally be called from a
+ * #SoupAuthDomainGenericAuthCallback.
+ *
+ * Return value: whether or not the message is authenticated
+ **/
+gboolean
+soup_auth_domain_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->check_password (domain, msg,
+ username,
+ password);
+}
+
+/**
+ * soup_auth_domain_covers:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Checks if @domain requires @msg to be authenticated (according to
+ * its paths and filter function). This does not actually look at
+ * whether @msg <emphasis>is</emphasis> authenticated, merely whether
+ * or not it needs to be.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ *
+ * Return value: %TRUE if @domain requires @msg to be authenticated
+ **/
+gboolean
+soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ const char *path;
+
+ if (!priv->proxy) {
+ path = soup_message_get_uri (msg)->path;
+ if (!soup_path_map_lookup (priv->paths, path))
+ return FALSE;
+ }
+
+ if (priv->filter && !priv->filter (domain, msg, priv->filter_data))
+ return FALSE;
+ else
+ return TRUE;
+}
+
+/**
+ * soup_auth_domain_accepts:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Checks if @msg contains appropriate authorization for @domain to
+ * accept it. Mirroring soup_auth_domain_covers(), this does not check
+ * whether or not @domain <emphasis>cares</emphasis> if @msg is
+ * authorized.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ *
+ * Return value: (nullable): the username that @msg has authenticated
+ * as, if in fact it has authenticated. %NULL otherwise.
+ **/
+char *
+soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ const char *header;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ priv->proxy ?
+ "Proxy-Authorization" :
+ "Authorization");
+ if (!header)
+ return NULL;
+ return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header);
+}
+
+/**
+ * soup_auth_domain_challenge: (virtual challenge)
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg,
+ * requesting that the client authenticate, and sets @msg's status
+ * accordingly.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ **/
+void
+soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ char *challenge;
+
+ challenge = SOUP_AUTH_DOMAIN_GET_CLASS (domain)->challenge (domain, msg);
+ soup_message_set_status (msg, priv->proxy ?
+ SOUP_STATUS_PROXY_UNAUTHORIZED :
+ SOUP_STATUS_UNAUTHORIZED);
+ soup_message_headers_append (msg->response_headers,
+ priv->proxy ?
+ "Proxy-Authenticate" :
+ "WWW-Authenticate",
+ challenge);
+ g_free (challenge);
+}
diff --git a/libsoup/auth/soup-auth-domain.h b/libsoup/auth/soup-auth-domain.h
new file mode 100644
index 00000000..0fdbbb29
--- /dev/null
+++ b/libsoup/auth/soup-auth-domain.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifndef __SOUP_AUTH_DOMAIN_H__
+#define __SOUP_AUTH_DOMAIN_H__ 1
+
+#include "soup-types.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN (soup_auth_domain_get_type ())
+#define SOUP_AUTH_DOMAIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomain))
+#define SOUP_AUTH_DOMAIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainClass))
+#define SOUP_IS_AUTH_DOMAIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN))
+#define SOUP_IS_AUTH_DOMAIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN))
+#define SOUP_AUTH_DOMAIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainClass))
+
+struct _SoupAuthDomain {
+ GObject parent;
+
+};
+
+typedef struct {
+ GObjectClass parent_class;
+
+ char * (*accepts) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *header);
+ char * (*challenge) (SoupAuthDomain *domain,
+ SoupMessage *msg);
+ gboolean (*check_password) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password);
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved2) (void);
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+} SoupAuthDomainClass;
+
+#define SOUP_AUTH_DOMAIN_REALM "realm"
+#define SOUP_AUTH_DOMAIN_PROXY "proxy"
+#define SOUP_AUTH_DOMAIN_ADD_PATH "add-path"
+#define SOUP_AUTH_DOMAIN_REMOVE_PATH "remove-path"
+#define SOUP_AUTH_DOMAIN_FILTER "filter"
+#define SOUP_AUTH_DOMAIN_FILTER_DATA "filter-data"
+#define SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK "generic-auth-callback"
+#define SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA "generic-auth-data"
+
+typedef gboolean (*SoupAuthDomainFilter) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ gpointer user_data);
+
+typedef gboolean (*SoupAuthDomainGenericAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_domain_get_type (void);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_add_path (SoupAuthDomain *domain,
+ const char *path);
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_remove_path (SoupAuthDomain *domain,
+ const char *path);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_set_filter (SoupAuthDomain *domain,
+ SoupAuthDomainFilter filter,
+ gpointer filter_data,
+ GDestroyNotify dnotify);
+
+SOUP_AVAILABLE_IN_2_4
+const char *soup_auth_domain_get_realm (SoupAuthDomain *domain);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainGenericAuthCallback auth_callback,
+ gpointer auth_data,
+ GDestroyNotify dnotify);
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password);
+
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_covers (SoupAuthDomain *domain,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_domain_accepts (SoupAuthDomain *domain,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_challenge (SoupAuthDomain *domain,
+ SoupMessage *msg);
+
+/* protected */
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username);
+
+G_END_DECLS
+
+#endif /* __SOUP_AUTH_DOMAIN_H__ */
diff --git a/libsoup/auth/soup-auth-manager.c b/libsoup/auth/soup-auth-manager.c
new file mode 100644
index 00000000..3b9e2b6b
--- /dev/null
+++ b/libsoup/auth/soup-auth-manager.c
@@ -0,0 +1,862 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-manager.c: SoupAuth manager for SoupSession
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-manager.h"
+#include "soup.h"
+#include "soup-connection-auth.h"
+#include "soup-message-private.h"
+#include "soup-message-queue.h"
+#include "soup-path-map.h"
+#include "soup-session-private.h"
+
+/**
+ * SECTION:soup-auth-manager
+ * @short_description: HTTP client-side authentication handler
+ * @see_also: #SoupSession, #SoupAuth
+ *
+ * #SoupAuthManager is the #SoupSessionFeature that handles HTTP
+ * authentication for a #SoupSession.
+ *
+ * A #SoupAuthManager is added to the session by default, and normally
+ * you don't need to worry about it at all. However, if you want to
+ * disable HTTP authentication, you can remove the feature from the
+ * session with soup_session_remove_feature_by_type(), or disable it on
+ * individual requests with soup_message_disable_feature().
+ *
+ * Since: 2.42
+ **/
+
+/**
+ * SOUP_TYPE_AUTH_MANAGER:
+ *
+ * The #GType of #SoupAuthManager; you can use this with
+ * soup_session_remove_feature_by_type() or
+ * soup_message_disable_feature().
+ *
+ * (Although this type has only been publicly visible since libsoup
+ * 2.42, it has always existed in the background, and you can use
+ * <literal><code>g_type_from_name ("SoupAuthManager")</code></literal>
+ * to get its #GType in earlier releases.)
+ *
+ * Since: 2.42
+ */
+static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+static SoupSessionFeatureInterface *soup_session_feature_default_interface;
+
+enum {
+ AUTHENTICATE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+
+struct SoupAuthManagerPrivate {
+ SoupSession *session;
+ GPtrArray *auth_types;
+ gboolean auto_ntlm;
+
+ GMutex lock;
+ SoupAuth *proxy_auth;
+ GHashTable *auth_hosts;
+};
+
+typedef struct {
+ SoupURI *uri;
+ SoupPathMap *auth_realms; /* path -> scheme:realm */
+ GHashTable *auths; /* scheme:realm -> SoupAuth */
+} SoupAuthHost;
+
+G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (SoupAuthManager)
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+ soup_auth_manager_session_feature_init))
+
+static void soup_auth_host_free (SoupAuthHost *host);
+static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv,
+ SoupURI *uri, SoupAuth *auth,
+ gboolean prior_auth_failed);
+
+static void
+soup_auth_manager_init (SoupAuthManager *manager)
+{
+ SoupAuthManagerPrivate *priv;
+
+ priv = manager->priv = soup_auth_manager_get_instance_private (manager);
+
+ priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
+ priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash,
+ soup_uri_host_equal,
+ NULL,
+ (GDestroyNotify)soup_auth_host_free);
+ g_mutex_init (&priv->lock);
+}
+
+static void
+soup_auth_manager_finalize (GObject *object)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (object)->priv;
+
+ g_ptr_array_free (priv->auth_types, TRUE);
+
+ g_hash_table_destroy (priv->auth_hosts);
+
+ g_clear_object (&priv->proxy_auth);
+
+ g_mutex_clear (&priv->lock);
+
+ G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
+
+ object_class->finalize = soup_auth_manager_finalize;
+
+ /**
+ * SoupAuthManager::authenticate:
+ * @manager: the #SoupAuthManager
+ * @msg: the #SoupMessage being sent
+ * @auth: the #SoupAuth to authenticate
+ * @retrying: %TRUE if this is the second (or later) attempt
+ *
+ * Emitted when the manager requires the application to
+ * provide authentication credentials.
+ *
+ * #SoupSession connects to this signal and emits its own
+ * #SoupSession::authenticate signal when it is emitted, so
+ * you shouldn't need to use this signal directly.
+ */
+ signals[AUTHENTICATE] =
+ g_signal_new ("authenticate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 3,
+ SOUP_TYPE_MESSAGE,
+ SOUP_TYPE_AUTH,
+ G_TYPE_BOOLEAN);
+
+}
+
+static int
+auth_type_compare_func (gconstpointer a, gconstpointer b)
+{
+ SoupAuthClass **auth1 = (SoupAuthClass **)a;
+ SoupAuthClass **auth2 = (SoupAuthClass **)b;
+
+ return (*auth1)->strength - (*auth2)->strength;
+}
+
+static gboolean
+soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
+ SoupAuthClass *auth_class;
+
+ if (!g_type_is_a (type, SOUP_TYPE_AUTH))
+ return FALSE;
+
+ auth_class = g_type_class_ref (type);
+ g_ptr_array_add (priv->auth_types, auth_class);
+ g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
+
+ /* Plain SoupSession does not get the backward-compat
+ * auto-NTLM behavior; SoupSession subclasses do.
+ */
+ if (type == SOUP_TYPE_AUTH_NTLM &&
+ G_TYPE_FROM_INSTANCE (priv->session) != SOUP_TYPE_SESSION)
+ priv->auto_ntlm = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
+ SoupAuthClass *auth_class;
+ guint i;
+
+ if (!g_type_is_a (type, SOUP_TYPE_AUTH))
+ return FALSE;
+
+ auth_class = g_type_class_peek (type);
+
+ for (i = 0; i < priv->auth_types->len; i++) {
+ if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
+ if (type == SOUP_TYPE_AUTH_NTLM)
+ priv->auto_ntlm = FALSE;
+
+ g_ptr_array_remove_index (priv->auth_types, i);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
+ SoupAuthClass *auth_class;
+ guint i;
+
+ if (!g_type_is_a (type, SOUP_TYPE_AUTH))
+ return FALSE;
+
+ auth_class = g_type_class_peek (type);
+ for (i = 0; i < priv->auth_types->len; i++) {
+ if (priv->auth_types->pdata[i] == (gpointer)auth_class)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
+
+ /* FIXME: should support multiple sessions */
+ priv->session = session;
+
+ soup_session_feature_default_interface->attach (feature, session);
+}
+
+static inline const char *
+auth_header_for_message (SoupMessage *msg)
+{
+ if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
+ return soup_message_headers_get_list (msg->response_headers,
+ "Proxy-Authenticate");
+ } else {
+ return soup_message_headers_get_list (msg->response_headers,
+ "WWW-Authenticate");
+ }
+}
+
+static GSList *
+next_challenge_start (GSList *items)
+{
+ /* The relevant grammar (from httpbis):
+ *
+ * WWW-Authenticate = 1#challenge
+ * Proxy-Authenticate = 1#challenge
+ * challenge = auth-scheme [ 1*SP ( b64token / #auth-param ) ]
+ * auth-scheme = token
+ * auth-param = token BWS "=" BWS ( token / quoted-string )
+ * b64token = 1*( ALPHA / DIGIT /
+ * "-" / "." / "_" / "~" / "+" / "/" ) *"="
+ *
+ * The fact that quoted-strings can contain commas, equals
+ * signs, and auth scheme names makes it tricky to "cheat" on
+ * the parsing. So soup_auth_manager_extract_challenge() will
+ * have used soup_header_parse_list() to split the header into
+ * items. Given the grammar above, the possible items are:
+ *
+ * auth-scheme
+ * auth-scheme 1*SP b64token
+ * auth-scheme 1*SP auth-param
+ * auth-param
+ *
+ * where the first three represent the start of a new challenge and
+ * the last one does not.
+ */
+
+ for (; items; items = items->next) {
+ const char *item = items->data;
+ const char *sp = strpbrk (item, "\t\r\n ");
+ const char *eq = strchr (item, '=');
+
+ if (!eq) {
+ /* No "=", so it can't be an auth-param */
+ return items;
+ }
+ if (!sp || sp > eq) {
+ /* No space, or first space appears after the "=",
+ * so it must be an auth-param.
+ */
+ continue;
+ }
+ while (g_ascii_isspace (*++sp))
+ ;
+ if (sp == eq) {
+ /* First "=" appears immediately after the first
+ * space, so this must be an auth-param with
+ * space around the "=".
+ */
+ continue;
+ }
+
+ /* "auth-scheme auth-param" or "auth-scheme b64token" */
+ return items;
+ }
+
+ return NULL;
+}
+
+static char *
+soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
+{
+ GSList *items, *i, *next;
+ int schemelen = strlen (scheme);
+ char *item;
+ GString *challenge;
+
+ items = soup_header_parse_list (challenges);
+
+ /* First item will start with the scheme name, followed by
+ * either nothing, or else a space and then the first
+ * auth-param.
+ */
+ for (i = items; i; i = next_challenge_start (i->next)) {
+ item = i->data;
+ if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
+ (!item[schemelen] || g_ascii_isspace (item[schemelen])))
+ break;
+ }
+ if (!i) {
+ soup_header_free_list (items);
+ return NULL;
+ }
+
+ next = next_challenge_start (i->next);
+ challenge = g_string_new (item);
+ for (i = i->next; i != next; i = i->next) {
+ item = i->data;
+ g_string_append (challenge, ", ");
+ g_string_append (challenge, item);
+ }
+
+ soup_header_free_list (items);
+ return g_string_free (challenge, FALSE);
+}
+
+static SoupAuth *
+create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
+{
+ const char *header;
+ SoupAuthClass *auth_class;
+ char *challenge = NULL;
+ SoupAuth *auth = NULL;
+ int i;
+
+ header = auth_header_for_message (msg);
+ if (!header)
+ return NULL;
+
+ for (i = priv->auth_types->len - 1; i >= 0; i--) {
+ auth_class = priv->auth_types->pdata[i];
+ challenge = soup_auth_manager_extract_challenge (header, auth_class->scheme_name);
+ if (!challenge)
+ continue;
+ auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
+ g_free (challenge);
+ if (auth)
+ break;
+ }
+
+ return auth;
+}
+
+static gboolean
+check_auth (SoupMessage *msg, SoupAuth *auth)
+{
+ const char *header, *scheme;
+ char *challenge = NULL;
+ gboolean ok = TRUE;
+
+ scheme = soup_auth_get_scheme_name (auth);
+
+ header = auth_header_for_message (msg);
+ if (header)
+ challenge = soup_auth_manager_extract_challenge (header, scheme);
+ if (!challenge) {
+ ok = FALSE;
+ challenge = g_strdup (scheme);
+ }
+
+ if (!soup_auth_update (auth, msg, challenge))
+ ok = FALSE;
+ g_free (challenge);
+ return ok;
+}
+
+static SoupAuthHost *
+get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri)
+{
+ SoupAuthHost *host;
+
+ host = g_hash_table_lookup (priv->auth_hosts, uri);
+ if (host)
+ return host;
+
+ host = g_slice_new0 (SoupAuthHost);
+ host->uri = soup_uri_copy_host (uri);
+ g_hash_table_insert (priv->auth_hosts, host->uri, host);
+
+ return host;
+}
+
+static void
+soup_auth_host_free (SoupAuthHost *host)
+{
+ g_clear_pointer (&host->auth_realms, soup_path_map_free);
+ g_clear_pointer (&host->auths, g_hash_table_destroy);
+
+ soup_uri_free (host->uri);
+ g_slice_free (SoupAuthHost, host);
+}
+
+static gboolean
+make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host)
+{
+ SoupAuth *auth;
+
+ if (!priv->auto_ntlm)
+ return FALSE;
+
+ auth = g_object_new (SOUP_TYPE_AUTH_NTLM,
+ SOUP_AUTH_HOST, host->uri->host,
+ NULL);
+ record_auth_for_uri (priv, host->uri, auth, FALSE);
+ g_object_unref (auth);
+ return TRUE;
+}
+
+static void
+update_authorization_header (SoupMessage *msg, SoupAuth *auth, gboolean is_proxy)
+{
+ const char *authorization_header = is_proxy ? "Proxy-Authorization" : "Authorization";
+ char *token;
+
+ if (soup_message_get_auth (msg))
+ soup_message_headers_remove (msg->request_headers, authorization_header);
+
+ if (!auth)
+ return;
+
+ token = soup_auth_get_authorization (auth, msg);
+ if (!token)
+ return;
+
+ soup_message_headers_replace (msg->request_headers, authorization_header, token);
+ g_free (token);
+}
+
+static SoupAuth *
+lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
+{
+ SoupAuthHost *host;
+ const char *path, *realm;
+ SoupAuth *auth;
+
+ /* If the message already has a ready auth, use that instead */
+ auth = soup_message_get_auth (msg);
+ if (auth && soup_auth_is_ready (auth, msg))
+ return auth;
+
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
+ return NULL;
+
+ host = get_auth_host_for_uri (priv, soup_message_get_uri (msg));
+ if (!host->auth_realms && !make_auto_ntlm_auth (priv, host))
+ return NULL;
+
+ /* Cannot change the above '&&' into '||', because make_auto_ntlm_auth() is used
+ * to populate host->auth_realms when it's not set yet. Even the make_auto_ntlm_auth()
+ * returns TRUE only if it also populates the host->auth_realms, this extra test
+ * is required to mute a FORWARD_NULL Coverity Scan warning, which is a false-positive
+ * here */
+ if (!host->auth_realms)
+ return NULL;
+
+ path = soup_message_get_uri (msg)->path;
+ if (!path)
+ path = "/";
+ realm = soup_path_map_lookup (host->auth_realms, path);
+ if (realm)
+ return g_hash_table_lookup (host->auths, realm);
+
+ return NULL;
+}
+
+static SoupAuth *
+lookup_proxy_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
+{
+ SoupAuth *auth;
+
+ /* If the message already has a ready auth, use that instead */
+ auth = soup_message_get_proxy_auth (msg);
+ if (auth && soup_auth_is_ready (auth, msg))
+ return auth;
+
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
+ return NULL;
+
+ return priv->proxy_auth;
+}
+
+static void
+authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
+ SoupMessage *msg, gboolean prior_auth_failed,
+ gboolean proxy, gboolean can_interact)
+{
+ SoupAuthManagerPrivate *priv = manager->priv;
+ SoupURI *uri;
+
+ if (!soup_auth_can_authenticate (auth))
+ return;
+
+ if (proxy) {
+ SoupMessageQueue *queue;
+ SoupMessageQueueItem *item;
+
+ queue = soup_session_get_queue (priv->session);
+ item = soup_message_queue_lookup (queue, msg);
+ if (!item)
+ return;
+
+ /* When loaded from the disk cache, the connection is NULL. */
+ uri = item->conn ? soup_connection_get_proxy_uri (item->conn) : NULL;
+ soup_message_queue_item_unref (item);
+ if (!uri)
+ return;
+ } else
+ uri = soup_message_get_uri (msg);
+
+ /* If a password is specified explicitly in the URI, use it
+ * even if the auth had previously already been authenticated.
+ */
+ if (uri->password && uri->user) {
+ soup_auth_authenticate (auth, uri->user, uri->password);
+ soup_uri_set_password (uri, NULL);
+ soup_uri_set_user (uri, NULL);
+ } else if (!soup_auth_is_authenticated (auth) && can_interact) {
+ g_signal_emit (manager, signals[AUTHENTICATE], 0,
+ msg, auth, prior_auth_failed);
+ }
+}
+
+static SoupAuth *
+record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri,
+ SoupAuth *auth, gboolean prior_auth_failed)
+{
+ SoupAuthHost *host;
+ SoupAuth *old_auth;
+ const char *path;
+ char *auth_info, *old_auth_info;
+ GSList *pspace, *p;
+
+ host = get_auth_host_for_uri (priv, uri);
+ auth_info = soup_auth_get_info (auth);
+
+ if (!host->auth_realms) {
+ host->auth_realms = soup_path_map_new (g_free);
+ host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ }
+
+ /* Record where this auth realm is used. */
+ pspace = soup_auth_get_protection_space (auth, uri);
+ for (p = pspace; p; p = p->next) {
+ path = p->data;
+ old_auth_info = soup_path_map_lookup (host->auth_realms, path);
+ if (old_auth_info) {
+ if (!strcmp (old_auth_info, auth_info))
+ continue;
+ soup_path_map_remove (host->auth_realms, path);
+ }
+
+ soup_path_map_add (host->auth_realms, path,
+ g_strdup (auth_info));
+ }
+ soup_auth_free_protection_space (auth, pspace);
+
+ /* Now, make sure the auth is recorded. (If there's a
+ * pre-existing good auth, we keep that rather than the new one,
+ * since the old one might already be authenticated.)
+ */
+ old_auth = g_hash_table_lookup (host->auths, auth_info);
+ if (old_auth && (old_auth != auth || !prior_auth_failed)) {
+ g_free (auth_info);
+ return old_auth;
+ } else {
+ g_hash_table_insert (host->auths, auth_info,
+ g_object_ref (auth));
+ return auth;
+ }
+}
+
+static void
+auth_got_headers (SoupMessage *msg, gpointer manager)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
+ SoupAuth *auth, *prior_auth;
+ gboolean prior_auth_failed = FALSE;
+
+ g_mutex_lock (&priv->lock);
+
+ /* See if we used auth last time */
+ prior_auth = soup_message_get_auth (msg);
+ if (prior_auth && check_auth (msg, prior_auth)) {
+ auth = g_object_ref (prior_auth);
+ if (!soup_auth_is_ready (auth, msg))
+ prior_auth_failed = TRUE;
+ } else {
+ auth = create_auth (priv, msg);
+ if (!auth) {
+ g_mutex_unlock (&priv->lock);
+ return;
+ }
+ }
+
+ if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) {
+ SoupAuth *new_auth;
+
+ new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg),
+ auth, prior_auth_failed);
+ g_object_unref (auth);
+ auth = g_object_ref (new_auth);
+ }
+
+ /* If we need to authenticate, try to do it. */
+ authenticate_auth (manager, auth, msg,
+ prior_auth_failed, FALSE, TRUE);
+ soup_message_set_auth (msg, auth);
+ g_object_unref (auth);
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+auth_got_body (SoupMessage *msg, gpointer manager)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
+ SoupAuth *auth;
+
+ g_mutex_lock (&priv->lock);
+ auth = lookup_auth (priv, msg);
+ if (auth && soup_auth_is_ready (auth, msg)) {
+ if (SOUP_IS_CONNECTION_AUTH (auth)) {
+ SoupMessageFlags flags;
+
+ flags = soup_message_get_flags (msg);
+ soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION);
+ }
+
+ /* When not using cached credentials, update the Authorization header
+ * right before requeuing the message.
+ */
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
+ update_authorization_header (msg, auth, FALSE);
+
+ soup_session_requeue_message (priv->session, msg);
+ }
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+proxy_auth_got_headers (SoupMessage *msg, gpointer manager)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
+ SoupAuth *auth = NULL, *prior_auth;
+ gboolean prior_auth_failed = FALSE;
+
+ g_mutex_lock (&priv->lock);
+
+ /* See if we used auth last time */
+ prior_auth = soup_message_get_proxy_auth (msg);
+ if (prior_auth && check_auth (msg, prior_auth)) {
+ if (!soup_auth_is_ready (prior_auth, msg))
+ prior_auth_failed = TRUE;
+ }
+
+ if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
+ auth = priv->proxy_auth ? g_object_ref (priv->proxy_auth) : NULL;
+
+ if (!auth) {
+ auth = create_auth (priv, msg);
+ if (!auth) {
+ g_mutex_unlock (&priv->lock);
+ return;
+ }
+ if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
+ priv->proxy_auth = g_object_ref (auth);
+ }
+
+ /* If we need to authenticate, try to do it. */
+ authenticate_auth (manager, auth, msg,
+ prior_auth_failed, TRUE, TRUE);
+ soup_message_set_proxy_auth (msg, auth);
+ g_object_unref (auth);
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+proxy_auth_got_body (SoupMessage *msg, gpointer manager)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
+ SoupAuth *auth;
+
+ g_mutex_lock (&priv->lock);
+
+ auth = lookup_proxy_auth (priv, msg);
+ if (auth && soup_auth_is_ready (auth, msg)) {
+ /* When not using cached credentials, update the Authorization header
+ * right before requeuing the message.
+ */
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
+ update_authorization_header (msg, auth, TRUE);
+ soup_session_requeue_message (priv->session, msg);
+ }
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+auth_msg_starting (SoupMessage *msg, gpointer manager)
+{
+ SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
+ SoupAuth *auth;
+
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
+ return;
+
+ g_mutex_lock (&priv->lock);
+
+ if (msg->method != SOUP_METHOD_CONNECT) {
+ auth = lookup_auth (priv, msg);
+ if (auth) {
+ authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE);
+ if (!soup_auth_is_ready (auth, msg))
+ auth = NULL;
+ }
+ soup_message_set_auth (msg, auth);
+ update_authorization_header (msg, auth, FALSE);
+ }
+
+ auth = lookup_proxy_auth (priv, msg);
+ if (auth) {
+ authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
+ if (!soup_auth_is_ready (auth, msg))
+ auth = NULL;
+ }
+ soup_message_set_proxy_auth (msg, auth);
+ update_authorization_header (msg, auth, TRUE);
+
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+soup_auth_manager_request_queued (SoupSessionFeature *manager,
+ SoupSession *session,
+ SoupMessage *msg)
+{
+ g_signal_connect (msg, "starting",
+ G_CALLBACK (auth_msg_starting), manager);
+
+ soup_message_add_status_code_handler (
+ msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
+ G_CALLBACK (auth_got_headers), manager);
+ soup_message_add_status_code_handler (
+ msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
+ G_CALLBACK (auth_got_body), manager);
+
+ soup_message_add_status_code_handler (
+ msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
+ G_CALLBACK (proxy_auth_got_headers), manager);
+ soup_message_add_status_code_handler (
+ msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
+ G_CALLBACK (proxy_auth_got_body), manager);
+}
+
+static void
+soup_auth_manager_request_unqueued (SoupSessionFeature *manager,
+ SoupSession *session,
+ SoupMessage *msg)
+{
+ g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, manager);
+}
+
+/**
+ * soup_auth_manager_use_auth:
+ * @manager: a #SoupAuthManager
+ * @uri: the #SoupURI under which @auth is to be used
+ * @auth: the #SoupAuth to use
+ *
+ * Records that @auth is to be used under @uri, as though a
+ * WWW-Authenticate header had been received at that URI. This can be
+ * used to "preload" @manager's auth cache, to avoid an extra HTTP
+ * round trip in the case where you know ahead of time that a 401
+ * response will be returned.
+ *
+ * This is only useful for authentication types where the initial
+ * Authorization header does not depend on any additional information
+ * from the server. (Eg, Basic or NTLM, but not Digest.)
+ *
+ * Since: 2.42
+ */
+void
+soup_auth_manager_use_auth (SoupAuthManager *manager,
+ SoupURI *uri,
+ SoupAuth *auth)
+{
+ SoupAuthManagerPrivate *priv = manager->priv;
+
+ g_mutex_lock (&priv->lock);
+ record_auth_for_uri (priv, uri, auth, FALSE);
+ g_mutex_unlock (&priv->lock);
+}
+
+/**
+ * soup_auth_manager_clear_cached_credentials:
+ * @manager: a #SoupAuthManager
+ *
+ * Clear all credentials cached by @manager
+ *
+ * Since: 2.58
+ */
+void
+soup_auth_manager_clear_cached_credentials (SoupAuthManager *manager)
+{
+ SoupAuthManagerPrivate *priv;
+
+ g_return_if_fail (SOUP_IS_AUTH_MANAGER (manager));
+
+ priv = manager->priv;
+ g_mutex_lock (&priv->lock);
+ g_hash_table_remove_all (priv->auth_hosts);
+ g_mutex_unlock (&priv->lock);
+}
+
+static void
+soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+ gpointer interface_data)
+{
+ soup_session_feature_default_interface =
+ g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
+
+ feature_interface->attach = soup_auth_manager_attach;
+ feature_interface->request_queued = soup_auth_manager_request_queued;
+ feature_interface->request_unqueued = soup_auth_manager_request_unqueued;
+ feature_interface->add_feature = soup_auth_manager_add_feature;
+ feature_interface->remove_feature = soup_auth_manager_remove_feature;
+ feature_interface->has_feature = soup_auth_manager_has_feature;
+}
diff --git a/libsoup/auth/soup-auth-manager.h b/libsoup/auth/soup-auth-manager.h
new file mode 100644
index 00000000..cf38936a
--- /dev/null
+++ b/libsoup/auth/soup-auth-manager.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifndef __SOUP_AUTH_MANAGER_H__
+#define __SOUP_AUTH_MANAGER_H__ 1
+
+#include "soup-types.h"
+#include "soup-auth.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_MANAGER (soup_auth_manager_get_type ())
+#define SOUP_AUTH_MANAGER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_MANAGER, SoupAuthManager))
+#define SOUP_AUTH_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerClass))
+#define SOUP_IS_AUTH_MANAGER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_MANAGER))
+#define SOUP_IS_AUTH_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_MANAGER))
+#define SOUP_AUTH_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerClass))
+
+typedef struct SoupAuthManagerPrivate SoupAuthManagerPrivate;
+
+typedef struct {
+ GObject parent;
+
+ SoupAuthManagerPrivate *priv;
+} SoupAuthManager;
+
+typedef struct {
+ GObjectClass parent_class;
+
+ void (*authenticate) (SoupAuthManager *manager, SoupMessage *msg,
+ SoupAuth *auth, gboolean retrying);
+} SoupAuthManagerClass;
+
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_manager_get_type (void);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_manager_use_auth (SoupAuthManager *manager,
+ SoupURI *uri,
+ SoupAuth *auth);
+
+SOUP_AVAILABLE_IN_2_58
+void soup_auth_manager_clear_cached_credentials (SoupAuthManager *manager);
+
+G_END_DECLS
+
+#endif /* __SOUP_AUTH_MANAGER_H__ */
diff --git a/libsoup/auth/soup-auth-negotiate.c b/libsoup/auth/soup-auth-negotiate.c
new file mode 100644
index 00000000..1fdf142d
--- /dev/null
+++ b/libsoup/auth/soup-auth-negotiate.c
@@ -0,0 +1,622 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-negotiate.c: HTTP Negotiate Authentication helper
+ *
+ * Copyright (C) 2009,2013 Guido Guenther <agx@sigxcpu.org>
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#ifdef LIBSOUP_HAVE_GSSAPI
+#include <gssapi/gssapi.h>
+#endif /* LIBSOUP_HAVE_GSSAPI */
+
+#include "soup-auth-negotiate.h"
+#include "soup-headers.h"
+#include "soup-message.h"
+#include "soup-message-private.h"
+#include "soup-misc-private.h"
+#include "soup-uri.h"
+
+/**
+ * soup_auth_negotiate_supported:
+ *
+ * Indicates whether libsoup was built with GSSAPI support. If this is
+ * %FALSE, %SOUP_TYPE_AUTH_NEGOTIATE will still be defined and can
+ * still be added to a #SoupSession, but libsoup will never attempt to
+ * actually use this auth type.
+ *
+ * Since: 2.54
+ */
+gboolean
+soup_auth_negotiate_supported (void)
+{
+#ifdef LIBSOUP_HAVE_GSSAPI
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+#define AUTH_GSS_ERROR -1
+#define AUTH_GSS_COMPLETE 1
+#define AUTH_GSS_CONTINUE 0
+
+typedef enum {
+ SOUP_NEGOTIATE_NEW,
+ SOUP_NEGOTIATE_RECEIVED_CHALLENGE, /* received initial negotiate header */
+ SOUP_NEGOTIATE_SENT_RESPONSE, /* sent response to server */
+ SOUP_NEGOTIATE_FAILED
+} SoupNegotiateState;
+
+typedef struct {
+ gboolean initialized;
+ gchar *response_header;
+
+#ifdef LIBSOUP_HAVE_GSSAPI
+ gss_ctx_id_t context;
+ gss_name_t server_name;
+#endif
+
+ SoupNegotiateState state;
+} SoupNegotiateConnectionState;
+
+typedef struct {
+ gboolean is_authenticated;
+} SoupAuthNegotiatePrivate;
+
+/**
+ * SOUP_TYPE_AUTH_NEGOTIATE:
+ *
+ * A #GType corresponding to HTTP-based GSS-Negotiate authentication.
+ * #SoupSessions do not support this type by default; if you want to
+ * enable support for it, call soup_session_add_feature_by_type(),
+ * passing %SOUP_TYPE_AUTH_NEGOTIATE.
+ *
+ * This auth type will only work if libsoup was compiled with GSSAPI
+ * support; you can check soup_auth_negotiate_supported() to see if it
+ * was.
+ *
+ * Since: 2.54
+ */
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthNegotiate, soup_auth_negotiate, SOUP_TYPE_CONNECTION_AUTH)
+
+#ifdef LIBSOUP_HAVE_GSSAPI
+static gboolean check_auth_trusted_uri (SoupConnectionAuth *auth,
+ SoupMessage *msg);
+static gboolean soup_gss_build_response (SoupNegotiateConnectionState *conn,
+ SoupAuth *auth, GError **err);
+static void soup_gss_client_cleanup (SoupNegotiateConnectionState *conn);
+static gboolean soup_gss_client_init (SoupNegotiateConnectionState *conn,
+ const char *host, GError **err);
+static int soup_gss_client_step (SoupNegotiateConnectionState *conn,
+ const char *host, GError **err);
+
+static GSList *trusted_uris;
+static GSList *blacklisted_uris;
+
+static void parse_uris_from_env_variable (const gchar *env_variable, GSList **list);
+
+static void check_server_response (SoupMessage *msg, gpointer auth);
+
+static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
+static const gss_OID_desc gss_mech_spnego = { sizeof (spnego_OID) - 1, (void *) &spnego_OID };
+
+static gpointer
+soup_auth_negotiate_create_connection_state (SoupConnectionAuth *auth)
+{
+ SoupNegotiateConnectionState *conn;
+
+ conn = g_slice_new0 (SoupNegotiateConnectionState);
+ conn->state = SOUP_NEGOTIATE_NEW;
+
+ return conn;
+}
+
+static void
+free_connection_state_data (SoupNegotiateConnectionState *conn)
+{
+ soup_gss_client_cleanup (conn);
+ g_free (conn->response_header);
+}
+
+static void
+soup_auth_negotiate_free_connection_state (SoupConnectionAuth *auth,
+ gpointer state)
+{
+ SoupNegotiateConnectionState *conn = state;
+
+ free_connection_state_data (conn);
+
+ g_slice_free (SoupNegotiateConnectionState, conn);
+}
+
+static GSList *
+soup_auth_negotiate_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
+{
+ char *space, *p;
+
+ space = g_strdup (source_uri->path);
+
+ /* Strip filename component */
+ p = strrchr (space, '/');
+ if (p && p == space && p[1])
+ p[1] = '\0';
+ else if (p && p[1])
+ *p = '\0';
+
+ return g_slist_prepend (NULL, space);
+}
+
+static void
+soup_auth_negotiate_authenticate (SoupAuth *auth, const char *username,
+ const char *password)
+{
+ SoupAuthNegotiate *negotiate = SOUP_AUTH_NEGOTIATE (auth);
+ SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
+
+ /* It is not possible to authenticate with username and password. */
+ priv->is_authenticated = FALSE;
+}
+
+static gboolean
+soup_auth_negotiate_is_authenticated (SoupAuth *auth)
+{
+ SoupAuthNegotiate *negotiate = SOUP_AUTH_NEGOTIATE (auth);
+ SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
+
+ /* We are authenticated just in case we received the GSS_S_COMPLETE. */
+ return priv->is_authenticated;
+}
+
+static gboolean
+soup_auth_negotiate_can_authenticate (SoupAuth *auth)
+{
+ return FALSE;
+}
+
+static char *
+soup_auth_negotiate_get_connection_authorization (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer state)
+{
+ SoupNegotiateConnectionState *conn = state;
+ char *header = NULL;
+
+ if (conn->state == SOUP_NEGOTIATE_NEW) {
+ GError *err = NULL;
+
+ if (!check_auth_trusted_uri (auth, msg)) {
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ return NULL;
+ }
+
+ if (!soup_gss_build_response (conn, SOUP_AUTH (auth), &err)) {
+ /* FIXME: report further upward via
+ * soup_message_get_error_message */
+ if (conn->initialized)
+ g_warning ("gssapi step failed: %s", err->message);
+ else
+ g_warning ("gssapi init failed: %s", err->message);
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ g_clear_error (&err);
+
+ return NULL;
+ }
+ }
+
+ if (conn->response_header) {
+ header = conn->response_header;
+ conn->response_header = NULL;
+ conn->state = SOUP_NEGOTIATE_SENT_RESPONSE;
+ }
+
+ return header;
+}
+
+static gboolean
+soup_auth_negotiate_is_connection_ready (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer state)
+{
+ SoupNegotiateConnectionState *conn = state;
+
+ return conn->state != SOUP_NEGOTIATE_FAILED;
+}
+#endif /* LIBSOUP_HAVE_GSSAPI */
+
+static gboolean
+soup_auth_negotiate_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
+ const char *header, gpointer state)
+{
+#ifdef LIBSOUP_HAVE_GSSAPI
+ gboolean success = TRUE;
+ SoupNegotiateConnectionState *conn = state;
+ GError *err = NULL;
+
+ if (!check_auth_trusted_uri (auth, msg)) {
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ goto out;
+ }
+
+ /* Found negotiate header with no token, start negotiate */
+ if (strcmp (header, "Negotiate") == 0) {
+ /* If we were already negotiating and we get a 401
+ * with no token, start again. */
+ if (conn->state == SOUP_NEGOTIATE_SENT_RESPONSE) {
+ free_connection_state_data (conn);
+ conn->initialized = FALSE;
+ }
+
+ conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
+ if (soup_gss_build_response (conn, SOUP_AUTH (auth), &err)) {
+ /* Connect the signal only once per message */
+ if (!g_object_get_data (G_OBJECT (msg), "negotiate-got-headers-connected")) {
+ /* Wait for the 2xx response to verify server response */
+ g_signal_connect_data (msg,
+ "got_headers",
+ G_CALLBACK (check_server_response),
+ g_object_ref (auth),
+ (GClosureNotify) g_object_unref,
+ 0);
+ /* Mark that the signal was connected */
+ g_object_set_data (G_OBJECT (msg),
+ "negotiate-got-headers-connected",
+ GINT_TO_POINTER (1));
+ }
+ goto out;
+ } else {
+ /* FIXME: report further upward via
+ * soup_message_get_error_message */
+ if (conn->initialized)
+ g_warning ("gssapi step failed: %s", err->message);
+ else
+ g_warning ("gssapi init failed: %s", err->message);
+ success = FALSE;
+ }
+ } else if (!strncmp (header, "Negotiate ", 10)) {
+ if (soup_gss_client_step (conn, header + 10, &err) == AUTH_GSS_CONTINUE) {
+ conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
+ goto out;
+ }
+ }
+
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ out:
+ g_clear_error (&err);
+ return success;
+#else
+ return FALSE;
+#endif /* LIBSOUP_HAVE_GSSAPI */
+}
+
+static void
+soup_auth_negotiate_init (SoupAuthNegotiate *negotiate)
+{
+ g_object_set (G_OBJECT (negotiate), SOUP_AUTH_REALM, "", NULL);
+}
+
+static void
+soup_auth_negotiate_class_init (SoupAuthNegotiateClass *auth_negotiate_class)
+{
+ SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_negotiate_class);
+ SoupConnectionAuthClass *conn_auth_class =
+ SOUP_CONNECTION_AUTH_CLASS (auth_negotiate_class);
+
+ auth_class->scheme_name = "Negotiate";
+ auth_class->strength = 0;
+
+ conn_auth_class->update_connection = soup_auth_negotiate_update_connection;
+#ifdef LIBSOUP_HAVE_GSSAPI
+ auth_class->strength = 7;
+
+ conn_auth_class->create_connection_state = soup_auth_negotiate_create_connection_state;
+ conn_auth_class->free_connection_state = soup_auth_negotiate_free_connection_state;
+ conn_auth_class->get_connection_authorization = soup_auth_negotiate_get_connection_authorization;
+ conn_auth_class->is_connection_ready = soup_auth_negotiate_is_connection_ready;
+
+ auth_class->get_protection_space = soup_auth_negotiate_get_protection_space;
+ auth_class->authenticate = soup_auth_negotiate_authenticate;
+ auth_class->is_authenticated = soup_auth_negotiate_is_authenticated;
+ auth_class->can_authenticate = soup_auth_negotiate_can_authenticate;
+
+ parse_uris_from_env_variable ("SOUP_GSSAPI_TRUSTED_URIS", &trusted_uris);
+ parse_uris_from_env_variable ("SOUP_GSSAPI_BLACKLISTED_URIS", &blacklisted_uris);
+#endif /* LIBSOUP_HAVE_GSSAPI */
+}
+
+#ifdef LIBSOUP_HAVE_GSSAPI
+static void
+check_server_response (SoupMessage *msg, gpointer auth)
+{
+ gint ret;
+ const char *auth_headers;
+ GError *err = NULL;
+ SoupAuthNegotiate *negotiate = auth;
+ SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
+ SoupNegotiateConnectionState *conn;
+
+ conn = soup_connection_auth_get_connection_state_for_message (SOUP_CONNECTION_AUTH (auth), msg);
+ if (!conn)
+ return;
+
+ if (auth != soup_message_get_auth (msg))
+ return;
+
+ if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
+ return;
+
+ /* FIXME: need to check for proxy-auth too */
+ auth_headers = soup_message_headers_get_one (msg->response_headers,
+ "WWW-Authenticate");
+ if (!auth_headers || g_ascii_strncasecmp (auth_headers, "Negotiate ", 10) != 0) {
+ g_warning ("Failed to parse auth header");
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ goto out;
+ }
+
+ ret = soup_gss_client_step (conn, auth_headers + 10, &err);
+
+ switch (ret) {
+ case AUTH_GSS_COMPLETE:
+ priv->is_authenticated = TRUE;
+ break;
+ case AUTH_GSS_CONTINUE:
+ conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
+ break;
+ case AUTH_GSS_ERROR:
+ if (err)
+ g_warning ("%s", err->message);
+ /* Unfortunately, so many programs (curl, Firefox, ..) ignore
+ * the return token that is included in the response, so it is
+ * possible that there are servers that send back broken stuff.
+ * Try to behave in the right way (pass the token to
+ * gss_init_sec_context()), show a warning, but don't fail
+ * if the server returned 200. */
+ if (msg->status_code == SOUP_STATUS_OK)
+ priv->is_authenticated = TRUE;
+ else
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ break;
+ default:
+ conn->state = SOUP_NEGOTIATE_FAILED;
+ }
+ out:
+ g_clear_error (&err);
+}
+
+/* Check if scheme://host:port from message matches the given URI. */
+static gint
+match_base_uri (SoupURI *list_uri, SoupURI *msg_uri)
+{
+ if (msg_uri->scheme != list_uri->scheme)
+ return 1;
+
+ if (list_uri->port && (msg_uri->port != list_uri->port))
+ return 1;
+
+ if (list_uri->host)
+ return !soup_host_matches_host (msg_uri->host, list_uri->host);
+
+ return 0;
+}
+
+/* Parses a comma separated list of URIS from the environment. */
+static void
+parse_uris_from_env_variable (const gchar *env_variable, GSList **list)
+{
+ gchar **uris = NULL;
+ const gchar *env;
+ gint i;
+ guint length;
+
+ /* Initialize the list */
+ *list = NULL;
+
+ if (!(env = g_getenv (env_variable)))
+ return;
+
+ if (!(uris = g_strsplit (env, ",", -1)))
+ return;
+
+ length = g_strv_length (uris);
+ for (i = 0; i < length; i++) {
+ SoupURI *uri;
+
+ /* If the supplied URI is valid, append it to the list */
+ if ((uri = soup_uri_new (uris[i])))
+ *list = g_slist_prepend (*list, uri);
+ }
+
+ g_strfreev (uris);
+}
+
+static gboolean
+check_auth_trusted_uri (SoupConnectionAuth *auth, SoupMessage *msg)
+{
+ SoupURI *msg_uri;
+ GSList *matched = NULL;
+
+ g_return_val_if_fail (auth != NULL, FALSE);
+ g_return_val_if_fail (msg != NULL, FALSE);
+
+ msg_uri = soup_message_get_uri (msg);
+
+ /* First check if the URI is not on blacklist */
+ if (blacklisted_uris &&
+ g_slist_find_custom (blacklisted_uris, msg_uri, (GCompareFunc) match_base_uri))
+ return FALSE;
+
+ /* If no trusted URIs are set, we allow all HTTPS URIs */
+ if (!trusted_uris)
+ return soup_uri_is_https (msg_uri, NULL);
+
+ matched = g_slist_find_custom (trusted_uris,
+ msg_uri,
+ (GCompareFunc) match_base_uri);
+
+ return matched ? TRUE : FALSE;
+}
+
+static gboolean
+soup_gss_build_response (SoupNegotiateConnectionState *conn, SoupAuth *auth, GError **err)
+{
+ if (!conn->initialized)
+ if (!soup_gss_client_init (conn, soup_auth_get_host (auth), err))
+ return FALSE;
+
+ if (soup_gss_client_step (conn, "", err) != AUTH_GSS_CONTINUE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+soup_gss_error (OM_uint32 err_maj, OM_uint32 err_min, GError **err)
+{
+ OM_uint32 maj_stat, min_stat, msg_ctx = 0;
+ gss_buffer_desc status;
+ gchar *buf_maj = NULL, *buf_min = NULL;
+
+ do {
+ maj_stat = gss_display_status (&min_stat,
+ err_maj,
+ GSS_C_GSS_CODE,
+ (gss_OID) &gss_mech_spnego,
+ &msg_ctx,
+ &status);
+ if (GSS_ERROR (maj_stat))
+ break;
+
+ buf_maj = g_strdup ((gchar *) status.value);
+ gss_release_buffer (&min_stat, &status);
+
+ maj_stat = gss_display_status (&min_stat,
+ err_min,
+ GSS_C_MECH_CODE,
+ GSS_C_NULL_OID,
+ &msg_ctx,
+ &status);
+ if (!GSS_ERROR (maj_stat)) {
+ buf_min = g_strdup ((gchar *) status.value);
+ gss_release_buffer (&min_stat, &status);
+ }
+
+ if (err && *err == NULL) {
+ g_set_error (err,
+ SOUP_HTTP_ERROR,
+ SOUP_STATUS_UNAUTHORIZED,
+ "%s: %s",
+ buf_maj,
+ buf_min ? buf_min : "");
+ }
+ g_free (buf_maj);
+ g_free (buf_min);
+ buf_min = buf_maj = NULL;
+ } while (!GSS_ERROR (maj_stat) && msg_ctx != 0);
+}
+
+static gboolean
+soup_gss_client_init (SoupNegotiateConnectionState *conn, const gchar *host, GError **err)
+{
+ OM_uint32 maj_stat, min_stat;
+ gchar *service = NULL;
+ gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
+ gboolean ret = FALSE;
+ gchar *h;
+
+ conn->server_name = GSS_C_NO_NAME;
+ conn->context = GSS_C_NO_CONTEXT;
+
+ h = g_ascii_strdown (host, -1);
+ service = g_strconcat ("HTTP@", h, NULL);
+ token.length = strlen (service);
+ token.value = (gchar *) service;
+
+ maj_stat = gss_import_name (&min_stat,
+ &token,
+ (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
+ &conn->server_name);
+
+ if (GSS_ERROR (maj_stat)) {
+ soup_gss_error (maj_stat, min_stat, err);
+ ret = FALSE;
+ goto out;
+ }
+
+ conn->initialized = TRUE;
+ ret = TRUE;
+out:
+ g_free (h);
+ g_free (service);
+ return ret;
+}
+
+static gint
+soup_gss_client_step (SoupNegotiateConnectionState *conn, const gchar *challenge, GError **err)
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
+ gint ret = AUTH_GSS_CONTINUE;
+
+ g_clear_pointer (&conn->response_header, g_free);
+
+ if (challenge && *challenge) {
+ size_t len;
+ in.value = g_base64_decode (challenge, &len);
+ in.length = len;
+ }
+
+ maj_stat = gss_init_sec_context (&min_stat,
+ GSS_C_NO_CREDENTIAL,
+ &conn->context,
+ conn->server_name,
+ (gss_OID) &gss_mech_spnego,
+ GSS_C_MUTUAL_FLAG,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &in,
+ NULL,
+ &out,
+ NULL,
+ NULL);
+
+ if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) {
+ soup_gss_error (maj_stat, min_stat, err);
+ ret = AUTH_GSS_ERROR;
+ goto out;
+ }
+
+ ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE;
+ if (out.length) {
+ gchar *response = g_base64_encode ((const guchar *) out.value, out.length);
+ conn->response_header = g_strconcat ("Negotiate ", response, NULL);
+ g_free (response);
+ maj_stat = gss_release_buffer (&min_stat, &out);
+ }
+
+out:
+ if (out.value)
+ gss_release_buffer (&min_stat, &out);
+ if (in.value)
+ g_free (in.value);
+ return ret;
+}
+
+static void
+soup_gss_client_cleanup (SoupNegotiateConnectionState *conn)
+{
+ OM_uint32 maj_stat, min_stat;
+
+ gss_release_name (&min_stat, &conn->server_name);
+ maj_stat = gss_delete_sec_context (&min_stat, &conn->context, GSS_C_NO_BUFFER);
+ if (maj_stat != GSS_S_COMPLETE)
+ maj_stat = gss_delete_sec_context (&min_stat, &conn->context, GSS_C_NO_BUFFER);
+}
+#endif /* LIBSOUP_HAVE_GSSAPI */
diff --git a/libsoup/auth/soup-auth-negotiate.h b/libsoup/auth/soup-auth-negotiate.h
new file mode 100644
index 00000000..5c3916bf
--- /dev/null
+++ b/libsoup/auth/soup-auth-negotiate.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Guido Guenther <agx@sigxcpu.org>
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#ifndef __SOUP_AUTH_NEGOTIATE_H__
+#define __SOUP_AUTH_NEGOTIATE_H__ 1
+
+#include "soup-connection-auth.h"
+
+#define SOUP_AUTH_NEGOTIATE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiate))
+#define SOUP_AUTH_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiateClass))
+#define SOUP_IS_AUTH_NEGOTIATE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_NEGOTIATE))
+#define SOUP_IS_AUTH_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_NEGOTIATE))
+#define SOUP_AUTH_NEGOTIATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NEGOTIATE, SoupAuthNegotiateClass))
+
+typedef struct {
+ SoupConnectionAuth parent;
+
+} SoupAuthNegotiate;
+
+typedef struct {
+ SoupConnectionAuthClass parent_class;
+
+} SoupAuthNegotiateClass;
+
+#endif /* __SOUP_AUTH_NEGOTIATE_H__ */
diff --git a/libsoup/auth/soup-auth-ntlm.c b/libsoup/auth/soup-auth-ntlm.c
new file mode 100644
index 00000000..2d078461
--- /dev/null
+++ b/libsoup/auth/soup-auth-ntlm.c
@@ -0,0 +1,1513 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-ntlm.c: HTTP NTLM Authentication helper
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "soup-auth-ntlm.h"
+#include "soup.h"
+#include "soup-message-private.h"
+
+static void soup_ntlm_lanmanager_hash (const char *password,
+ guchar hash[21]);
+static void soup_ntlm_nt_hash (const char *password,
+ guchar hash[21]);
+static char *soup_ntlm_request (void);
+static gboolean soup_ntlm_parse_challenge (const char *challenge,
+ char **nonce,
+ char **default_domain,
+ gboolean *ntlmv2_session,
+ gboolean *negotiate_target,
+ char **target_info,
+ size_t *target_info_sz);
+static char *soup_ntlm_response (const char *nonce,
+ const char *user,
+ guchar nt_hash[21],
+ guchar lm_hash[21],
+ const char *host,
+ const char *domain,
+ gboolean ntlmv2_session,
+ gboolean negotiate_target,
+ const char *target_info,
+ size_t target_info_sz);
+
+typedef enum {
+ SOUP_NTLM_NEW,
+ SOUP_NTLM_SSO_FAILED,
+ SOUP_NTLM_SENT_REQUEST,
+ SOUP_NTLM_RECEIVED_CHALLENGE,
+ SOUP_NTLM_SENT_RESPONSE,
+ SOUP_NTLM_FAILED
+} SoupNTLMState;
+
+typedef struct {
+ SoupNTLMState state;
+ char *nonce;
+ char *response_header;
+ gboolean ntlmv2_session;
+ gboolean negotiate_target;
+ char *target_info;
+ size_t target_info_sz;
+} SoupNTLMConnectionState;
+
+typedef enum {
+ SOUP_NTLM_PASSWORD_NONE,
+ SOUP_NTLM_PASSWORD_PROVIDED,
+ SOUP_NTLM_PASSWORD_ACCEPTED,
+ SOUP_NTLM_PASSWORD_REJECTED
+} SoupNTLMPasswordState;
+
+typedef struct {
+ char *username, *domain;
+ guchar nt_hash[21], lm_hash[21];
+ SoupNTLMPasswordState password_state;
+
+#ifdef USE_NTLM_AUTH
+ /* Use Samba's 'winbind' daemon to support NTLM single-sign-on,
+ * by delegating the NTLM challenge/response protocal to a helper
+ * in ntlm_auth.
+ * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
+ * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
+ * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
+ */
+ gboolean sso_available;
+ int fd_in;
+ int fd_out;
+#endif
+} SoupAuthNTLMPrivate;
+
+#ifdef USE_NTLM_AUTH
+static gboolean ntlm_auth_available, ntlm_auth_debug;
+static void sso_ntlm_close (SoupAuthNTLMPrivate *priv);
+#endif
+
+/**
+ * SOUP_TYPE_AUTH_NTLM:
+ *
+ * A #GType corresponding to HTTP-based NTLM authentication.
+ * #SoupSessions do not support this type by default; if you want to
+ * enable support for it, call soup_session_add_feature_by_type(),
+ * passing %SOUP_TYPE_AUTH_NTLM.
+ *
+ * Since: 2.34
+ */
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_CONNECTION_AUTH)
+
+static void
+soup_auth_ntlm_init (SoupAuthNTLM *ntlm)
+{
+#ifdef USE_NTLM_AUTH
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (ntlm);
+ const char *username = NULL, *slash;
+
+ priv->sso_available = TRUE;
+ priv->fd_in = -1;
+ priv->fd_out = -1;
+
+ username = getenv ("NTLMUSER");
+ if (!username)
+ username = g_get_user_name ();
+
+ slash = strpbrk (username, "\\/");
+ if (slash) {
+ priv->username = g_strdup (slash + 1);
+ priv->domain = g_strndup (username, slash - username);
+ } else {
+ priv->username = g_strdup (username);
+ priv->domain = NULL;
+ }
+#endif
+}
+
+static void
+soup_auth_ntlm_finalize (GObject *object)
+{
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (SOUP_AUTH_NTLM (object));
+
+ g_free (priv->username);
+ g_free (priv->domain);
+
+ memset (priv->nt_hash, 0, sizeof (priv->nt_hash));
+ memset (priv->lm_hash, 0, sizeof (priv->lm_hash));
+
+#ifdef USE_NTLM_AUTH
+ sso_ntlm_close (priv);
+#endif
+
+ G_OBJECT_CLASS (soup_auth_ntlm_parent_class)->finalize (object);
+}
+
+#ifdef USE_NTLM_AUTH
+static void
+sso_ntlm_close (SoupAuthNTLMPrivate *priv)
+{
+ if (priv->fd_in != -1) {
+ close (priv->fd_in);
+ priv->fd_in = -1;
+ }
+
+ if (priv->fd_out != -1) {
+ close (priv->fd_out);
+ priv->fd_out = -1;
+ }
+}
+
+static gboolean
+sso_ntlm_initiate (SoupAuthNTLMPrivate *priv)
+{
+ char *argv[9];
+ gboolean ret;
+
+ if (!priv->sso_available)
+ return FALSE;
+
+ if (!ntlm_auth_available && !ntlm_auth_debug) {
+ priv->sso_available = FALSE;
+ return FALSE;
+ }
+
+ /* Return if ntlm_auth execution process exist already */
+ if (priv->fd_in != -1 && priv->fd_out != -1)
+ return TRUE;
+ else {
+ /* Clean all sso data before re-initiate */
+ sso_ntlm_close (priv);
+ }
+
+ if (ntlm_auth_debug) {
+ argv[0] = (char *) g_getenv ("SOUP_NTLM_AUTH_DEBUG");
+ if (!*argv[0]) {
+ priv->sso_available = FALSE;
+ return FALSE;
+ }
+ } else
+ argv[0] = NTLM_AUTH;
+ argv[1] = "--helper-protocol";
+ argv[2] = "ntlmssp-client-1";
+ argv[3] = "--use-cached-creds";
+ argv[4] = "--username";
+ argv[5] = priv->username;
+ argv[6] = priv->domain ? "--domain" : NULL;
+ argv[7] = priv->domain;
+ argv[8] = NULL;
+
+ ret = g_spawn_async_with_pipes (NULL, argv, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL,
+ NULL, &priv->fd_in, &priv->fd_out,
+ NULL, NULL);
+ if (!ret)
+ priv->sso_available = FALSE;
+ return ret;
+}
+
+static char *
+sso_ntlm_response (SoupAuthNTLMPrivate *priv, const char *input, SoupNTLMState conn_state)
+{
+ ssize_t size;
+ char buf[1024];
+ char *tmpbuf = buf;
+ size_t len_in = strlen (input), len_out = sizeof (buf);
+
+ while (len_in > 0) {
+ int written = write (priv->fd_in, input, len_in);
+ if (written == -1) {
+ if (errno == EINTR)
+ continue;
+ /* write failed if other errors happen */
+ return NULL;
+ }
+ input += written;
+ len_in -= written;
+ }
+ /* Read one line */
+ while (len_out > 0) {
+ size = read (priv->fd_out, tmpbuf, len_out);
+ if (size == -1) {
+ if (errno == EINTR)
+ continue;
+ return NULL;
+ } else if (size == 0)
+ return NULL;
+ else if (tmpbuf[size - 1] == '\n') {
+ tmpbuf[size - 1] = '\0';
+ goto wrfinish;
+ }
+ tmpbuf += size;
+ len_out -= size;
+ }
+ return NULL;
+
+wrfinish:
+ if (g_ascii_strcasecmp (buf, "PW") == 0) {
+ /* Samba/winbind installed but not configured */
+ return g_strdup ("PW");
+ }
+ if (conn_state == SOUP_NTLM_NEW &&
+ g_ascii_strncasecmp (buf, "YR ", 3) != 0) {
+ /* invalid response for type 1 message */
+ return NULL;
+ }
+ if (conn_state == SOUP_NTLM_RECEIVED_CHALLENGE &&
+ g_ascii_strncasecmp (buf, "KK ", 3) != 0 &&
+ g_ascii_strncasecmp (buf, "AF ", 3) != 0) {
+ /* invalid response for type 3 message */
+ return NULL;
+ }
+
+ return g_strdup_printf ("NTLM %.*s", (int)(size - 4), buf + 3);
+}
+#endif /* USE_NTLM_AUTH */
+
+static gpointer
+soup_auth_ntlm_create_connection_state (SoupConnectionAuth *auth)
+{
+ SoupNTLMConnectionState *conn;
+
+ conn = g_slice_new0 (SoupNTLMConnectionState);
+ conn->state = SOUP_NTLM_NEW;
+
+ return conn;
+}
+
+static void
+soup_auth_ntlm_free_connection_state (SoupConnectionAuth *auth,
+ gpointer state)
+{
+ SoupNTLMConnectionState *conn = state;
+
+ g_free (conn->nonce);
+ g_free (conn->response_header);
+ g_free (conn->target_info);
+ g_slice_free (SoupNTLMConnectionState, conn);
+}
+
+static gboolean
+soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
+ const char *auth_header, gpointer state)
+{
+ SoupAuthNTLM *auth_ntlm = SOUP_AUTH_NTLM (auth);
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (auth_ntlm);
+ SoupNTLMConnectionState *conn = state;
+ gboolean success = TRUE;
+
+ /* Note that we only return FALSE if some sort of parsing error
+ * occurs. Otherwise, the SoupAuth is still reusable (though it may
+ * no longer be _ready or _authenticated).
+ */
+
+ if (!g_str_has_prefix (auth_header, "NTLM"))
+ return FALSE;
+
+ if (conn->state > SOUP_NTLM_SENT_REQUEST) {
+ if (priv->password_state == SOUP_NTLM_PASSWORD_ACCEPTED) {
+ /* We know our password is correct, so a 401
+ * means "permission denied". The code can't deal
+ * with re-authenticating correctly, so make sure
+ * we don't try.
+ */
+ conn->state = SOUP_NTLM_FAILED;
+ if (soup_message_is_keepalive (msg)) {
+ soup_message_headers_append (msg->response_headers,
+ "Connection", "close");
+ }
+ return TRUE;
+ }
+
+#ifdef USE_NTLM_AUTH
+ if (priv->sso_available) {
+ conn->state = SOUP_NTLM_SSO_FAILED;
+ priv->password_state = SOUP_NTLM_PASSWORD_NONE;
+ } else {
+#endif
+ conn->state = SOUP_NTLM_FAILED;
+ priv->password_state = SOUP_NTLM_PASSWORD_REJECTED;
+#ifdef USE_NTLM_AUTH
+ }
+#endif
+ return TRUE;
+ }
+
+ if (conn->state == SOUP_NTLM_NEW && !auth_header[4])
+ return TRUE;
+
+ if (!auth_header[4] || !auth_header[5]) {
+ conn->state = SOUP_NTLM_FAILED;
+ return FALSE;
+ }
+
+ if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce,
+ priv->domain ? NULL : &priv->domain,
+ &conn->ntlmv2_session, &conn->negotiate_target,
+ &conn->target_info, &conn->target_info_sz)) {
+ conn->state = SOUP_NTLM_FAILED;
+ return FALSE;
+ }
+
+#ifdef USE_NTLM_AUTH
+ if (priv->sso_available && conn->state == SOUP_NTLM_SENT_REQUEST) {
+ char *input, *response;
+
+ /* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */
+ if (!sso_ntlm_initiate (priv)) {
+ conn->state = SOUP_NTLM_SSO_FAILED;
+ success = FALSE;
+ goto out;
+ }
+
+ input = g_strdup_printf ("TT %s\n", auth_header + 5);
+ response = sso_ntlm_response (priv, input, conn->state);
+ sso_ntlm_close (priv);
+ g_free (input);
+
+ if (!response) {
+ conn->state = SOUP_NTLM_SSO_FAILED;
+ success = FALSE;
+ } else if (!g_ascii_strcasecmp (response, "PW")) {
+ conn->state = SOUP_NTLM_SSO_FAILED;
+ priv->sso_available = FALSE;
+ g_free (response);
+ } else {
+ conn->response_header = response;
+ if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED)
+ priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED;
+ }
+ }
+ out:
+#endif
+
+ if (conn->state == SOUP_NTLM_SENT_REQUEST)
+ conn->state = SOUP_NTLM_RECEIVED_CHALLENGE;
+
+ g_object_set (G_OBJECT (auth),
+ SOUP_AUTH_REALM, priv->domain,
+ SOUP_AUTH_HOST, soup_message_get_uri (msg)->host,
+ NULL);
+ return success;
+}
+
+static GSList *
+soup_auth_ntlm_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
+{
+ char *space, *p;
+
+ space = g_strdup (source_uri->path);
+
+ /* Strip filename component */
+ p = strrchr (space, '/');
+ if (p && p != space && p[1])
+ *p = '\0';
+
+ return g_slist_prepend (NULL, space);
+}
+
+static void
+soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username,
+ const char *password)
+{
+ SoupAuthNTLM *auth_ntlm = SOUP_AUTH_NTLM (auth);
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (auth_ntlm);
+ const char *slash;
+
+ g_return_if_fail (username != NULL);
+ g_return_if_fail (password != NULL);
+
+ if (priv->username)
+ g_free (priv->username);
+ if (priv->domain)
+ g_free (priv->domain);
+
+ slash = strpbrk (username, "\\/");
+ if (slash) {
+ priv->domain = g_strndup (username, slash - username);
+ priv->username = g_strdup (slash + 1);
+ } else {
+ priv->domain = g_strdup ("");
+ priv->username = g_strdup (username);
+ }
+
+ soup_ntlm_nt_hash (password, priv->nt_hash);
+ soup_ntlm_lanmanager_hash (password, priv->lm_hash);
+
+ priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED;
+}
+
+static gboolean
+soup_auth_ntlm_is_authenticated (SoupAuth *auth)
+{
+ SoupAuthNTLM *auth_ntlm = SOUP_AUTH_NTLM (auth);
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (auth_ntlm);
+
+ return (priv->password_state != SOUP_NTLM_PASSWORD_NONE &&
+ priv->password_state != SOUP_NTLM_PASSWORD_REJECTED);
+}
+
+static gboolean
+soup_auth_ntlm_is_connection_ready (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer state)
+{
+ SoupAuthNTLM *auth_ntlm = SOUP_AUTH_NTLM (auth);
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (auth_ntlm);
+ SoupNTLMConnectionState *conn = state;
+
+ if (priv->password_state == SOUP_NTLM_PASSWORD_REJECTED)
+ return FALSE;
+
+ if (priv->password_state == SOUP_NTLM_PASSWORD_PROVIDED)
+ return TRUE;
+
+ return conn->state != SOUP_NTLM_FAILED;
+}
+
+static void
+got_final_auth_result (SoupMessage *msg, gpointer data)
+{
+ SoupAuth *auth = data;
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (SOUP_AUTH_NTLM (auth));
+
+ g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (got_final_auth_result), auth);
+
+ if (auth != soup_message_get_auth (msg))
+ return;
+
+ if (msg->status_code != SOUP_STATUS_UNAUTHORIZED)
+ priv->password_state = SOUP_NTLM_PASSWORD_ACCEPTED;
+}
+
+static char *
+soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer state)
+{
+ SoupAuthNTLM *auth_ntlm = SOUP_AUTH_NTLM (auth);
+ SoupAuthNTLMPrivate *priv = soup_auth_ntlm_get_instance_private (auth_ntlm);
+ SoupNTLMConnectionState *conn = state;
+ char *header = NULL;
+
+ switch (conn->state) {
+ case SOUP_NTLM_NEW:
+#ifdef USE_NTLM_AUTH
+ if (sso_ntlm_initiate (priv)) {
+ header = sso_ntlm_response (priv, "YR\n", conn->state);
+ if (header) {
+ if (g_ascii_strcasecmp (header, "PW") != 0) {
+ conn->state = SOUP_NTLM_SENT_REQUEST;
+ break;
+ } else {
+ g_free (header);
+ header = NULL;
+ priv->sso_available = FALSE;
+ }
+ } else {
+ g_debug ("NTLM single-sign-on using %s failed", NTLM_AUTH);
+ }
+ }
+ /* If NTLM single-sign-on fails, go back to original
+ * request handling process.
+ */
+#endif
+ header = soup_ntlm_request ();
+ conn->state = SOUP_NTLM_SENT_REQUEST;
+ break;
+ case SOUP_NTLM_RECEIVED_CHALLENGE:
+ if (conn->response_header) {
+ header = conn->response_header;
+ conn->response_header = NULL;
+ } else {
+ header = soup_ntlm_response (conn->nonce,
+ priv->username,
+ priv->nt_hash,
+ priv->lm_hash,
+ NULL,
+ priv->domain,
+ conn->ntlmv2_session,
+ conn->negotiate_target,
+ conn->target_info,
+ conn->target_info_sz);
+ }
+ g_clear_pointer (&conn->nonce, g_free);
+ conn->state = SOUP_NTLM_SENT_RESPONSE;
+
+ if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED) {
+ /* We need to know if this worked */
+ g_signal_connect (msg, "got-headers",
+ G_CALLBACK (got_final_auth_result),
+ auth);
+ }
+ break;
+#ifdef USE_NTLM_AUTH
+ case SOUP_NTLM_SSO_FAILED:
+ /* Restart request without SSO */
+ g_debug ("NTLM single-sign-on by using %s failed", NTLM_AUTH);
+ priv->sso_available = FALSE;
+ header = soup_ntlm_request ();
+ conn->state = SOUP_NTLM_SENT_REQUEST;
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return header;
+}
+
+static void
+soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class)
+{
+ SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_ntlm_class);
+ SoupConnectionAuthClass *connauth_class = SOUP_CONNECTION_AUTH_CLASS (auth_ntlm_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_ntlm_class);
+
+ auth_class->scheme_name = "NTLM";
+ auth_class->strength = 3;
+
+ auth_class->get_protection_space = soup_auth_ntlm_get_protection_space;
+ auth_class->authenticate = soup_auth_ntlm_authenticate;
+ auth_class->is_authenticated = soup_auth_ntlm_is_authenticated;
+
+ connauth_class->create_connection_state = soup_auth_ntlm_create_connection_state;
+ connauth_class->free_connection_state = soup_auth_ntlm_free_connection_state;
+ connauth_class->update_connection = soup_auth_ntlm_update_connection;
+ connauth_class->get_connection_authorization = soup_auth_ntlm_get_connection_authorization;
+ connauth_class->is_connection_ready = soup_auth_ntlm_is_connection_ready;
+
+ object_class->finalize = soup_auth_ntlm_finalize;
+
+#ifdef USE_NTLM_AUTH
+ ntlm_auth_available = g_file_test (NTLM_AUTH, G_FILE_TEST_IS_EXECUTABLE);
+ ntlm_auth_debug = (g_getenv ("SOUP_NTLM_AUTH_DEBUG") != NULL);
+#endif
+}
+
+static void md4sum (const unsigned char *in,
+ int nbytes,
+ unsigned char digest[16]);
+
+typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */
+
+static void deskey (DES_KS, unsigned char *, int);
+
+static void des (DES_KS, unsigned char *);
+
+static void setup_schedule (const guchar *key_56, DES_KS ks);
+
+static void calc_response (const guchar *key,
+ const guchar *plaintext,
+ guchar *results);
+
+#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \
+ "\x4B\x47\x53\x21\x40\x23\x24\x25" \
+ "\x00\x00\x00\x00\x00"
+
+static void
+soup_ntlm_lanmanager_hash (const char *password, guchar hash[21])
+{
+ guchar lm_password [15];
+ DES_KS ks;
+ int i;
+
+ for (i = 0; i < 14 && password [i]; i++)
+ lm_password [i] = g_ascii_toupper ((unsigned char) password [i]);
+
+ for (; i < 15; i++)
+ lm_password [i] = '\0';
+
+ memcpy (hash, LM_PASSWORD_MAGIC, 21);
+
+ setup_schedule (lm_password, ks);
+ des (ks, hash);
+
+ setup_schedule (lm_password + 7, ks);
+ des (ks, hash + 8);
+}
+
+static void
+soup_ntlm_nt_hash (const char *password, guchar hash[21])
+{
+ unsigned char *buf, *p;
+
+ p = buf = g_malloc (strlen (password) * 2);
+
+ while (*password) {
+ *p++ = *password++;
+ *p++ = '\0';
+ }
+
+ md4sum (buf, p - buf, hash);
+ memset (hash + 16, 0, 5);
+
+ g_free (buf);
+}
+
+typedef struct {
+ guint16 length;
+ guint16 length2;
+ guint16 offset;
+ guchar zero_pad[2];
+} NTLMString;
+
+#define NTLM_CHALLENGE_NONCE_OFFSET 24
+#define NTLM_CHALLENGE_NONCE_LENGTH 8
+#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12
+#define NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET 40
+
+#define NTLM_CHALLENGE_FLAGS_OFFSET 20
+#define NTLM_FLAGS_NEGOTIATE_NTLMV2 0x00080000
+#define NTLM_FLAGS_NEGOTIATE_TARGET_INFORMATION 0x00800000
+#define NTLM_FLAGS_REQUEST_TARGET 0x00000004
+
+#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00"
+#define NTLM_RESPONSE_FLAGS 0x8201
+#define NTLM_RESPONSE_TARGET_INFORMATION_OFFSET 44
+#define NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY 0x00080000
+
+#define HMAC_MD5_LENGTH 16
+
+typedef struct {
+ guchar header[12];
+
+ NTLMString lm_resp;
+ NTLMString nt_resp;
+ NTLMString domain;
+ NTLMString user;
+ NTLMString host;
+ NTLMString session_key;
+
+ guint32 flags;
+} NTLMResponse;
+
+static void
+ntlm_set_string (NTLMString *string, int *offset, int len)
+{
+ string->offset = GUINT16_TO_LE (*offset);
+ string->length = string->length2 = GUINT16_TO_LE (len);
+ *offset += len;
+}
+
+static char *
+soup_ntlm_request (void)
+{
+ return g_strdup ("NTLM TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA");
+}
+
+static gboolean
+soup_ntlm_parse_challenge (const char *challenge,
+ char **nonce,
+ char **default_domain,
+ gboolean *ntlmv2_session,
+ gboolean *negotiate_target,
+ char **target_info,
+ size_t *target_info_sz)
+{
+ gsize clen;
+ NTLMString domain;
+ NTLMString target;
+ guchar *chall;
+ guint32 flags;
+
+ chall = g_base64_decode (challenge, &clen);
+ if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET ||
+ clen < NTLM_CHALLENGE_NONCE_OFFSET + NTLM_CHALLENGE_NONCE_LENGTH) {
+ g_free (chall);
+ return FALSE;
+ }
+
+ memcpy (&flags, chall + NTLM_CHALLENGE_FLAGS_OFFSET, sizeof(flags));
+ flags = GUINT_FROM_LE (flags);
+ *ntlmv2_session = (flags & NTLM_FLAGS_NEGOTIATE_NTLMV2) ? TRUE : FALSE;
+ /* To know if NTLMv2 responses should be calculated */
+ *negotiate_target = (flags & NTLM_FLAGS_NEGOTIATE_TARGET_INFORMATION ) ? TRUE : FALSE;
+ if (*negotiate_target) {
+ if (clen < NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET + sizeof (target)) {
+ g_free (chall);
+ return FALSE;
+ }
+ }
+
+ if (default_domain) {
+ memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain));
+ domain.length = GUINT16_FROM_LE (domain.length);
+ domain.offset = GUINT16_FROM_LE (domain.offset);
+
+ if (clen < domain.length + domain.offset) {
+ g_free (chall);
+ return FALSE;
+ }
+
+ *default_domain = g_convert ((char *)chall + domain.offset,
+ domain.length, "UTF-8", "UCS-2LE",
+ NULL, NULL, NULL);
+ }
+
+ if (nonce) {
+ *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET,
+ NTLM_CHALLENGE_NONCE_LENGTH);
+ }
+ /* For NTLMv2 response */
+ if (*negotiate_target && target_info) {
+ memcpy (&target, chall + NTLM_CHALLENGE_TARGET_INFORMATION_OFFSET, sizeof (target));
+ target.length = GUINT16_FROM_LE (target.length);
+ target.offset = GUINT16_FROM_LE (target.offset);
+
+ if (clen < target.length + target.offset) {
+ g_free (chall);
+ return FALSE;
+ }
+ *target_info = g_memdup (chall + target.offset, target.length);
+ *target_info_sz = target.length;
+ }
+
+ g_free (chall);
+ return TRUE;
+}
+
+static void
+calc_ntlm2_session_response (const char *nonce,
+ guchar nt_hash[21],
+ guchar lm_hash[21],
+ guchar *lm_resp,
+ gsize lm_resp_sz,
+ guchar *nt_resp)
+{
+ guint32 client_nonce[2];
+ guchar ntlmv2_hash[16];
+ GChecksum *ntlmv2_cksum;
+ gsize ntlmv2_hash_sz = sizeof (ntlmv2_hash);
+
+ /* FIXME: if GLib ever gets a more secure random number
+ * generator, use it here
+ */
+ client_nonce[0] = g_random_int();
+ client_nonce[1] = g_random_int();
+
+ ntlmv2_cksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (ntlmv2_cksum, (const guchar *) nonce, 8);
+ g_checksum_update (ntlmv2_cksum, (const guchar *) client_nonce, sizeof (client_nonce));
+ g_checksum_get_digest (ntlmv2_cksum, ntlmv2_hash, &ntlmv2_hash_sz);
+ g_checksum_free (ntlmv2_cksum);
+
+ /* Send the padded client nonce as a fake lm_resp */
+ memset (lm_resp, 0, lm_resp_sz);
+ memcpy (lm_resp, client_nonce, sizeof (client_nonce));
+
+ /* Compute nt_hash as usual but with a new nonce */
+ calc_response (nt_hash, ntlmv2_hash, nt_resp);
+}
+
+/* Compute HMAC-MD5 with Glib function*/
+static void
+calc_hmac_md5 (unsigned char *hmac, const guchar *key, gsize key_sz, const guchar *data, gsize data_sz)
+{
+ char *hmac_hex, *hex_pos;
+ size_t count;
+
+ hmac_hex = g_compute_hmac_for_data(G_CHECKSUM_MD5, key, key_sz, data, data_sz);
+ hex_pos = hmac_hex;
+ for (count = 0; count < HMAC_MD5_LENGTH; count++)
+ {
+ /* The 'hh' sscanf format modifier is C99, so we enable it on
+ * non-Windows or if __USE_MINGW_ANSI_STDIO is enabled or`
+ * if we are building on Visual Studio 2015 or later
+ */
+#if !defined (G_OS_WIN32) || (__USE_MINGW_ANSI_STDIO == 1) || (_MSC_VER >= 1900)
+ sscanf(hex_pos, "%2hhx", &hmac[count]);
+#else
+ unsigned int tmp_hmac;
+ sscanf(hex_pos, "%2x", &tmp_hmac);
+ hmac[count] = (guint8)tmp_hmac;
+#endif
+
+ hex_pos += 2;
+ }
+ g_free(hmac_hex);
+}
+
+static void
+calc_ntlmv2_response (const char *user, const char *domain,
+ const guchar *nt_hash, const gsize nt_hash_sz,
+ const guchar *nonce,
+ const char *target_info, size_t target_info_sz,
+ guchar *lm_resp, size_t lm_resp_sz,
+ guchar *nt_resp, size_t nt_resp_sz)
+{
+ const unsigned char blob_signature[] = {0x01,0x01,0x00,0x00};
+ const unsigned char blob_reserved[] = {0x00,0x00,0x00,0x00};
+ gint64 blob_timestamp;
+ unsigned char client_nonce[8];
+ const unsigned char blob_unknown[] = {0x00,0x00,0x00,0x00};
+
+ unsigned char ntv2_hash[HMAC_MD5_LENGTH];
+ guchar *nonce_blob, *blob, *p_blob;
+ unsigned char nonce_blob_hash[HMAC_MD5_LENGTH];
+ unsigned char nonce_client_nonce[16], nonce_client_nonce_hash[HMAC_MD5_LENGTH];
+ gchar *user_uppercase, *user_domain, *user_domain_conv;
+ gsize user_domain_conv_sz;
+ size_t blob_sz;
+ int i;
+
+ /* create HMAC-MD5 hash of Unicode uppercase username and Unicode domain */
+ user_uppercase = g_utf8_strup (user, strlen (user));
+ user_domain = g_strconcat (user_uppercase, domain, NULL);
+ user_domain_conv = g_convert (user_domain, -1, "UCS-2LE", "UTF-8", NULL, &user_domain_conv_sz, NULL);
+ calc_hmac_md5 (ntv2_hash, nt_hash, nt_hash_sz, (const guchar *)user_domain_conv, user_domain_conv_sz);
+ g_free (user_uppercase);
+ g_free (user_domain);
+ g_free (user_domain_conv);
+
+ /* create random client nonce */
+ for (i = 0; i < sizeof (client_nonce); i++)
+ {
+ client_nonce[i] = g_random_int();
+ }
+
+ /* create timestamp for blob
+ * LE, 64-bit signed value, number of tenths of a ms since January 1, 1601.*/
+ blob_timestamp = GINT64_TO_LE(((unsigned long)time(NULL) + 11644473600) * 10000000);
+
+ /* create blob */
+ blob_sz = sizeof (blob_signature) + sizeof (blob_reserved) +
+ sizeof (blob_timestamp) + sizeof (client_nonce) +
+ sizeof (blob_unknown) + target_info_sz;
+ p_blob = blob = g_malloc (blob_sz);
+ memset (blob, 0, blob_sz);
+ memcpy (p_blob, blob_signature, sizeof (blob_signature));
+ memcpy (p_blob += sizeof (blob_signature), blob_reserved, sizeof (blob_reserved));
+ memcpy (p_blob += sizeof (blob_reserved), &blob_timestamp, sizeof (blob_timestamp));
+ memcpy (p_blob += sizeof (blob_timestamp), client_nonce, sizeof (client_nonce));
+ memcpy (p_blob += sizeof (client_nonce), blob_unknown, sizeof (blob_unknown));
+ memcpy (p_blob += sizeof (blob_unknown), target_info, target_info_sz);
+
+ /* create HMAC-MD5 hash of concatenated nonce and blob */
+ nonce_blob = g_malloc (NTLM_CHALLENGE_NONCE_LENGTH + blob_sz);
+ memcpy (nonce_blob, nonce, NTLM_CHALLENGE_NONCE_LENGTH);
+ memcpy (nonce_blob + NTLM_CHALLENGE_NONCE_LENGTH, blob, blob_sz);
+ calc_hmac_md5 (nonce_blob_hash, (const guchar *)ntv2_hash, (gsize) sizeof (ntv2_hash), (const guchar *) nonce_blob, (gsize) NTLM_CHALLENGE_NONCE_LENGTH + blob_sz);
+ g_free (nonce_blob);
+
+ /* create NTv2 response */
+ memset (nt_resp, 0, nt_resp_sz);
+ memcpy (nt_resp, nonce_blob_hash, sizeof (nonce_blob_hash));
+ memcpy (nt_resp + sizeof (nonce_blob_hash), blob, blob_sz);
+
+ g_free (blob);
+
+ /* LMv2
+ * create HMAC-MD5 hash of concatenated nonce and client nonce
+ */
+ memcpy (nonce_client_nonce, nonce, NTLM_CHALLENGE_NONCE_LENGTH);
+ memcpy (nonce_client_nonce + NTLM_CHALLENGE_NONCE_LENGTH, client_nonce, sizeof (client_nonce));
+ calc_hmac_md5 (nonce_client_nonce_hash, (const guchar *) ntv2_hash, (gsize) sizeof (ntv2_hash), (const guchar *) nonce_client_nonce, (gsize) NTLM_CHALLENGE_NONCE_LENGTH + sizeof (client_nonce));
+
+ /* create LMv2 response */
+ memset (lm_resp, 0, lm_resp_sz);
+ memcpy (lm_resp, nonce_client_nonce_hash, sizeof (nonce_client_nonce_hash));
+ memcpy (lm_resp + sizeof (nonce_client_nonce_hash), client_nonce, sizeof (client_nonce));
+}
+
+static char *
+soup_ntlm_response (const char *nonce,
+ const char *user,
+ guchar nt_hash[21],
+ guchar lm_hash[21],
+ const char *host,
+ const char *domain,
+ gboolean ntlmv2_session,
+ gboolean negotiate_target,
+ const char *target_info,
+ size_t target_info_sz)
+{
+
+ int offset;
+ gsize hlen, dlen, ulen, nt_resp_sz;
+ guchar lm_resp[24], *nt_resp;
+ char *user_conv, *host_conv, *domain_conv;
+ NTLMResponse resp;
+ char *out, *p;
+ int state, save;
+
+ if (negotiate_target)
+ {
+ /* nonce_blob_hash 16 + blob_signature 4 + blob_reserved 4 +
+ * blob_timestamp 8 + client_nonce 8 + blob_unknown 4 +
+ * target_info*/
+ nt_resp_sz = NTLM_RESPONSE_TARGET_INFORMATION_OFFSET + target_info_sz;
+ } else {
+ nt_resp_sz = 24;
+ }
+ nt_resp = g_malloc (nt_resp_sz);
+
+ if (ntlmv2_session && !negotiate_target) {
+ calc_ntlm2_session_response (nonce, nt_hash, lm_hash,
+ lm_resp, sizeof(lm_resp), nt_resp);
+ } else if (!negotiate_target){
+ /* Compute a regular NTLMv1 response */
+ calc_response (nt_hash, (guchar *) nonce, nt_resp);
+ calc_response (lm_hash, (guchar *) nonce, lm_resp);
+ } else {
+ calc_ntlmv2_response (user, domain,
+ nt_hash, 21,
+ (guchar *) nonce,
+ target_info, target_info_sz,
+ lm_resp, sizeof (lm_resp),
+ nt_resp, (size_t) nt_resp_sz);
+ }
+
+ memset (&resp, 0, sizeof (resp));
+ memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header));
+ resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS);
+ if (ntlmv2_session)
+ resp.flags |= GUINT32_TO_LE (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY);
+ if (negotiate_target)
+ resp.flags |= GUINT32_TO_LE (NTLM_FLAGS_REQUEST_TARGET);
+ offset = sizeof (resp);
+
+ if (!host)
+ host = "UNKNOWN";
+
+ domain_conv = g_convert (domain, -1, "UCS-2LE", "UTF-8", NULL, &dlen, NULL);
+ user_conv = g_convert (user, -1, "UCS-2LE", "UTF-8", NULL, &ulen, NULL);
+ host_conv = g_convert (host, -1, "UCS-2LE", "UTF-8", NULL, &hlen, NULL);
+
+ ntlm_set_string (&resp.domain, &offset, dlen);
+ ntlm_set_string (&resp.user, &offset, ulen);
+ ntlm_set_string (&resp.host, &offset, hlen);
+ ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp));
+ ntlm_set_string (&resp.nt_resp, &offset, nt_resp_sz);
+
+ out = g_malloc (((offset + 3) * 4) / 3 + 6);
+ memcpy (out, "NTLM ", 5);
+ p = out + 5;
+
+ state = save = 0;
+
+ p += g_base64_encode_step ((const guchar *) &resp, sizeof (resp),
+ FALSE, p, &state, &save);
+ p += g_base64_encode_step ((const guchar *) domain_conv, dlen,
+ FALSE, p, &state, &save);
+ p += g_base64_encode_step ((const guchar *) user_conv, ulen,
+ FALSE, p, &state, &save);
+ p += g_base64_encode_step ((const guchar *) host_conv, hlen,
+ FALSE, p, &state, &save);
+ p += g_base64_encode_step (lm_resp, sizeof (lm_resp),
+ FALSE, p, &state, &save);
+ p += g_base64_encode_step (nt_resp, nt_resp_sz,
+ FALSE, p, &state, &save);
+ p += g_base64_encode_close (FALSE, p, &state, &save);
+ *p = '\0';
+
+ g_free (domain_conv);
+ g_free (user_conv);
+ g_free (host_conv);
+ g_free (nt_resp);
+
+ return out;
+}
+
+/* DES utils */
+/* Set up a key schedule based on a 56bit key */
+static void
+setup_schedule (const guchar *key_56, DES_KS ks)
+{
+ guchar key[8];
+ int i, c, bit;
+
+ key[0] = (key_56[0]) ;
+ key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF);
+ key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF);
+ key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF);
+ key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF);
+ key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF);
+ key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF);
+ key[7] = ((key_56[6] << 1) & 0xFF);
+
+ /* Fix parity */
+ for (i = 0; i < 8; i++) {
+ for (c = bit = 0; bit < 8; bit++)
+ if (key[i] & (1 << bit))
+ c++;
+ if (!(c & 1))
+ key[i] ^= 0x01;
+ }
+
+ deskey (ks, key, 0);
+}
+
+static void
+calc_response (const guchar *key, const guchar *plaintext, guchar *results)
+{
+ DES_KS ks;
+
+ memcpy (results, plaintext, 8);
+ memcpy (results + 8, plaintext, 8);
+ memcpy (results + 16, plaintext, 8);
+
+ setup_schedule (key, ks);
+ des (ks, results);
+
+ setup_schedule (key + 7, ks);
+ des (ks, results + 8);
+
+ setup_schedule (key + 14, ks);
+ des (ks, results + 16);
+}
+
+
+/*
+ * MD4 encoder. (The one everyone else uses is not GPL-compatible;
+ * this is a reimplementation from spec.) This doesn't need to be
+ * efficient for our purposes, although it would be nice to fix
+ * it to not malloc()...
+ */
+
+#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) )
+#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) )
+#define H(X,Y,Z) ( (X)^(Y)^(Z) )
+#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) )
+
+static void
+md4sum (const unsigned char *in, int nbytes, unsigned char digest[16])
+{
+ unsigned char *M;
+ guint32 A, B, C, D, AA, BB, CC, DD, X[16];
+ int pbytes, nbits = nbytes * 8, i, j;
+
+ /* There is *always* padding of at least one bit. */
+ pbytes = ((119 - (nbytes % 64)) % 64) + 1;
+ M = alloca (nbytes + pbytes + 8);
+ memcpy (M, in, nbytes);
+ memset (M + nbytes, 0, pbytes + 8);
+ M[nbytes] = 0x80;
+ M[nbytes + pbytes] = nbits & 0xFF;
+ M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF;
+ M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF;
+ M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF;
+
+ A = 0x67452301;
+ B = 0xEFCDAB89;
+ C = 0x98BADCFE;
+ D = 0x10325476;
+
+ for (i = 0; i < nbytes + pbytes + 8; i += 64) {
+ for (j = 0; j < 16; j++) {
+ X[j] = (M[i + j*4]) |
+ (M[i + j*4 + 1] << 8) |
+ (M[i + j*4 + 2] << 16) |
+ (M[i + j*4 + 3] << 24);
+ }
+
+ AA = A;
+ BB = B;
+ CC = C;
+ DD = D;
+
+ A = ROT (A + F(B, C, D) + X[0], 3);
+ D = ROT (D + F(A, B, C) + X[1], 7);
+ C = ROT (C + F(D, A, B) + X[2], 11);
+ B = ROT (B + F(C, D, A) + X[3], 19);
+ A = ROT (A + F(B, C, D) + X[4], 3);
+ D = ROT (D + F(A, B, C) + X[5], 7);
+ C = ROT (C + F(D, A, B) + X[6], 11);
+ B = ROT (B + F(C, D, A) + X[7], 19);
+ A = ROT (A + F(B, C, D) + X[8], 3);
+ D = ROT (D + F(A, B, C) + X[9], 7);
+ C = ROT (C + F(D, A, B) + X[10], 11);
+ B = ROT (B + F(C, D, A) + X[11], 19);
+ A = ROT (A + F(B, C, D) + X[12], 3);
+ D = ROT (D + F(A, B, C) + X[13], 7);
+ C = ROT (C + F(D, A, B) + X[14], 11);
+ B = ROT (B + F(C, D, A) + X[15], 19);
+
+ A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13);
+ A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3);
+ D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5);
+ C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9);
+ B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13);
+
+ A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15);
+ A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3);
+ D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9);
+ C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11);
+ B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15);
+
+ A += AA;
+ B += BB;
+ C += CC;
+ D += DD;
+ }
+
+ digest[0] = A & 0xFF;
+ digest[1] = (A >> 8) & 0xFF;
+ digest[2] = (A >> 16) & 0xFF;
+ digest[3] = (A >> 24) & 0xFF;
+ digest[4] = B & 0xFF;
+ digest[5] = (B >> 8) & 0xFF;
+ digest[6] = (B >> 16) & 0xFF;
+ digest[7] = (B >> 24) & 0xFF;
+ digest[8] = C & 0xFF;
+ digest[9] = (C >> 8) & 0xFF;
+ digest[10] = (C >> 16) & 0xFF;
+ digest[11] = (C >> 24) & 0xFF;
+ digest[12] = D & 0xFF;
+ digest[13] = (D >> 8) & 0xFF;
+ digest[14] = (D >> 16) & 0xFF;
+ digest[15] = (D >> 24) & 0xFF;
+}
+
+
+/* Public domain DES implementation from Phil Karn */
+static const guint32 Spbox[8][64] = {
+ { 0x01010400,0x00000000,0x00010000,0x01010404,
+ 0x01010004,0x00010404,0x00000004,0x00010000,
+ 0x00000400,0x01010400,0x01010404,0x00000400,
+ 0x01000404,0x01010004,0x01000000,0x00000004,
+ 0x00000404,0x01000400,0x01000400,0x00010400,
+ 0x00010400,0x01010000,0x01010000,0x01000404,
+ 0x00010004,0x01000004,0x01000004,0x00010004,
+ 0x00000000,0x00000404,0x00010404,0x01000000,
+ 0x00010000,0x01010404,0x00000004,0x01010000,
+ 0x01010400,0x01000000,0x01000000,0x00000400,
+ 0x01010004,0x00010000,0x00010400,0x01000004,
+ 0x00000400,0x00000004,0x01000404,0x00010404,
+ 0x01010404,0x00010004,0x01010000,0x01000404,
+ 0x01000004,0x00000404,0x00010404,0x01010400,
+ 0x00000404,0x01000400,0x01000400,0x00000000,
+ 0x00010004,0x00010400,0x00000000,0x01010004 },
+ { 0x80108020,0x80008000,0x00008000,0x00108020,
+ 0x00100000,0x00000020,0x80100020,0x80008020,
+ 0x80000020,0x80108020,0x80108000,0x80000000,
+ 0x80008000,0x00100000,0x00000020,0x80100020,
+ 0x00108000,0x00100020,0x80008020,0x00000000,
+ 0x80000000,0x00008000,0x00108020,0x80100000,
+ 0x00100020,0x80000020,0x00000000,0x00108000,
+ 0x00008020,0x80108000,0x80100000,0x00008020,
+ 0x00000000,0x00108020,0x80100020,0x00100000,
+ 0x80008020,0x80100000,0x80108000,0x00008000,
+ 0x80100000,0x80008000,0x00000020,0x80108020,
+ 0x00108020,0x00000020,0x00008000,0x80000000,
+ 0x00008020,0x80108000,0x00100000,0x80000020,
+ 0x00100020,0x80008020,0x80000020,0x00100020,
+ 0x00108000,0x00000000,0x80008000,0x00008020,
+ 0x80000000,0x80100020,0x80108020,0x00108000 },
+ { 0x00000208,0x08020200,0x00000000,0x08020008,
+ 0x08000200,0x00000000,0x00020208,0x08000200,
+ 0x00020008,0x08000008,0x08000008,0x00020000,
+ 0x08020208,0x00020008,0x08020000,0x00000208,
+ 0x08000000,0x00000008,0x08020200,0x00000200,
+ 0x00020200,0x08020000,0x08020008,0x00020208,
+ 0x08000208,0x00020200,0x00020000,0x08000208,
+ 0x00000008,0x08020208,0x00000200,0x08000000,
+ 0x08020200,0x08000000,0x00020008,0x00000208,
+ 0x00020000,0x08020200,0x08000200,0x00000000,
+ 0x00000200,0x00020008,0x08020208,0x08000200,
+ 0x08000008,0x00000200,0x00000000,0x08020008,
+ 0x08000208,0x00020000,0x08000000,0x08020208,
+ 0x00000008,0x00020208,0x00020200,0x08000008,
+ 0x08020000,0x08000208,0x00000208,0x08020000,
+ 0x00020208,0x00000008,0x08020008,0x00020200 },
+ { 0x00802001,0x00002081,0x00002081,0x00000080,
+ 0x00802080,0x00800081,0x00800001,0x00002001,
+ 0x00000000,0x00802000,0x00802000,0x00802081,
+ 0x00000081,0x00000000,0x00800080,0x00800001,
+ 0x00000001,0x00002000,0x00800000,0x00802001,
+ 0x00000080,0x00800000,0x00002001,0x00002080,
+ 0x00800081,0x00000001,0x00002080,0x00800080,
+ 0x00002000,0x00802080,0x00802081,0x00000081,
+ 0x00800080,0x00800001,0x00802000,0x00802081,
+ 0x00000081,0x00000000,0x00000000,0x00802000,
+ 0x00002080,0x00800080,0x00800081,0x00000001,
+ 0x00802001,0x00002081,0x00002081,0x00000080,
+ 0x00802081,0x00000081,0x00000001,0x00002000,
+ 0x00800001,0x00002001,0x00802080,0x00800081,
+ 0x00002001,0x00002080,0x00800000,0x00802001,
+ 0x00000080,0x00800000,0x00002000,0x00802080 },
+ { 0x00000100,0x02080100,0x02080000,0x42000100,
+ 0x00080000,0x00000100,0x40000000,0x02080000,
+ 0x40080100,0x00080000,0x02000100,0x40080100,
+ 0x42000100,0x42080000,0x00080100,0x40000000,
+ 0x02000000,0x40080000,0x40080000,0x00000000,
+ 0x40000100,0x42080100,0x42080100,0x02000100,
+ 0x42080000,0x40000100,0x00000000,0x42000000,
+ 0x02080100,0x02000000,0x42000000,0x00080100,
+ 0x00080000,0x42000100,0x00000100,0x02000000,
+ 0x40000000,0x02080000,0x42000100,0x40080100,
+ 0x02000100,0x40000000,0x42080000,0x02080100,
+ 0x40080100,0x00000100,0x02000000,0x42080000,
+ 0x42080100,0x00080100,0x42000000,0x42080100,
+ 0x02080000,0x00000000,0x40080000,0x42000000,
+ 0x00080100,0x02000100,0x40000100,0x00080000,
+ 0x00000000,0x40080000,0x02080100,0x40000100 },
+ { 0x20000010,0x20400000,0x00004000,0x20404010,
+ 0x20400000,0x00000010,0x20404010,0x00400000,
+ 0x20004000,0x00404010,0x00400000,0x20000010,
+ 0x00400010,0x20004000,0x20000000,0x00004010,
+ 0x00000000,0x00400010,0x20004010,0x00004000,
+ 0x00404000,0x20004010,0x00000010,0x20400010,
+ 0x20400010,0x00000000,0x00404010,0x20404000,
+ 0x00004010,0x00404000,0x20404000,0x20000000,
+ 0x20004000,0x00000010,0x20400010,0x00404000,
+ 0x20404010,0x00400000,0x00004010,0x20000010,
+ 0x00400000,0x20004000,0x20000000,0x00004010,
+ 0x20000010,0x20404010,0x00404000,0x20400000,
+ 0x00404010,0x20404000,0x00000000,0x20400010,
+ 0x00000010,0x00004000,0x20400000,0x00404010,
+ 0x00004000,0x00400010,0x20004010,0x00000000,
+ 0x20404000,0x20000000,0x00400010,0x20004010 },
+ { 0x00200000,0x04200002,0x04000802,0x00000000,
+ 0x00000800,0x04000802,0x00200802,0x04200800,
+ 0x04200802,0x00200000,0x00000000,0x04000002,
+ 0x00000002,0x04000000,0x04200002,0x00000802,
+ 0x04000800,0x00200802,0x00200002,0x04000800,
+ 0x04000002,0x04200000,0x04200800,0x00200002,
+ 0x04200000,0x00000800,0x00000802,0x04200802,
+ 0x00200800,0x00000002,0x04000000,0x00200800,
+ 0x04000000,0x00200800,0x00200000,0x04000802,
+ 0x04000802,0x04200002,0x04200002,0x00000002,
+ 0x00200002,0x04000000,0x04000800,0x00200000,
+ 0x04200800,0x00000802,0x00200802,0x04200800,
+ 0x00000802,0x04000002,0x04200802,0x04200000,
+ 0x00200800,0x00000000,0x00000002,0x04200802,
+ 0x00000000,0x00200802,0x04200000,0x00000800,
+ 0x04000002,0x04000800,0x00000800,0x00200002 },
+ { 0x10001040,0x00001000,0x00040000,0x10041040,
+ 0x10000000,0x10001040,0x00000040,0x10000000,
+ 0x00040040,0x10040000,0x10041040,0x00041000,
+ 0x10041000,0x00041040,0x00001000,0x00000040,
+ 0x10040000,0x10000040,0x10001000,0x00001040,
+ 0x00041000,0x00040040,0x10040040,0x10041000,
+ 0x00001040,0x00000000,0x00000000,0x10040040,
+ 0x10000040,0x10001000,0x00041040,0x00040000,
+ 0x00041040,0x00040000,0x10041000,0x00001000,
+ 0x00000040,0x10040040,0x00001000,0x00041040,
+ 0x10001000,0x00000040,0x10000040,0x10040000,
+ 0x10040040,0x10000000,0x00040000,0x10001040,
+ 0x00000000,0x10041040,0x00040040,0x10000040,
+ 0x10040000,0x10001000,0x10001040,0x00000000,
+ 0x10041040,0x00041000,0x00041000,0x00001040,
+ 0x00001040,0x00040040,0x10000000,0x10041000 }
+};
+
+#undef F
+#define F(l,r,key){\
+ work = ((r >> 4) | (r << 28)) ^ key[0];\
+ l ^= Spbox[6][work & 0x3f];\
+ l ^= Spbox[4][(work >> 8) & 0x3f];\
+ l ^= Spbox[2][(work >> 16) & 0x3f];\
+ l ^= Spbox[0][(work >> 24) & 0x3f];\
+ work = r ^ key[1];\
+ l ^= Spbox[7][work & 0x3f];\
+ l ^= Spbox[5][(work >> 8) & 0x3f];\
+ l ^= Spbox[3][(work >> 16) & 0x3f];\
+ l ^= Spbox[1][(work >> 24) & 0x3f];\
+}
+/* Encrypt or decrypt a block of data in ECB mode */
+static void
+des (guint32 ks[16][2], unsigned char block[8])
+{
+ guint32 left,right,work;
+
+ /* Read input block and place in left/right in big-endian order */
+ left = ((guint32)block[0] << 24)
+ | ((guint32)block[1] << 16)
+ | ((guint32)block[2] << 8)
+ | (guint32)block[3];
+ right = ((guint32)block[4] << 24)
+ | ((guint32)block[5] << 16)
+ | ((guint32)block[6] << 8)
+ | (guint32)block[7];
+
+ /* Hoey's clever initial permutation algorithm, from Outerbridge
+ * (see Schneier p 478)
+ *
+ * The convention here is the same as Outerbridge: rotate each
+ * register left by 1 bit, i.e., so that "left" contains permuted
+ * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
+ * (using origin-1 numbering as in the FIPS). This allows us to avoid
+ * one of the two rotates that would otherwise be required in each of
+ * the 16 rounds.
+ */
+ work = ((left >> 4) ^ right) & 0x0f0f0f0f;
+ right ^= work;
+ left ^= work << 4;
+ work = ((left >> 16) ^ right) & 0xffff;
+ right ^= work;
+ left ^= work << 16;
+ work = ((right >> 2) ^ left) & 0x33333333;
+ left ^= work;
+ right ^= (work << 2);
+ work = ((right >> 8) ^ left) & 0xff00ff;
+ left ^= work;
+ right ^= (work << 8);
+ right = (right << 1) | (right >> 31);
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = (left << 1) | (left >> 31);
+
+ /* Now do the 16 rounds */
+ F(left,right,ks[0]);
+ F(right,left,ks[1]);
+ F(left,right,ks[2]);
+ F(right,left,ks[3]);
+ F(left,right,ks[4]);
+ F(right,left,ks[5]);
+ F(left,right,ks[6]);
+ F(right,left,ks[7]);
+ F(left,right,ks[8]);
+ F(right,left,ks[9]);
+ F(left,right,ks[10]);
+ F(right,left,ks[11]);
+ F(left,right,ks[12]);
+ F(right,left,ks[13]);
+ F(left,right,ks[14]);
+ F(right,left,ks[15]);
+
+ /* Inverse permutation, also from Hoey via Outerbridge and Schneier */
+ right = (right << 31) | (right >> 1);
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = (left >> 1) | (left << 31);
+ work = ((left >> 8) ^ right) & 0xff00ff;
+ right ^= work;
+ left ^= work << 8;
+ work = ((left >> 2) ^ right) & 0x33333333;
+ right ^= work;
+ left ^= work << 2;
+ work = ((right >> 16) ^ left) & 0xffff;
+ left ^= work;
+ right ^= work << 16;
+ work = ((right >> 4) ^ left) & 0x0f0f0f0f;
+ left ^= work;
+ right ^= work << 4;
+
+ /* Put the block back into the user's buffer with final swap */
+ block[0] = right >> 24;
+ block[1] = right >> 16;
+ block[2] = right >> 8;
+ block[3] = right;
+ block[4] = left >> 24;
+ block[5] = left >> 16;
+ block[6] = left >> 8;
+ block[7] = left;
+}
+
+/* Key schedule-related tables from FIPS-46 */
+
+/* permuted choice table (key) */
+static const unsigned char pc1[] = {
+ 57, 49, 41, 33, 25, 17, 9,
+ 1, 58, 50, 42, 34, 26, 18,
+ 10, 2, 59, 51, 43, 35, 27,
+ 19, 11, 3, 60, 52, 44, 36,
+
+ 63, 55, 47, 39, 31, 23, 15,
+ 7, 62, 54, 46, 38, 30, 22,
+ 14, 6, 61, 53, 45, 37, 29,
+ 21, 13, 5, 28, 20, 12, 4
+};
+
+/* number left rotations of pc1 */
+static const unsigned char totrot[] = {
+ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
+};
+
+/* permuted choice key (table) */
+static const unsigned char pc2[] = {
+ 14, 17, 11, 24, 1, 5,
+ 3, 28, 15, 6, 21, 10,
+ 23, 19, 12, 4, 26, 8,
+ 16, 7, 27, 20, 13, 2,
+ 41, 52, 31, 37, 47, 55,
+ 30, 40, 51, 45, 33, 48,
+ 44, 49, 39, 56, 34, 53,
+ 46, 42, 50, 36, 29, 32
+};
+
+/* End of DES-defined tables */
+
+
+/* bit 0 is left-most in byte */
+static const int bytebit[] = {
+ 0200,0100,040,020,010,04,02,01
+};
+
+
+/* Generate key schedule for encryption or decryption
+ * depending on the value of "decrypt"
+ */
+static void
+deskey (DES_KS k, unsigned char *key, int decrypt)
+{
+ unsigned char pc1m[56]; /* place to modify pc1 into */
+ unsigned char pcr[56]; /* place to rotate pc1 into */
+ register int i,j,l;
+ int m;
+ unsigned char ks[8];
+
+ for (j=0; j<56; j++) { /* convert pc1 to bits of key */
+ l=pc1[j]-1; /* integer bit location */
+ m = l & 07; /* find bit */
+ pc1m[j]=(key[l>>3] & /* find which key byte l is in */
+ bytebit[m]) /* and which bit of that byte */
+ ? 1 : 0; /* and store 1-bit result */
+ }
+ for (i=0; i<16; i++) { /* key chunk for each iteration */
+ memset(ks,0,sizeof(ks)); /* Clear key schedule */
+ for (j=0; j<56; j++) /* rotate pc1 the right amount */
+ pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28];
+ /* rotate left and right halves independently */
+ for (j=0; j<48; j++){ /* select bits individually */
+ /* check bit that goes to ks[j] */
+ if (pcr[pc2[j]-1]){
+ /* mask it in if it's there */
+ l= j % 6;
+ ks[j/6] |= bytebit[l] >> 2;
+ }
+ }
+ /* Now convert to packed odd/even interleaved form */
+ k[i][0] = ((guint32)ks[0] << 24)
+ | ((guint32)ks[2] << 16)
+ | ((guint32)ks[4] << 8)
+ | ((guint32)ks[6]);
+ k[i][1] = ((guint32)ks[1] << 24)
+ | ((guint32)ks[3] << 16)
+ | ((guint32)ks[5] << 8)
+ | ((guint32)ks[7]);
+ }
+}
diff --git a/libsoup/auth/soup-auth-ntlm.h b/libsoup/auth/soup-auth-ntlm.h
new file mode 100644
index 00000000..ef07353b
--- /dev/null
+++ b/libsoup/auth/soup-auth-ntlm.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifndef __SOUP_AUTH_NTLM_H__
+#define __SOUP_AUTH_NTLM_H__ 1
+
+#include "soup-connection-auth.h"
+
+#define SOUP_AUTH_NTLM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLM))
+#define SOUP_AUTH_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass))
+#define SOUP_IS_AUTH_NTLM(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_NTLM))
+#define SOUP_IS_AUTH_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_NTLM))
+#define SOUP_AUTH_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass))
+
+typedef struct {
+ SoupConnectionAuth parent;
+
+} SoupAuthNTLM;
+
+typedef struct {
+ SoupConnectionAuthClass parent_class;
+
+} SoupAuthNTLMClass;
+
+#endif /* __SOUP_AUTH_NTLM_H__ */
diff --git a/libsoup/auth/soup-auth.c b/libsoup/auth/soup-auth.c
new file mode 100644
index 00000000..2ba75e23
--- /dev/null
+++ b/libsoup/auth/soup-auth.c
@@ -0,0 +1,554 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth.c: HTTP Authentication framework
+ *
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth.h"
+#include "soup.h"
+#include "soup-connection-auth.h"
+
+/**
+ * SECTION:soup-auth
+ * @short_description: HTTP client-side authentication support
+ * @see_also: #SoupSession
+ *
+ * #SoupAuth objects store the authentication data associated with a
+ * given bit of web space. They are created automatically by
+ * #SoupSession.
+ **/
+
+/**
+ * SoupAuth:
+ *
+ * The abstract base class for handling authentication. Specific HTTP
+ * Authentication mechanisms are implemented by its subclasses, but
+ * applications never need to be aware of the specific subclasses
+ * being used.
+ **/
+
+typedef struct {
+ gboolean proxy;
+ char *host;
+} SoupAuthPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SoupAuth, soup_auth, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+
+ PROP_SCHEME_NAME,
+ PROP_REALM,
+ PROP_HOST,
+ PROP_IS_FOR_PROXY,
+ PROP_IS_AUTHENTICATED,
+
+ LAST_PROP
+};
+
+static void
+soup_auth_init (SoupAuth *auth)
+{
+}
+
+static void
+soup_auth_finalize (GObject *object)
+{
+ SoupAuth *auth = SOUP_AUTH (object);
+ SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
+
+ g_free (auth->realm);
+ g_free (priv->host);
+
+ G_OBJECT_CLASS (soup_auth_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuth *auth = SOUP_AUTH (object);
+ SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
+
+ switch (prop_id) {
+ case PROP_REALM:
+ g_free (auth->realm);
+ auth->realm = g_value_dup_string (value);
+ break;
+ case PROP_HOST:
+ g_free (priv->host);
+ priv->host = g_value_dup_string (value);
+ break;
+ case PROP_IS_FOR_PROXY:
+ priv->proxy = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuth *auth = SOUP_AUTH (object);
+ SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
+
+ switch (prop_id) {
+ case PROP_SCHEME_NAME:
+ g_value_set_string (value, soup_auth_get_scheme_name (auth));
+ break;
+ case PROP_REALM:
+ g_value_set_string (value, soup_auth_get_realm (auth));
+ break;
+ case PROP_HOST:
+ g_value_set_string (value, soup_auth_get_host (auth));
+ break;
+ case PROP_IS_FOR_PROXY:
+ g_value_set_boolean (value, priv->proxy);
+ break;
+ case PROP_IS_AUTHENTICATED:
+ g_value_set_boolean (value, soup_auth_is_authenticated (auth));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+auth_can_authenticate (SoupAuth *auth)
+{
+ return TRUE;
+}
+
+static void
+soup_auth_class_init (SoupAuthClass *auth_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_class);
+
+ auth_class->can_authenticate = auth_can_authenticate;
+
+ object_class->finalize = soup_auth_finalize;
+ object_class->set_property = soup_auth_set_property;
+ object_class->get_property = soup_auth_get_property;
+
+ /* properties */
+ /**
+ * SOUP_AUTH_SCHEME_NAME:
+ *
+ * An alias for the #SoupAuth:scheme-name property. (The
+ * authentication scheme name.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_SCHEME_NAME,
+ g_param_spec_string (SOUP_AUTH_SCHEME_NAME,
+ "Scheme name",
+ "Authentication scheme name",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_REALM:
+ *
+ * An alias for the #SoupAuth:realm property. (The
+ * authentication realm.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REALM,
+ g_param_spec_string (SOUP_AUTH_REALM,
+ "Realm",
+ "Authentication realm",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_HOST:
+ *
+ * An alias for the #SoupAuth:host property. (The
+ * host being authenticated to.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_HOST,
+ g_param_spec_string (SOUP_AUTH_HOST,
+ "Host",
+ "Authentication host",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_IS_FOR_PROXY:
+ *
+ * An alias for the #SoupAuth:is-for-proxy property. (Whether
+ * or not the auth is for a proxy server.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_IS_FOR_PROXY,
+ g_param_spec_boolean (SOUP_AUTH_IS_FOR_PROXY,
+ "For Proxy",
+ "Whether or not the auth is for a proxy server",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_IS_AUTHENTICATED:
+ *
+ * An alias for the #SoupAuth:is-authenticated property.
+ * (Whether or not the auth has been authenticated.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_IS_AUTHENTICATED,
+ g_param_spec_boolean (SOUP_AUTH_IS_AUTHENTICATED,
+ "Authenticated",
+ "Whether or not the auth is authenticated",
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * soup_auth_new:
+ * @type: the type of auth to create (a subtype of #SoupAuth)
+ * @msg: the #SoupMessage the auth is being created for
+ * @auth_header: the WWW-Authenticate/Proxy-Authenticate header
+ *
+ * Creates a new #SoupAuth of type @type with the information from
+ * @msg and @auth_header.
+ *
+ * This is called by #SoupSession; you will normally not create auths
+ * yourself.
+ *
+ * Return value: (nullable): the new #SoupAuth, or %NULL if it could
+ * not be created
+ **/
+SoupAuth *
+soup_auth_new (GType type, SoupMessage *msg, const char *auth_header)
+{
+ SoupAuth *auth;
+ GHashTable *params;
+ const char *scheme, *realm;
+
+ g_return_val_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH), NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+ g_return_val_if_fail (auth_header != NULL, NULL);
+
+ auth = g_object_new (type,
+ SOUP_AUTH_IS_FOR_PROXY, (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED),
+ SOUP_AUTH_HOST, soup_message_get_uri (msg)->host,
+ NULL);
+
+ scheme = soup_auth_get_scheme_name (auth);
+ if (g_ascii_strncasecmp (auth_header, scheme, strlen (scheme)) != 0) {
+ g_object_unref (auth);
+ return NULL;
+ }
+
+ params = soup_header_parse_param_list (auth_header + strlen (scheme));
+ if (!params)
+ params = g_hash_table_new (NULL, NULL);
+
+ realm = g_hash_table_lookup (params, "realm");
+ if (realm)
+ auth->realm = g_strdup (realm);
+
+ if (!SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params)) {
+ g_object_unref (auth);
+ auth = NULL;
+ }
+ soup_header_free_param_list (params);
+ return auth;
+}
+
+/**
+ * soup_auth_update:
+ * @auth: a #SoupAuth
+ * @msg: the #SoupMessage @auth is being updated for
+ * @auth_header: the WWW-Authenticate/Proxy-Authenticate header
+ *
+ * Updates @auth with the information from @msg and @auth_header,
+ * possibly un-authenticating it. As with soup_auth_new(), this is
+ * normally only used by #SoupSession.
+ *
+ * Return value: %TRUE if @auth is still a valid (but potentially
+ * unauthenticated) #SoupAuth. %FALSE if something about @auth_params
+ * could not be parsed or incorporated into @auth at all.
+ **/
+gboolean
+soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header)
+{
+ GHashTable *params;
+ const char *scheme, *realm;
+ gboolean was_authenticated, success;
+
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE);
+ g_return_val_if_fail (auth_header != NULL, FALSE);
+
+ scheme = soup_auth_get_scheme_name (auth);
+ if (g_ascii_strncasecmp (auth_header, scheme, strlen (scheme)) != 0)
+ return FALSE;
+
+ params = soup_header_parse_param_list (auth_header + strlen (scheme));
+ if (!params)
+ params = g_hash_table_new (NULL, NULL);
+
+ realm = g_hash_table_lookup (params, "realm");
+ if (realm && auth->realm && strcmp (realm, auth->realm) != 0) {
+ soup_header_free_param_list (params);
+ return FALSE;
+ }
+
+ was_authenticated = soup_auth_is_authenticated (auth);
+ success = SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params);
+ if (was_authenticated != soup_auth_is_authenticated (auth))
+ g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED);
+ soup_header_free_param_list (params);
+ return success;
+}
+
+/**
+ * soup_auth_authenticate:
+ * @auth: a #SoupAuth
+ * @username: the username provided by the user or client
+ * @password: the password provided by the user or client
+ *
+ * Call this on an auth to authenticate it; normally this will cause
+ * the auth's message to be requeued with the new authentication info.
+ **/
+void
+soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password)
+{
+ gboolean was_authenticated;
+
+ g_return_if_fail (SOUP_IS_AUTH (auth));
+ g_return_if_fail (username != NULL);
+ g_return_if_fail (password != NULL);
+
+ was_authenticated = soup_auth_is_authenticated (auth);
+ SOUP_AUTH_GET_CLASS (auth)->authenticate (auth, username, password);
+ if (was_authenticated != soup_auth_is_authenticated (auth))
+ g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED);
+}
+
+/**
+ * soup_auth_is_for_proxy:
+ * @auth: a #SoupAuth
+ *
+ * Tests whether or not @auth is associated with a proxy server rather
+ * than an "origin" server.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+soup_auth_is_for_proxy (SoupAuth *auth)
+{
+ SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
+
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
+
+ return priv->proxy;
+}
+
+/**
+ * soup_auth_get_scheme_name:
+ * @auth: a #SoupAuth
+ *
+ * Returns @auth's scheme name. (Eg, "Basic", "Digest", or "NTLM")
+ *
+ * Return value: the scheme name
+ **/
+const char *
+soup_auth_get_scheme_name (SoupAuth *auth)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+
+ return SOUP_AUTH_GET_CLASS (auth)->scheme_name;
+}
+
+/**
+ * soup_auth_get_host:
+ * @auth: a #SoupAuth
+ *
+ * Returns the host that @auth is associated with.
+ *
+ * Return value: the hostname
+ **/
+const char *
+soup_auth_get_host (SoupAuth *auth)
+{
+ SoupAuthPrivate *priv = soup_auth_get_instance_private (auth);
+
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+
+ return priv->host;
+}
+
+/**
+ * soup_auth_get_realm:
+ * @auth: a #SoupAuth
+ *
+ * Returns @auth's realm. This is an identifier that distinguishes
+ * separate authentication spaces on a given server, and may be some
+ * string that is meaningful to the user. (Although it is probably not
+ * localized.)
+ *
+ * Return value: the realm name
+ **/
+const char *
+soup_auth_get_realm (SoupAuth *auth)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+
+ return auth->realm;
+}
+
+/**
+ * soup_auth_get_info:
+ * @auth: a #SoupAuth
+ *
+ * Gets an opaque identifier for @auth, for use as a hash key or the
+ * like. #SoupAuth objects from the same server with the same
+ * identifier refer to the same authentication domain (eg, the URLs
+ * associated with them take the same usernames and passwords).
+ *
+ * Return value: the identifier
+ **/
+char *
+soup_auth_get_info (SoupAuth *auth)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+
+ if (SOUP_IS_CONNECTION_AUTH (auth))
+ return g_strdup (SOUP_AUTH_GET_CLASS (auth)->scheme_name);
+ else {
+ return g_strdup_printf ("%s:%s",
+ SOUP_AUTH_GET_CLASS (auth)->scheme_name,
+ auth->realm);
+ }
+}
+
+/**
+ * soup_auth_is_authenticated:
+ * @auth: a #SoupAuth
+ *
+ * Tests if @auth has been given a username and password
+ *
+ * Return value: %TRUE if @auth has been given a username and password
+ **/
+gboolean
+soup_auth_is_authenticated (SoupAuth *auth)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE);
+
+ return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth);
+}
+
+/**
+ * soup_auth_get_authorization:
+ * @auth: a #SoupAuth
+ * @msg: the #SoupMessage to be authorized
+ *
+ * Generates an appropriate "Authorization" header for @msg. (The
+ * session will only call this if soup_auth_is_authenticated()
+ * returned %TRUE.)
+ *
+ * Return value: the "Authorization" header, which must be freed.
+ **/
+char *
+soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+ g_return_val_if_fail (msg != NULL, NULL);
+
+ return SOUP_AUTH_GET_CLASS (auth)->get_authorization (auth, msg);
+}
+
+/**
+ * soup_auth_is_ready:
+ * @auth: a #SoupAuth
+ * @msg: a #SoupMessage
+ *
+ * Tests if @auth is ready to make a request for @msg with. For most
+ * auths, this is equivalent to soup_auth_is_authenticated(), but for
+ * some auth types (eg, NTLM), the auth may be sendable (eg, as an
+ * authentication request) even before it is authenticated.
+ *
+ * Return value: %TRUE if @auth is ready to make a request with.
+ *
+ * Since: 2.42
+ **/
+gboolean
+soup_auth_is_ready (SoupAuth *auth,
+ SoupMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), TRUE);
+
+ if (SOUP_AUTH_GET_CLASS (auth)->is_ready)
+ return SOUP_AUTH_GET_CLASS (auth)->is_ready (auth, msg);
+ else
+ return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth);
+}
+
+/**
+ * soup_auth_can_authenticate:
+ * @auth: a #SoupAuth
+ *
+ * Tests if @auth is able to authenticate by providing credentials to the
+ * soup_auth_authenticate().
+ *
+ * Return value: %TRUE if @auth is able to accept credentials.
+ *
+ * Since: 2.54
+ **/
+gboolean
+soup_auth_can_authenticate (SoupAuth *auth)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE);
+
+ return SOUP_AUTH_GET_CLASS (auth)->can_authenticate (auth);
+}
+
+/**
+ * soup_auth_get_protection_space:
+ * @auth: a #SoupAuth
+ * @source_uri: the URI of the request that @auth was generated in
+ * response to.
+ *
+ * Returns a list of paths on the server which @auth extends over.
+ * (All subdirectories of these paths are also assumed to be part
+ * of @auth's protection space, unless otherwise discovered not to
+ * be.)
+ *
+ * Return value: (element-type utf8) (transfer full): the list of
+ * paths, which can be freed with soup_auth_free_protection_space().
+ **/
+GSList *
+soup_auth_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
+{
+ g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
+ g_return_val_if_fail (source_uri != NULL, NULL);
+
+ return SOUP_AUTH_GET_CLASS (auth)->get_protection_space (auth, source_uri);
+}
+
+/**
+ * soup_auth_free_protection_space: (skip)
+ * @auth: a #SoupAuth
+ * @space: the return value from soup_auth_get_protection_space()
+ *
+ * Frees @space.
+ **/
+void
+soup_auth_free_protection_space (SoupAuth *auth, GSList *space)
+{
+ g_slist_free_full (space, g_free);
+}
diff --git a/libsoup/auth/soup-auth.h b/libsoup/auth/soup-auth.h
new file mode 100644
index 00000000..987946f9
--- /dev/null
+++ b/libsoup/auth/soup-auth.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#ifndef __SOUP_AUTH_H__
+#define __SOUP_AUTH_H__ 1
+
+#include "soup-types.h"
+#include "soup-headers.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH (soup_auth_get_type ())
+#define SOUP_AUTH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH, SoupAuth))
+#define SOUP_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH, SoupAuthClass))
+#define SOUP_IS_AUTH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH))
+#define SOUP_IS_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH))
+#define SOUP_AUTH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH, SoupAuthClass))
+
+struct _SoupAuth {
+ GObject parent;
+
+ char *realm;
+};
+
+typedef struct {
+ GObjectClass parent_class;
+
+ const char *scheme_name;
+ guint strength;
+
+ gboolean (*update) (SoupAuth *auth,
+ SoupMessage *msg,
+ GHashTable *auth_header);
+
+ GSList * (*get_protection_space) (SoupAuth *auth,
+ SoupURI *source_uri);
+
+ void (*authenticate) (SoupAuth *auth,
+ const char *username,
+ const char *password);
+ gboolean (*is_authenticated) (SoupAuth *auth);
+
+ char * (*get_authorization) (SoupAuth *auth,
+ SoupMessage *msg);
+
+ gboolean (*is_ready) (SoupAuth *auth,
+ SoupMessage *msg);
+
+ gboolean (*can_authenticate) (SoupAuth *auth);
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+} SoupAuthClass;
+
+#define SOUP_AUTH_SCHEME_NAME "scheme-name"
+#define SOUP_AUTH_REALM "realm"
+#define SOUP_AUTH_HOST "host"
+#define SOUP_AUTH_IS_FOR_PROXY "is-for-proxy"
+#define SOUP_AUTH_IS_AUTHENTICATED "is-authenticated"
+
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_get_type (void);
+
+SOUP_AVAILABLE_IN_2_4
+SoupAuth *soup_auth_new (GType type,
+ SoupMessage *msg,
+ const char *auth_header);
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_update (SoupAuth *auth,
+ SoupMessage *msg,
+ const char *auth_header);
+
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_is_for_proxy (SoupAuth *auth);
+SOUP_AVAILABLE_IN_2_4
+const char *soup_auth_get_scheme_name (SoupAuth *auth);
+SOUP_AVAILABLE_IN_2_4
+const char *soup_auth_get_host (SoupAuth *auth);
+SOUP_AVAILABLE_IN_2_4
+const char *soup_auth_get_realm (SoupAuth *auth);
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_get_info (SoupAuth *auth);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_authenticate (SoupAuth *auth,
+ const char *username,
+ const char *password);
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_is_authenticated (SoupAuth *auth);
+SOUP_AVAILABLE_IN_2_42
+gboolean soup_auth_is_ready (SoupAuth *auth,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_54
+gboolean soup_auth_can_authenticate (SoupAuth *auth);
+
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_get_authorization (SoupAuth *auth,
+ SoupMessage *msg);
+
+SOUP_AVAILABLE_IN_2_4
+GSList *soup_auth_get_protection_space (SoupAuth *auth,
+ SoupURI *source_uri);
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_free_protection_space (SoupAuth *auth,
+ GSList *space);
+
+/* The actual auth types, which can be added/removed as features */
+
+#define SOUP_TYPE_AUTH_BASIC (soup_auth_basic_get_type ())
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_basic_get_type (void);
+#define SOUP_TYPE_AUTH_DIGEST (soup_auth_digest_get_type ())
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_digest_get_type (void);
+#define SOUP_TYPE_AUTH_NTLM (soup_auth_ntlm_get_type ())
+SOUP_AVAILABLE_IN_2_4
+GType soup_auth_ntlm_get_type (void);
+#define SOUP_TYPE_AUTH_NEGOTIATE (soup_auth_negotiate_get_type ())
+SOUP_AVAILABLE_IN_2_54
+GType soup_auth_negotiate_get_type (void);
+
+SOUP_AVAILABLE_IN_2_54
+gboolean soup_auth_negotiate_supported (void);
+
+G_END_DECLS
+
+#endif /* __SOUP_AUTH_H__ */
diff --git a/libsoup/auth/soup-connection-auth.c b/libsoup/auth/soup-connection-auth.c
new file mode 100644
index 00000000..f55cfe6b
--- /dev/null
+++ b/libsoup/auth/soup-connection-auth.c
@@ -0,0 +1,190 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-connection-auth.c: Abstract base class for hacky Microsoft
+ * connection-based auth mechanisms (NTLM and Negotiate)
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#include "soup-connection-auth.h"
+#include "soup.h"
+#include "soup-connection.h"
+#include "soup-message-private.h"
+
+struct SoupConnectionAuthPrivate {
+ GHashTable *conns;
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SoupConnectionAuth, soup_connection_auth, SOUP_TYPE_AUTH)
+
+static void
+soup_connection_auth_init (SoupConnectionAuth *auth)
+{
+ auth->priv = soup_connection_auth_get_instance_private (auth);
+
+ auth->priv->conns = g_hash_table_new (NULL, NULL);
+}
+
+static void connection_disconnected (SoupConnection *conn, gpointer user_data);
+
+static void
+soup_connection_auth_free_connection_state (SoupConnectionAuth *auth,
+ SoupConnection *conn,
+ gpointer state)
+{
+ g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_disconnected), auth);
+ SOUP_CONNECTION_AUTH_GET_CLASS (auth)->free_connection_state (auth, state);
+}
+
+static void
+connection_disconnected (SoupConnection *conn, gpointer user_data)
+{
+ SoupConnectionAuth *auth = user_data;
+ gpointer state;
+
+ state = g_hash_table_lookup (auth->priv->conns, conn);
+ g_hash_table_remove (auth->priv->conns, conn);
+ soup_connection_auth_free_connection_state (auth, conn, state);
+}
+
+static void
+soup_connection_auth_finalize (GObject *object)
+{
+ SoupConnectionAuth *auth = SOUP_CONNECTION_AUTH (object);
+ GHashTableIter iter;
+ gpointer conn, state;
+
+ g_hash_table_iter_init (&iter, auth->priv->conns);
+ while (g_hash_table_iter_next (&iter, &conn, &state)) {
+ soup_connection_auth_free_connection_state (auth, conn, state);
+ g_hash_table_iter_remove (&iter);
+ }
+ g_hash_table_destroy (auth->priv->conns);
+
+ G_OBJECT_CLASS (soup_connection_auth_parent_class)->finalize (object);
+}
+
+
+/**
+ * soup_connection_auth_get_connection_state_for_message:
+ * @auth: a #SoupConnectionAuth
+ * @msg: a #SoupMessage
+ *
+ * Returns an associated connection state object for the given @auth and @msg.
+ *
+ * This function is only useful from within implementations of SoupConnectionAuth
+ * subclasses.
+ *
+ * Return value: (transfer none): the connection state
+ *
+ * Since: 2.58
+ **/
+gpointer
+soup_connection_auth_get_connection_state_for_message (SoupConnectionAuth *auth,
+ SoupMessage *msg)
+{
+ SoupConnection *conn;
+ gpointer state;
+
+ g_return_val_if_fail (SOUP_IS_CONNECTION_AUTH (auth), NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+ conn = soup_message_get_connection (msg);
+ state = g_hash_table_lookup (auth->priv->conns, conn);
+ if (state)
+ return state;
+
+ state = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->create_connection_state (auth);
+ if (conn) {
+ g_signal_connect (conn, "disconnected",
+ G_CALLBACK (connection_disconnected), auth);
+ }
+
+ g_hash_table_insert (auth->priv->conns, conn, state);
+ return state;
+}
+
+static gboolean
+soup_connection_auth_update (SoupAuth *auth,
+ SoupMessage *msg,
+ GHashTable *auth_params)
+{
+ SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+ gpointer conn = soup_connection_auth_get_connection_state_for_message (cauth, msg);
+ GHashTableIter iter;
+ GString *auth_header;
+ gpointer key, value;
+ gboolean result;
+
+ /* Recreate @auth_header out of @auth_params. If the
+ * base64 data ended with 1 or more "="s, then it
+ * will have been parsed as key=value. Otherwise
+ * it will all have been parsed as key, and value
+ * will be %NULL.
+ */
+ auth_header = g_string_new (soup_auth_get_scheme_name (auth));
+ g_hash_table_iter_init (&iter, auth_params);
+ if (g_hash_table_iter_next (&iter, &key, &value)) {
+ if (value) {
+ g_string_append_printf (auth_header, " %s=%s",
+ (char *)key,
+ (char *)value);
+ } else {
+ g_string_append_printf (auth_header, " %s",
+ (char *)key);
+ }
+
+ if (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_string_free (auth_header, TRUE);
+ return FALSE;
+ }
+ }
+
+ result = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+ update_connection (cauth, msg, auth_header->str, conn);
+
+ g_string_free (auth_header, TRUE);
+ return result;
+}
+
+static char *
+soup_connection_auth_get_authorization (SoupAuth *auth,
+ SoupMessage *msg)
+{
+ SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+ gpointer conn = soup_connection_auth_get_connection_state_for_message (cauth, msg);
+
+ return SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+ get_connection_authorization (cauth, msg, conn);
+}
+
+static gboolean
+soup_connection_auth_is_ready (SoupAuth *auth,
+ SoupMessage *msg)
+{
+ SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth);
+ gpointer conn = soup_connection_auth_get_connection_state_for_message (cauth, msg);
+
+ return SOUP_CONNECTION_AUTH_GET_CLASS (auth)->
+ is_connection_ready (SOUP_CONNECTION_AUTH (auth), msg, conn);
+}
+
+static void
+soup_connection_auth_class_init (SoupConnectionAuthClass *connauth_class)
+{
+ SoupAuthClass *auth_class = SOUP_AUTH_CLASS (connauth_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (connauth_class);
+
+ auth_class->update = soup_connection_auth_update;
+ auth_class->get_authorization = soup_connection_auth_get_authorization;
+ auth_class->is_ready = soup_connection_auth_is_ready;
+
+ object_class->finalize = soup_connection_auth_finalize;
+}
diff --git a/libsoup/auth/soup-connection-auth.h b/libsoup/auth/soup-connection-auth.h
new file mode 100644
index 00000000..b7ef16fe
--- /dev/null
+++ b/libsoup/auth/soup-connection-auth.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#ifndef __SOUP_CONNECTION_AUTH_H__
+#define __SOUP_CONNECTION_AUTH_H__ 1
+
+#include "soup-auth.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_CONNECTION_AUTH (soup_connection_auth_get_type ())
+#define SOUP_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuth))
+#define SOUP_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass))
+#define SOUP_IS_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CONNECTION_AUTH))
+#define SOUP_IS_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CONNECTION_AUTH))
+#define SOUP_CONNECTION_AUTH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass))
+
+typedef struct SoupConnectionAuthPrivate SoupConnectionAuthPrivate;
+
+typedef struct {
+ SoupAuth parent;
+
+ SoupConnectionAuthPrivate *priv;
+} SoupConnectionAuth;
+
+typedef struct {
+ SoupAuthClass parent_class;
+
+ gpointer (*create_connection_state) (SoupConnectionAuth *auth);
+ void (*free_connection_state) (SoupConnectionAuth *auth,
+ gpointer conn);
+
+ gboolean (*update_connection) (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ const char *auth_header,
+ gpointer conn);
+ char *(*get_connection_authorization) (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer conn);
+ gboolean (*is_connection_ready) (SoupConnectionAuth *auth,
+ SoupMessage *msg,
+ gpointer conn);
+} SoupConnectionAuthClass;
+
+GType soup_connection_auth_get_type (void);
+
+SOUP_AVAILABLE_IN_2_58
+gpointer soup_connection_auth_get_connection_state_for_message
+ (SoupConnectionAuth *auth,
+ SoupMessage *message);
+G_END_DECLS
+
+#endif /* __SOUP_CONNECTION_AUTH_H__ */