[libsoup] Bug 587145 - Add GSS-Negotiate support



commit 6a8826f0d25459c943719b88f22619f22b81c547
Author: Guido Guenther <agx sigxcpu org>
Date:   Wed Mar 2 15:20:16 2016 +0100

    Bug 587145 - Add GSS-Negotiate support
    
    If a "WWW-Authenticate: Negotiate" HTTP header is spotted libsoup will check if
    the host is on blacklist and the authentication fails if so. Otherwise the host
    is compared against a trusted URIs (if the trusted URIs list is not set all
    the HTTPS requests are trusted by default) and then processed. The trusted URIs
    list and blacklist are both created when a SoupNegotiateAuth is created. The
    trusted URIs list (blacklist) is parsed from the SOUP_GSSAPI_TRUSTED_URIS (
    SOUP_GSSAPI_BLACKLIST_URIS) environment variable that expects the URIs be comma
    separated (e.g. "http://www.example.com,https://www.test.com:80";). Then the
    request is processed by the GSS library (the SPNEGO mechanism is used) which
    produces a token that is send back to the server in the next request. The reply
    is then again processed by the GSS library and the authentication succeeds by
    receiving the GSS_S_COMPLETE status or we continue negotiating when the
    GSS_S_CONTINUE_NEEDED is received.
    
    The SoupAuth object is marked as not authenticated if a user will try to call
    the soup_auth_authenticate() with the credentials provided as this is not
    supported.
    
    If the libsoup is configured with GSS-Negotiate support, a Kerberos library with
    GSSAPI support needs to be available on the system (MIT Kerberos was tested
    while working on this). Developers can check whether the libsoup was
    compiled with the GSS-Negotiate support enabled by checking the
    soup_auth_negotiate_supported() function.
    
    To easily test the GSS-Negotiate functionality a new argument "N" was added to
    the examples/get utility.
    
    A support for NTLMSSP is provided by this patch given that a Kerberos library
    supports NTLMSSP mechanism via GSSAPI. For MIT Kerberos one can use gss-ntlmssp
    module, https://fedorahosted.org/gss-ntlmssp/.
    
    Co-Authored-By: Tomas Popela <tpopela redhat com>
    Co-Authored-By: David Woodhouse <dwmw2 infradead org>
    Co-Authored-By: Dan Winship <danw gnome org>

 configure.ac                            |   28 ++
 docs/reference/Makefile.am              |    2 +-
 docs/reference/libsoup-2.4-sections.txt |    4 +
 examples/get.c                          |   21 +
 libsoup/Makefile.am                     |    8 +-
 libsoup/soup-auth-manager.c             |    3 +
 libsoup/soup-auth-negotiate.c           |  611 +++++++++++++++++++++++++++++++
 libsoup/soup-auth-negotiate.h           |   28 ++
 libsoup/soup-auth.c                     |   26 ++
 libsoup/soup-auth.h                     |   10 +
 libsoup/soup-cookie.c                   |   21 +-
 libsoup/soup-misc-private.h             |    3 +
 libsoup/soup-misc.c                     |   35 ++
 13 files changed, 778 insertions(+), 22 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 8711a70..43afe2b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -321,6 +321,34 @@ AC_DEFINE_UNQUOTED(NTLM_AUTH, "$ntlm_auth", [Samba's 'winbind' daemon helper 'nt
 
 AX_CODE_COVERAGE
 
+dnl **********************
+dnl *** GSSAPI support ***
+dnl **********************
+AC_ARG_WITH(gssapi,
+           AS_HELP_STRING([--with-gssapi],
+           [Build with GSSAPI support [default=auto]]))
+
+if test "$with_gssapi" != "no"; then
+    AC_ARG_WITH(krb5-config,
+               AS_HELP_STRING([--with-krb5-config=PATH],[Where to look for krb5-config, path points to 
krb5-config installation (default: /usr/kerberos/bin/)]),
+               KRB5_CONFIG="$withval",
+               [AC_PATH_PROGS(KRB5_CONFIG, krb5-config, no, ${PATH}:/usr/kerberos/bin)])
+
+    if test "$KRB5_CONFIG" != "no"; then
+       KRB5_LIBS="`${KRB5_CONFIG} --libs gssapi`"
+       KRB5_CFLAGS="`${KRB5_CONFIG} --cflags gssapi`"
+       AC_SUBST(KRB5_CFLAGS)
+       AC_SUBST(KRB5_LIBS)
+       if test "$KRB5_CONFIG" != none; then
+           AC_DEFINE(LIBSOUP_HAVE_GSSAPI, 1, [Whether or not GSSAPI libs are available])
+       fi
+    else
+       if test "$with_gssapi" == "yes"; then
+           AC_MSG_ERROR([GSSAPI support requested but failed to found krb5-config. Try to set KRB5_CONFIG.])
+       fi
+    fi
+fi
+
 dnl ****************************************************
 dnl *** Warnings to show if using GCC                ***
 dnl *** (do this last so -Werror won't mess up tests ***
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 71306f7..425d42e 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -31,7 +31,7 @@ CFILE_GLOB=
 # Header files to ignore when scanning.
 IGNORE_HFILES= soup.h soup-autocleanups.h soup-enum-types.h \
        soup-message-private.h soup-session-private.h \
-       soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h \
+       soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h soup-auth-negotiate.h \
        soup-connection.h soup-connection-auth.h \
        soup-message-queue.h soup-path-map.h soup-gnome-features.h \
        soup-proxy-resolver.h soup-proxy-resolver-gnome.h \
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 5185d0a..285c30e 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -580,6 +580,8 @@ soup_auth_update
 SOUP_TYPE_AUTH_BASIC
 SOUP_TYPE_AUTH_DIGEST
 SOUP_TYPE_AUTH_NTLM
+SOUP_TYPE_AUTH_NEGOTIATE
+soup_auth_negotiate_supported
 <SUBSECTION>
 soup_auth_is_for_proxy
 soup_auth_get_scheme_name
@@ -588,6 +590,7 @@ soup_auth_get_realm
 soup_auth_get_info
 <SUBSECTION>
 soup_auth_authenticate
+soup_auth_can_authenticate
 soup_auth_is_authenticated
 soup_auth_is_ready
 <SUBSECTION>
@@ -612,6 +615,7 @@ SoupAuthClass
 soup_auth_basic_get_type
 soup_auth_digest_get_type
 soup_auth_ntlm_get_type
+soup_auth_negotiate_get_type
 <SUBSECTION Private>
 soup_auth_get_saved_password
 soup_auth_get_saved_users
diff --git a/examples/get.c b/examples/get.c
index a8888d4..df05289 100644
--- a/examples/get.c
+++ b/examples/get.c
@@ -4,6 +4,10 @@
  * Copyright (C) 2013 Igalia, S.L.
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -90,6 +94,7 @@ get_url (const char *url)
 
 static const char *ca_file, *proxy;
 static gboolean synchronous, ntlm;
+static gboolean negotiate;
 
 static GOptionEntry entries[] = {
        { "ca-file", 'c', 0,
@@ -119,6 +124,13 @@ static GOptionEntry entries[] = {
        { NULL }
 };
 
+static GOptionEntry negotiate_entries[] = {
+       { "negotiate", 'N', 0,
+         G_OPTION_ARG_NONE, &negotiate,
+         "Use Negotiate authentication", NULL },
+       { NULL }
+};
+
 int
 main (int argc, char **argv)
 {
@@ -130,6 +142,8 @@ main (int argc, char **argv)
 
        opts = g_option_context_new (NULL);
        g_option_context_add_main_entries (opts, entries, NULL);
+       if (soup_auth_negotiate_supported())
+               g_option_context_add_main_entries (opts, negotiate_entries, NULL);
        if (!g_option_context_parse (opts, &argc, &argv, &error)) {
                g_printerr ("Could not parse arguments: %s\n",
                            error->message);
@@ -183,6 +197,13 @@ main (int argc, char **argv)
                soup_uri_free (proxy_uri);
        }
 
+#ifdef LIBSOUP_HAVE_GSSAPI
+       if (negotiate) {
+               soup_session_add_feature_by_type (session,
+                                                 SOUP_TYPE_AUTH_NEGOTIATE);
+       }
+#endif /* LIBSOUP_HAVE_GSSAPI */
+
        if (!synchronous)
                loop = g_main_loop_new (NULL, TRUE);
 
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 19a77c7..0b208d3 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -23,7 +23,8 @@ AM_CPPFLAGS =                                 \
        $(GLIB_CFLAGS)                  \
        $(XML_CFLAGS)                   \
        $(SQLITE_CFLAGS)                \
-       $(CODE_COVERAGE_CFLAGS)
+       $(CODE_COVERAGE_CFLAGS)         \
+       $(KRB5_CFLAGS)
 
 libsoupincludedir = $(includedir)/libsoup-2.4/libsoup
 
@@ -101,7 +102,8 @@ libsoup_2_4_la_LIBADD =                     \
        $(GLIB_LIBS)                    \
        $(LIBWS2_32)                    \
        $(XML_LIBS)                     \
-       $(SQLITE_LIBS)
+       $(SQLITE_LIBS)                  \
+       $(KRB5_LIBS)
 
 libsoup_2_4_la_SOURCES =               \
        gconstructor.h                  \
@@ -113,6 +115,8 @@ libsoup_2_4_la_SOURCES =            \
        soup-auth-digest.c              \
        soup-auth-ntlm.h                \
        soup-auth-ntlm.c                \
+       soup-auth-negotiate.h           \
+       soup-auth-negotiate.c           \
        soup-auth-domain.c              \
        soup-auth-domain-basic.c        \
        soup-auth-domain-digest.c       \
diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c
index 0709c42..9455265 100644
--- a/libsoup/soup-auth-manager.c
+++ b/libsoup/soup-auth-manager.c
@@ -468,6 +468,9 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
        SoupAuthManagerPrivate *priv = manager->priv;
        SoupURI *uri;
 
+       if (!soup_auth_can_authenticate (auth))
+               return;
+
        if (proxy) {
                SoupMessageQueue *queue;
                SoupMessageQueueItem *item;
diff --git a/libsoup/soup-auth-negotiate.c b/libsoup/soup-auth-negotiate.c
new file mode 100644
index 0000000..5b9ea6c
--- /dev/null
+++ b/libsoup/soup-auth-negotiate.c
@@ -0,0 +1,611 @@
+/* -*- 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>
+#include <gssapi/gssapi_krb5.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_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 (SoupAuthNegotiate, soup_auth_negotiate, SOUP_TYPE_CONNECTION_AUTH)
+
+/**
+ * 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
+}
+
+#ifdef LIBSOUP_HAVE_GSSAPI
+#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;
+
+       gss_ctx_id_t context;
+       gss_name_t   server_name;
+
+       SoupNegotiateState state;
+} SoupNegotiateConnectionState;
+
+typedef struct {
+       gboolean is_authenticated;
+
+       gulong message_finished_signal_id;
+       gulong message_got_headers_signal_id;
+
+       SoupNegotiateConnectionState *conn_state;
+} SoupAuthNegotiatePrivate;
+
+#define SOUP_AUTH_NEGOTIATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_NEGOTIATE, 
SoupAuthNegotiatePrivate))
+
+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 void remove_server_response_handler (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)
+{
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth);
+       SoupNegotiateConnectionState *conn;
+
+       conn = g_slice_new0 (SoupNegotiateConnectionState);
+       conn->state = SOUP_NEGOTIATE_NEW;
+       priv->conn_state = conn;
+
+       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)
+{
+       SoupAuthNegotiate *negotiate = SOUP_AUTH_NEGOTIATE (auth);
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (negotiate);
+       SoupNegotiateConnectionState *conn = state;
+
+       free_connection_state_data (conn);
+
+       g_slice_free (SoupNegotiateConnectionState, conn);
+       priv->conn_state = NULL;
+}
+
+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_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_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_RECEIVED_CHALLENGE) {
+               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;
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth);
+       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)) {
+                       /* Register the callbacks just once */
+                       if (priv->message_finished_signal_id == 0) {
+                               gulong id = 0;
+                               id = g_signal_connect (msg,
+                                                      "finished",
+                                                      G_CALLBACK (remove_server_response_handler),
+                                                      auth);
+                               priv->message_finished_signal_id = id;
+                       }
+
+                       if (priv->message_got_headers_signal_id == 0) {
+                               gulong id = 0;
+                               /* Wait for the 2xx response to verify server response */
+                               id = g_signal_connect (msg,
+                                                      "got_headers",
+                                                      G_CALLBACK (check_server_response),
+                                                      auth);
+                               priv->message_got_headers_signal_id = id;
+                       }
+                       goto out;
+               } else {
+                       /* FIXME: report further upward via
+                        * soup_message_get_error_message  */
+                       g_warning ("gssapi step 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;
+
+       g_type_class_add_private (auth_negotiate_class, sizeof (SoupAuthNegotiatePrivate));
+
+       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_PRIVATE (negotiate);
+       SoupNegotiateConnectionState *conn = priv->conn_state;
+
+       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);
+
+       priv->is_authenticated = ret == AUTH_GSS_COMPLETE;
+
+       if (ret == AUTH_GSS_CONTINUE) {
+               conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
+       } else if (ret == AUTH_GSS_ERROR) {
+               if (err)
+                       g_warning ("%s", err->message);
+               conn->state = SOUP_NEGOTIATE_FAILED;
+       }
+ out:
+       g_clear_error (&err);
+}
+
+static void
+remove_server_response_handler (SoupMessage *msg, gpointer auth)
+{
+       SoupAuthNegotiate *negotiate = auth;
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (negotiate);
+
+       g_signal_handler_disconnect (msg, priv->message_got_headers_signal_id);
+       priv->message_got_headers_signal_id = 0;
+
+       g_signal_handler_disconnect (msg, priv->message_finished_signal_id);
+       priv->message_finished_signal_id = 0;
+}
+
+/* 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 msg_uri->scheme == SOUP_URI_SCHEME_HTTPS;
+
+       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/soup-auth-negotiate.h b/libsoup/soup-auth-negotiate.h
new file mode 100644
index 0000000..e976644
--- /dev/null
+++ b/libsoup/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/soup-auth.c b/libsoup/soup-auth.c
index 49d8df2..79aa7c3 100644
--- a/libsoup/soup-auth.c
+++ b/libsoup/soup-auth.c
@@ -129,6 +129,11 @@ soup_auth_get_property (GObject *object, guint prop_id,
        }
 }
 
+static gboolean
+auth_can_authenticate (SoupAuth *auth)
+{
+       return TRUE;
+}
 
 static void
 soup_auth_class_init (SoupAuthClass *auth_class)
@@ -137,6 +142,8 @@ soup_auth_class_init (SoupAuthClass *auth_class)
 
        g_type_class_add_private (auth_class, sizeof (SoupAuthPrivate));
 
+       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;
@@ -491,6 +498,25 @@ soup_auth_is_ready (SoupAuth    *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
diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h
index d2e6a10..c92699d 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -48,6 +48,8 @@ typedef struct {
        gboolean     (*is_ready)             (SoupAuth      *auth,
                                              SoupMessage   *msg);
 
+       gboolean     (*can_authenticate)     (SoupAuth      *auth);
+
        /* Padding for future expansion */
        void (*_libsoup_reserved2) (void);
        void (*_libsoup_reserved3) (void);
@@ -92,6 +94,8 @@ 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, 
@@ -115,6 +119,9 @@ 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);
 
 /* Deprecated SoupPasswordManager-related APIs: all are now no-ops */
 SOUP_AVAILABLE_IN_2_28
@@ -135,6 +142,9 @@ void        soup_auth_has_saved_password (SoupAuth   *auth,
                                          const char *username,
                                          const char *password);
 
+SOUP_AVAILABLE_IN_2_54
+gboolean    soup_auth_negotiate_supported   (void);
+
 G_END_DECLS
 
 #endif /* SOUP_AUTH_H */
diff --git a/libsoup/soup-cookie.c b/libsoup/soup-cookie.c
index 5af36e9..cbb991a 100644
--- a/libsoup/soup-cookie.c
+++ b/libsoup/soup-cookie.c
@@ -13,6 +13,7 @@
 #include <string.h>
 
 #include "soup-cookie.h"
+#include "soup-misc-private.h"
 #include "soup.h"
 
 /**
@@ -107,28 +108,10 @@ soup_cookie_copy (SoupCookie *cookie)
 gboolean
 soup_cookie_domain_matches (SoupCookie *cookie, const char *host)
 {
-       char *match;
-       int dlen;
-       const char *domain;
-
        g_return_val_if_fail (cookie != NULL, FALSE);
        g_return_val_if_fail (host != NULL, FALSE);
 
-       domain = cookie->domain;
-
-       if (!g_ascii_strcasecmp (domain, host))
-               return TRUE;
-       if (*domain != '.')
-               return FALSE;
-       if (!g_ascii_strcasecmp (domain + 1, host))
-               return TRUE;
-       dlen = strlen (domain);
-       while ((match = strstr (host, domain))) {
-               if (!match[dlen])
-                       return TRUE;
-               host = match + 1;
-       }
-       return FALSE;
+       return soup_host_matches_host (cookie->domain, host);
 }
 
 static inline const char *
diff --git a/libsoup/soup-misc-private.h b/libsoup/soup-misc-private.h
index 7826533..6171ab8 100644
--- a/libsoup/soup-misc-private.h
+++ b/libsoup/soup-misc-private.h
@@ -42,4 +42,7 @@ guint soup_message_headers_get_ranges_internal (SoupMessageHeaders  *hdrs,
 
 SoupAddress *soup_address_new_from_gsockaddr (GSocketAddress *addr);
 
+gboolean           soup_host_matches_host    (const gchar *host,
+                                             const gchar *compare_with);
+
 #endif /* SOUP_MISC_PRIVATE_H */
diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c
index 073e895..92dbdb5 100644
--- a/libsoup/soup-misc.c
+++ b/libsoup/soup-misc.c
@@ -241,3 +241,38 @@ const char soup_char_attributes[] = {
        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
 };
+
+/**
+ * soup_host_matches_host
+ * @host: a URI
+ * @compare_with: a URI
+ *
+ * Checks if the @host and @compare_with exactly match or prefixed with a dot.
+ *
+ * Return value: %TRUE if the hosts match, %FALSE otherwise
+ *
+ * Since: 2.54
+ **/
+gboolean
+soup_host_matches_host (const gchar *host, const gchar *compare_with)
+{
+       char *match;
+       int dlen;
+
+       g_return_val_if_fail (host != NULL, FALSE);
+       g_return_val_if_fail (compare_with != NULL, FALSE);
+
+       if (!g_ascii_strcasecmp (host, compare_with))
+               return TRUE;
+       if (*host != '.')
+               return FALSE;
+       if (!g_ascii_strcasecmp (host + 1, compare_with))
+               return TRUE;
+       dlen = strlen (host);
+       while ((match = strstr (compare_with, host))) {
+               if (!match[dlen])
+                       return TRUE;
+               compare_with = match + 1;
+       }
+       return FALSE;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]