diff options
| author | Patrick Griffis <pgriffis@igalia.com> | 2020-03-10 13:58:38 -0700 |
|---|---|---|
| committer | Patrick Griffis <pgriffis@igalia.com> | 2020-09-19 15:41:24 -0700 |
| commit | 5dcc1a9cffb351eec1e882b82c4dcf08145fa280 (patch) | |
| tree | 0a08ae0d46408914453892d26fff7f9e410587a2 /libsoup/auth | |
| parent | 1b3b31371f01af8b6081e5358273b4b28ff3489b (diff) | |
| download | libsoup-5dcc1a9cffb351eec1e882b82c4dcf08145fa280.tar.gz | |
Reorganize source tree
Diffstat (limited to 'libsoup/auth')
| -rw-r--r-- | libsoup/auth/soup-auth-basic.c | 145 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-basic.h | 27 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-digest.c | 490 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-digest.h | 64 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain-basic.c | 344 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain-basic.h | 59 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain-digest.c | 450 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain-digest.h | 63 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain.c | 635 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-domain.h | 110 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-manager.c | 862 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-manager.h | 49 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-negotiate.c | 622 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-negotiate.h | 28 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-ntlm.c | 1513 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth-ntlm.h | 27 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth.c | 554 | ||||
| -rw-r--r-- | libsoup/auth/soup-auth.h | 130 | ||||
| -rw-r--r-- | libsoup/auth/soup-connection-auth.c | 190 | ||||
| -rw-r--r-- | libsoup/auth/soup-connection-auth.h | 55 |
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__ */ |
