[libsoup/wip/tpopela/negotiate: 1/8] Rebased Guido G ünther's work from [0] on top of 2.52.0



commit 9f2b3e9c80f06679d041a94b2dca7b67f4b0353b
Author: Tomas Popela <tpopela redhat com>
Date:   Wed Sep 30 08:52:12 2015 +0200

    Rebased Guido Günther's work from [0] on top of 2.52.0
    
    [0] - http://anonscm.debian.org/cgit/users/agx/libsoup.git/log/

 configure.ac                   |   15 ++
 examples/get.c                 |   10 +-
 libsoup/Makefile.am            |   25 +++-
 libsoup/libsoup-2.4.sym        |    1 +
 libsoup/libsoup-gssapi-2.4.sym |    3 +
 libsoup/soup-auth-negotiate.c  |  437 ++++++++++++++++++++++++++++++++++++++++
 libsoup/soup-auth-negotiate.h  |   27 +++
 libsoup/soup-auth.h            |    2 +
 libsoup/soup-gssapi.c          |  169 ++++++++++++++++
 libsoup/soup-gssapi.h          |   41 ++++
 libsoup/soup-status.h          |    1 +
 11 files changed, 729 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d15b40c..58173e6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -317,6 +317,21 @@ AC_DEFINE_UNQUOTED(NTLM_AUTH, "$ntlm_auth", [Samba's 'winbind' daemon helper 'nt
 
 AX_CODE_COVERAGE
 
+dnl **********************
+dnl *** GSSAPI support ***
+dnl **********************
+AC_PATH_PROG([KRB5_CONFIG], krb5-config, none, $PATH:/usr/kerberos/bin)
+if test "x$KRB5_CONFIG" != "xnone"; 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(HAVE_GSSAPI, 1, [Whether or not gssapi libs are available])
+    fi
+fi
+AM_CONDITIONAL(BUILD_LIBSOUP_GSSAPI, test $KRB5_CONFIG != none)
+
 dnl ****************************************************
 dnl *** Warnings to show if using GCC                ***
 dnl *** (do this last so -Werror won't mess up tests ***
diff --git a/examples/get.c b/examples/get.c
index a8888d4..6f761af 100644
--- a/examples/get.c
+++ b/examples/get.c
@@ -89,7 +89,7 @@ get_url (const char *url)
 }
 
 static const char *ca_file, *proxy;
-static gboolean synchronous, ntlm;
+static gboolean synchronous, ntlm, negotiate;
 
 static GOptionEntry entries[] = {
        { "ca-file", 'c', 0,
@@ -104,6 +104,9 @@ static GOptionEntry entries[] = {
        { "ntlm", 'n', 0,
          G_OPTION_ARG_NONE, &ntlm,
          "Use NTLM authentication", NULL },
+       { "negotiate", 'N', 0,
+         G_OPTION_ARG_NONE, &negotiate,
+         "Use Negotiate authentication", NULL },
        { "output", 'o', 0,
          G_OPTION_ARG_STRING, &output_file_path,
          "Write the received data to FILE instead of stdout", "FILE" },
@@ -183,6 +186,11 @@ main (int argc, char **argv)
                soup_uri_free (proxy_uri);
        }
 
+       if (negotiate) {
+               soup_session_add_feature_by_type(session,
+                                                SOUP_TYPE_AUTH_NEGOTIATE);
+       }
+
        if (!synchronous)
                loop = g_main_loop_new (NULL, TRUE);
 
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 345bd3e..00ac40b 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -20,7 +20,8 @@ AM_CPPFLAGS =                                 \
        $(GLIB_CFLAGS)                  \
        $(XML_CFLAGS)                   \
        $(SQLITE_CFLAGS)                \
-       $(CODE_COVERAGE_CFLAGS)
+       $(CODE_COVERAGE_CFLAGS)         \
+       $(KRB5_CFLAGS)
 
 libsoupincludedir = $(includedir)/libsoup-2.4/libsoup
 
@@ -110,6 +111,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       \
@@ -242,6 +245,26 @@ libsoup_gnome_2_4_la_SOURCES =             \
 
 endif
 
+if BUILD_LIBSOUP_GSSAPI
+
+lib_LTLIBRARIES += libsoup-gssapi-2.4.la
+
+libsoup_gssapi_2_4_la_LDFLAGS =        \
+       -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) \
+       -no-undefined \
+       -export-symbols $(srcdir)/libsoup-gssapi-2.4.sym
+
+EXTRA_DIST += libsoup-gssapi-2.4.sym
+
+libsoup_gssapi_2_4_la_LIBADD =         \
+       $(KRB5_LIBS)                    \
+       $(GLIB_LIBS)
+
+libsoup_gssapi_2_4_la_SOURCES =                \
+       soup-gssapi.c
+
+endif
+
 GLIB_GENERATED = soup-enum-types.c soup-enum-types.h
 BUILT_SOURCES = \
        $(GLIB_GENERATED)   \
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index e6ff89e..11833cb 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -79,6 +79,7 @@ soup_auth_is_for_proxy
 soup_auth_is_ready
 soup_auth_manager_get_type
 soup_auth_manager_use_auth
+soup_auth_negotiate_get_type
 soup_auth_new
 soup_auth_ntlm_get_type
 soup_auth_save_password
diff --git a/libsoup/libsoup-gssapi-2.4.sym b/libsoup/libsoup-gssapi-2.4.sym
new file mode 100644
index 0000000..2df49d6
--- /dev/null
+++ b/libsoup/libsoup-gssapi-2.4.sym
@@ -0,0 +1,3 @@
+soup_gss_client_init
+soup_gss_client_step
+soup_gss_client_cleanup
diff --git a/libsoup/soup-auth-negotiate.c b/libsoup/soup-auth-negotiate.c
new file mode 100644
index 0000000..79db107
--- /dev/null
+++ b/libsoup/soup-auth-negotiate.c
@@ -0,0 +1,437 @@
+/* -*- Mode: C; tabstop: 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>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_GSSAPI
+# include <gssapi/gssapi.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-negotiate.h"
+#include "soup-gssapi.h"
+#include "soup-headers.h"
+#include "soup-message.h"
+#include "soup-message-private.h"
+#include "soup-misc.h"
+#include "soup-uri.h"
+
+static gboolean soup_gss_build_response (SoupNegotiateConnectionState *conn,
+                                        SoupAuth *auth, GError **err);
+static gchar** parse_trusted_uris (void);
+static gboolean check_auth_trusted_uri (SoupAuthNegotiate *negotiate,
+                                       SoupMessage *msg);
+
+typedef struct {
+       char *username;
+} SoupAuthNegotiatePrivate;
+#define SOUP_AUTH_NEGOTIATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_NEGOTIATE, 
SoupAuthNegotiatePrivate))
+
+G_DEFINE_TYPE (SoupAuthNegotiate, soup_auth_negotiate, SOUP_TYPE_CONNECTION_AUTH)
+
+/* Function pointers to dlopen'ed libsoup-gssapi */
+struct {
+       int (*client_init)(SoupNegotiateConnectionState *conn,
+                          const char *host,
+                          GError **err);
+       int (*client_step)(SoupNegotiateConnectionState *conn,
+                          const char *challenge,
+                          GError **err);
+       void (*client_cleanup)(SoupNegotiateConnectionState *conn);
+} soup_gssapi_syms;
+gboolean have_gssapi;
+
+static gchar **trusted_uris;
+
+static void
+soup_auth_negotiate_init (SoupAuthNegotiate *negotiate)
+{
+}
+
+static void
+soup_auth_negotiate_finalize (GObject *object)
+{
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (object);
+
+       g_free (priv->username);
+
+       G_OBJECT_CLASS (soup_auth_negotiate_parent_class)->finalize (object);
+}
+
+static gpointer
+soup_auth_negotiate_create_connection_state (SoupConnectionAuth *auth)
+{
+       return g_slice_new0 (SoupNegotiateConnectionState);
+}
+
+static void
+soup_auth_negotiate_free_connection_state (SoupConnectionAuth *auth,
+                                          gpointer state)
+{
+       SoupNegotiateConnectionState *conn = state;
+
+       if (have_gssapi)
+               soup_gssapi_syms.client_cleanup (conn);
+       g_free (conn->response_header);
+}
+
+
+static gboolean
+soup_auth_negotiate_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
+                                      const char *header, gpointer state)
+{
+       SoupAuthNegotiatePrivate *priv =
+               SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth);
+       SoupNegotiateConnectionState *conn = state;
+       GError *err = NULL;
+
+       if (conn->state > SOUP_NEGOTIATE_RECEIVED_CHALLENGE) {
+               /* We already authenticated, but then got another 401.
+                * That means "permission denied", so don't try to
+                * authenticate again.
+                */
+               conn->state = SOUP_NEGOTIATE_FAILED;
+
+               /* Make sure we don't claim to be authenticated */
+               g_free (priv->username);
+               priv->username = NULL;
+
+               return FALSE;
+       }
+
+       /* Found negotiate header, start negotiate */
+       if (strcmp (header, "Negotiate") == 0) {
+               conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
+               if (soup_gss_build_response (conn, SOUP_AUTH (auth), &err))
+                       return TRUE;
+               else {
+                       /* FIXME: report further upward via
+                        * soup_message_get_error_message  */
+                       g_warning ("gssapi step failed: %s", err->message);
+               }
+       }
+       g_clear_error (&err);
+       return FALSE;
+}
+
+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 = '\0';
+
+       return g_slist_prepend (NULL, space);
+}
+
+static void
+soup_auth_negotiate_authenticate (SoupAuth *auth, const char *username,
+                                 const char *password)
+{
+       SoupAuthNegotiatePrivate *priv = SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth);
+
+       g_return_if_fail (username != NULL);
+       priv->username = g_strdup (username);
+}
+
+static gboolean
+soup_auth_negotiate_is_authenticated (SoupAuth *auth)
+{
+       return SOUP_AUTH_NEGOTIATE_GET_PRIVATE (auth)->username != NULL;
+}
+
+static gboolean
+soup_auth_negotiate_is_ready (SoupAuth *auth,
+                             SoupMessage *msg)
+{
+       SoupAuthNegotiate* negotiate = SOUP_AUTH_NEGOTIATE (auth);
+       return check_auth_trusted_uri (negotiate, msg);
+}
+
+
+static void
+check_server_response(SoupMessage *msg, gpointer state)
+{
+       gchar **parts, *p;
+       gint ret;
+       const char *auth_headers;
+       SoupNegotiateConnectionState *conn = state;
+       GError *err = NULL;
+
+       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");
+       parts = g_strsplit (auth_headers, " ", 0);
+       if (g_strv_length (parts) != 2) {
+               g_warning ("Failed to parse auth header %s", auth_headers);
+               conn->state = SOUP_NEGOTIATE_FAILED;
+               goto out;
+       }
+       if (g_ascii_strcasecmp (parts[0], "Negotiate")) {
+               g_warning ("Failed to parse auth header %s", auth_headers);
+               conn->state = SOUP_NEGOTIATE_FAILED;
+       }
+
+       p = parts[1];
+       ret = soup_gssapi_syms.client_step (conn, p, &err);
+
+       if (ret != AUTH_GSS_COMPLETE) {
+               g_warning ("%s", err->message);
+               conn->state = SOUP_NEGOTIATE_FAILED;
+       }
+ out:
+       g_clear_error (&err);
+       g_strfreev (parts);
+}
+
+
+static void
+remove_server_response_handler(SoupMessage *msg, gpointer state)
+{
+       g_signal_handlers_disconnect_by_func (msg,
+                                             G_CALLBACK (check_server_response),
+                                             state);
+}
+
+
+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;
+       }
+
+
+       g_signal_connect (msg,
+                         "finished",
+                         G_CALLBACK (remove_server_response_handler),
+                         conn);
+
+       /* Wait for the 2xx response to verify server response */
+       g_signal_connect (msg,
+                         "got_headers",
+                         G_CALLBACK (check_server_response),
+                         conn);
+
+       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;
+}
+
+static gboolean
+soup_gssapi_load (void)
+{
+       GModule *gssapi;
+       const char *modulename = PACKAGE "-gssapi-2.4." G_MODULE_SUFFIX;
+
+       if (!g_module_supported ())
+               return FALSE;
+
+       gssapi = g_module_open (modulename, G_MODULE_BIND_LOCAL);
+       if (!gssapi) {
+               g_warning ("Failed to load %s - negotiate support will "
+                          "be disabled.", modulename);
+               return FALSE;
+       }
+
+#define GSSAPI_BIND_SYMBOL(name) \
+       g_return_val_if_fail (g_module_symbol (gssapi, "soup_gss_" #name, (gpointer)&soup_gssapi_syms.name), 
FALSE)
+
+       GSSAPI_BIND_SYMBOL(client_step);
+       GSSAPI_BIND_SYMBOL(client_init);
+       GSSAPI_BIND_SYMBOL(client_cleanup);
+#undef GSSPI_BIND_SYMBOL
+       return TRUE;
+}
+
+static void
+soup_auth_negotiate_class_init (SoupAuthNegotiateClass *auth_negotiate_class)
+{
+       SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_negotiate_class);
+       SoupConnectionAuthClass *connauth_class =
+                       SOUP_CONNECTION_AUTH_CLASS (auth_negotiate_class);
+       GObjectClass *object_class = G_OBJECT_CLASS (auth_negotiate_class);
+
+       g_type_class_add_private (auth_negotiate_class, sizeof (SoupAuthNegotiatePrivate));
+
+       auth_class->scheme_name = "Negotiate";
+       auth_class->strength = 7;
+
+       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->is_ready = soup_auth_negotiate_is_ready;
+
+       connauth_class->create_connection_state = soup_auth_negotiate_create_connection_state;
+       connauth_class->free_connection_state = soup_auth_negotiate_free_connection_state;
+       connauth_class->update_connection = soup_auth_negotiate_update_connection;
+       connauth_class->get_connection_authorization = soup_auth_negotiate_get_connection_authorization;
+       connauth_class->is_connection_ready = soup_auth_negotiate_is_connection_ready;
+
+       object_class->finalize = soup_auth_negotiate_finalize;
+
+       trusted_uris = parse_trusted_uris ();
+       have_gssapi = soup_gssapi_load();
+}
+
+static gboolean
+soup_gss_build_response (SoupNegotiateConnectionState *conn, SoupAuth *auth, GError **err)
+{
+       if (!have_gssapi) {
+               if (err && *err == NULL) {
+                       g_set_error (err,
+                                    SOUP_HTTP_ERROR,
+                                    SOUP_STATUS_GSSAPI_FAILED,
+                                    "GSSAPI unavailable");
+               }
+               return FALSE;
+       }
+
+       if (!soup_gssapi_syms.client_init (conn, soup_auth_get_host (SOUP_AUTH (auth)), err))
+               return FALSE;
+
+       if (soup_gssapi_syms.client_step (conn, "", err) != AUTH_GSS_CONTINUE)
+               return FALSE;
+
+       return TRUE;
+}
+
+/* Parses a comma separated list of URIS from the environment. */
+static gchar**
+parse_trusted_uris(void)
+{
+       gchar **uris = NULL;
+       const gchar *env;
+
+       env = g_getenv ("SOUP_AUTH_TRUSTED_URIS");
+       if (env)
+               uris = g_strsplit (env, ",", -1);
+       return uris;
+}
+
+
+/* check if scheme://host:port from msg matches the trusted uri */
+static gboolean
+match_base_uri (SoupMessage *msg, const gchar *trusted)
+{
+       SoupURI *uri;
+       gboolean ret = FALSE;
+
+       /* params of the trusted uri */
+       gchar **trusted_parts = NULL;
+       gchar **trusted_host_port = NULL;
+       const gchar *trusted_host = NULL;
+       gint trusted_host_len;
+
+       /* params of the msg's uri */
+       const gchar  *host = NULL;
+       gint port;
+       gint host_len;
+
+       uri = soup_message_get_uri (msg);
+       /* split trusted uri into scheme and host/port */
+       if (strstr (trusted, "://")) {
+               trusted_parts = g_strsplit (trusted, "://", -1);
+
+               /* The scheme has to match exactly */
+               if (g_ascii_strcasecmp (trusted_parts[0],
+                                  soup_uri_get_scheme (uri))) {
+                       goto out;
+               }
+               if (strlen (trusted_parts[1]) == 0) {
+                       /* scheme only, we're done */
+                       ret = TRUE;
+                       goto out;
+               } else
+                       trusted_host = trusted_parts[1];
+       } else {
+               trusted_host = trusted;
+       }
+
+       trusted_host_port = g_strsplit (trusted_host, ":", 2);
+       /* If we got a port in the trusted uri it has to match exactly */
+       if (g_strv_length (trusted_host_port) > 1) {
+               port = g_ascii_strtoll (trusted_host_port[1], NULL, 10);
+               if (port != soup_uri_get_port (uri)) {
+                       goto out;
+               }
+       }
+
+       trusted_host = trusted_host_port[0];
+       host = soup_uri_get_host (uri);
+       if (g_str_has_suffix (host, trusted_host)) {
+           /* if the msg host ends with host from the trusted uri, then make
+            * sure it is either an exact match, or prefixed with a dot. We
+            * don't want "foobar.com" to match "bar.com"
+            */
+               if (g_ascii_strcasecmp (host, trusted_host) == 0) {
+                       ret = TRUE;
+                       goto out;
+               } else {
+                       /* we don't want example.com to match fooexample.com
+                         */
+                       trusted_host_len = strlen (trusted_host);
+                       host_len = strlen (host);
+                       if (host[host_len - trusted_host_len - 1] == '.') {
+                               ret = TRUE;
+                       }
+               }
+       }
+out:
+       g_strfreev (trusted_parts);
+       g_strfreev (trusted_host_port);
+       return ret;
+}
+
+
+static gboolean
+check_auth_trusted_uri (SoupAuthNegotiate *negotiate, SoupMessage *msg)
+{
+       SoupAuthNegotiatePrivate *priv =
+               SOUP_AUTH_NEGOTIATE_GET_PRIVATE (negotiate);
+       int i;
+
+       g_return_val_if_fail (negotiate != NULL, FALSE);
+       g_return_val_if_fail (priv != NULL, FALSE);
+       g_return_val_if_fail (msg != NULL, FALSE);
+
+       if (!trusted_uris) {
+               return FALSE;
+       }
+
+       for (i=0; i < g_strv_length (trusted_uris); i++) {
+               if (match_base_uri (msg, trusted_uris[i]))
+                       return TRUE;
+       }
+       return FALSE;
+}
diff --git a/libsoup/soup-auth-negotiate.h b/libsoup/soup-auth-negotiate.h
new file mode 100644
index 0000000..dbdfc3c
--- /dev/null
+++ b/libsoup/soup-auth-negotiate.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2010 Guido Guenther <agx sigxcpu org>
+ */
+
+#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.h b/libsoup/soup-auth.h
index 824857e..8db2ccc 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -99,6 +99,8 @@ GType soup_auth_basic_get_type  (void);
 GType soup_auth_digest_get_type (void);
 #define SOUP_TYPE_AUTH_NTLM   (soup_auth_ntlm_get_type ())
 GType soup_auth_ntlm_get_type   (void);
+#define SOUP_TYPE_AUTH_NEGOTIATE  (soup_auth_negotiate_get_type ())
+GType soup_auth_negotiate_get_type   (void);
 
 /* Deprecated SoupPasswordManager-related APIs: all are now no-ops */
 SOUP_AVAILABLE_IN_2_28
diff --git a/libsoup/soup-gssapi.c b/libsoup/soup-gssapi.c
new file mode 100644
index 0000000..78aaa77
--- /dev/null
+++ b/libsoup/soup-gssapi.c
@@ -0,0 +1,169 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-gssapi.c: GSSAPI related functions
+ *
+ * Copyright (C) 2013 Guido Guenther <agx sigxcpu org>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <soup-status.h>
+#include <soup-gssapi.h>
+
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_krb5.h>
+
+int
+soup_gss_client_init (SoupNegotiateConnectionState *conn, const char *host, GError **err);
+
+int
+soup_gss_client_step (SoupNegotiateConnectionState *conn, const char *host, GError **err);
+
+void
+soup_gss_client_cleanup (SoupNegotiateConnectionState *conn);
+
+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;
+       char *buf_maj = NULL, *buf_min = NULL;
+
+       do {
+               maj_stat = gss_display_status (&min_stat,
+                                              err_maj,
+                                              GSS_C_GSS_CODE,
+                                              GSS_C_NO_OID,
+                                              &msg_ctx,
+                                              &status);
+               if (GSS_ERROR (maj_stat))
+                       break;
+
+               buf_maj = g_strdup ((char*) 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 ((char*) status.value);
+                       gss_release_buffer (&min_stat, &status);
+               }
+
+               if (err && *err == NULL) {
+                       g_set_error (err,
+                                    SOUP_HTTP_ERROR,
+                                    SOUP_STATUS_GSSAPI_FAILED,
+                                    "%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);
+}
+
+G_MODULE_EXPORT int
+soup_gss_client_init (SoupNegotiateConnectionState *conn, const char *host, GError **err)
+{
+       OM_uint32 maj_stat, min_stat;
+       char *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 = (char *)service;
+
+       maj_stat = gss_import_name (&min_stat,
+                                   &token,
+                                   (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
+                                   &conn->server_name);
+
+       if (GSS_ERROR (maj_stat)) {
+               soup_gss_error (maj_stat, min_stat, err);
+               ret = FALSE;
+               goto out;
+       }
+
+       ret = TRUE;
+out:
+       g_free (h);
+       g_free (service);
+       return ret;
+}
+
+G_MODULE_EXPORT int
+soup_gss_client_step (SoupNegotiateConnectionState *conn, const char *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;
+       int 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_C_NO_OID,
+                                        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) {
+               char *response = g_base64_encode ((const unsigned char *)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;
+}
+
+
+G_MODULE_EXPORT void
+soup_gss_client_cleanup (SoupNegotiateConnectionState *conn)
+{
+       OM_uint32 min_stat;
+
+       gss_release_name (&min_stat, &conn->server_name);
+}
+
diff --git a/libsoup/soup-gssapi.h b/libsoup/soup-gssapi.h
new file mode 100644
index 0000000..05a2b48
--- /dev/null
+++ b/libsoup/soup-gssapi.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-negotiate.c: HTTP Negotiate Authentication helper
+ *
+ * Copyright (C) 2013 Guido Guenther <agx sigxcpu org>
+ */
+
+#ifndef SOUP_GSSAPI_H
+#define SOUP_GSSAPI_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_GSSAPI
+# include <gssapi/gssapi.h>
+#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 intial negotiate header */
+       SOUP_NEGOTIATE_SENT_RESPONSE,      /* sent response to server */
+       SOUP_NEGOTIATE_FAILED
+} SoupNegotiateState;
+
+typedef struct {
+       SoupNegotiateState state;
+
+#ifdef HAVE_GSSAPI
+       gss_ctx_id_t context;
+       gss_name_t   server_name;
+#endif
+
+       char *response_header;
+} SoupNegotiateConnectionState;
+
+#endif /* SOUP_GSSAPI_H */
diff --git a/libsoup/soup-status.h b/libsoup/soup-status.h
index fb4147c..70a9864 100644
--- a/libsoup/soup-status.h
+++ b/libsoup/soup-status.h
@@ -34,6 +34,7 @@ typedef enum {
        SOUP_STATUS_TRY_AGAIN,
        SOUP_STATUS_TOO_MANY_REDIRECTS,
        SOUP_STATUS_TLS_FAILED,
+       SOUP_STATUS_GSSAPI_FAILED,
 
        /* HTTP Status Codes */
        SOUP_STATUS_CONTINUE                        = 100,


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