From 6d6f9ca1d978817af2f2780dafcceba281256e11 Mon Sep 17 00:00:00 2001
From: Steve Holme <steve_holme@hotmail.com>
Date: Sun, 13 Mar 2016 18:51:46 +0000
Subject: [PATCH] vauth: Moved the Negotiate authentication code to the new
 vauth directory

Part 2 of 2 - Moved the GSS-API based Negotiate authentication code.
---
 lib/Makefile.inc                   |   3 +-
 lib/Makefile.vc6                   |   1 +
 lib/http_negotiate.c               | 157 +++---------------
 lib/vauth/spnego_gssapi.c          | 257 +++++++++++++++++++++++++++++
 lib/vauth/spnego_sspi.c            |   1 +
 lib/vauth/vauth.h                  |   6 +-
 packages/Symbian/group/libcurl.mmp |   3 +-
 7 files changed, 292 insertions(+), 136 deletions(-)
 create mode 100644 lib/vauth/spnego_gssapi.c

diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 67918f2d0d..b9bb7149fd 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -23,8 +23,7 @@
 LIB_VAUTH_CFILES = vauth/vauth.c vauth/cleartext.c vauth/cram.c         \
   vauth/digest.c vauth/digest_sspi.c vauth/krb5_gssapi.c                \
   vauth/krb5_sspi.c vauth/ntlm.c vauth/ntlm_sspi.c vauth/oauth2.c       \
-  vauth/spnego_sspi.c
-  
+  vauth/spnego_gssapi.c vauth/spnego_sspi.c
 
 LIB_VAUTH_HFILES = vauth/vauth.h vauth/digest.h vauth/ntlm.h
 
diff --git a/lib/Makefile.vc6 b/lib/Makefile.vc6
index b916a21766..b87350a7f2 100644
--- a/lib/Makefile.vc6
+++ b/lib/Makefile.vc6
@@ -627,6 +627,7 @@ X_OBJS= \
 	$(DIROBJ)\ntlm.obj \
 	$(DIROBJ)\ntlm_sspi.obj \
 	$(DIROBJ)\oauth2.obj \
+	$(DIROBJ)\spnego_gssapi.obj \
 	$(DIROBJ)\spnego_sspi.obj \
 	$(DIROBJ)\vtls.obj \
 	$(DIROBJ)\openssl.obj \
diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c
index 3f52bbcf35..587ea2a1cd 100644
--- a/lib/http_negotiate.c
+++ b/lib/http_negotiate.c
@@ -26,12 +26,9 @@
 
 #include "urldata.h"
 #include "sendf.h"
-#include "curl_gssapi.h"
 #include "rawstr.h"
-#include "curl_base64.h"
 #include "http_negotiate.h"
 #include "vauth/vauth.h"
-#include "url.h"
 #include "curl_printf.h"
 
 /* The last #include files should be: */
@@ -42,136 +39,51 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy,
                               const char *header)
 {
   struct SessionHandle *data = conn->data;
-  struct negotiatedata *neg_ctx = proxy?&data->state.proxyneg:
-    &data->state.negotiate;
-  OM_uint32 major_status, minor_status, discard_st;
-  gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER;
-  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
-  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
-  size_t len;
-  size_t rawlen = 0;
-  CURLcode result;
-
-  if(neg_ctx->context && neg_ctx->status == GSS_S_COMPLETE) {
-    /* We finished successfully our part of authentication, but server
-     * rejected it (since we're again here). Exit with an error since we
-     * can't invent anything better */
-    Curl_cleanup_negotiate(data);
-    return CURLE_LOGIN_DENIED;
-  }
-
-  if(!neg_ctx->server_name) {
-    /* Generate our SPN */
-    char *spn = Curl_auth_build_gssapi_spn(
-      proxy ? data->set.str[STRING_PROXY_SERVICE_NAME] :
-      data->set.str[STRING_SERVICE_NAME],
-      proxy ? conn->proxy.name : conn->host.name);
-    if(!spn)
-      return CURLE_OUT_OF_MEMORY;
 
-    /* Populate the SPN structure */
-    spn_token.value = spn;
-    spn_token.length = strlen(spn);
+  /* Point to the service and host */
+  const char *service;
+  const char *host;
 
-    /* Import the SPN */
-    major_status = gss_import_name(&minor_status, &spn_token,
-                                   GSS_C_NT_HOSTBASED_SERVICE,
-                                   &neg_ctx->server_name);
-    if(GSS_ERROR(major_status)) {
-      Curl_gss_log_error(data, minor_status, "gss_import_name() failed: ");
+  /* Point to the correct struct with this */
+  struct negotiatedata *neg_ctx;
 
-      free(spn);
-
-      return CURLE_OUT_OF_MEMORY;
-    }
-
-    free(spn);
+  if(proxy) {
+    service = data->set.str[STRING_PROXY_SERVICE_NAME];
+    host = conn->host.name;
+    neg_ctx = &data->state.proxyneg;
+  }
+  else {
+    service = data->set.str[STRING_SERVICE_NAME];
+    host = conn->proxy.name;
+    neg_ctx = &data->state.negotiate;
   }
 
+  /* Obtain the input token, if any */
   header += strlen("Negotiate");
   while(*header && ISSPACE(*header))
     header++;
 
-  len = strlen(header);
-  if(len > 0) {
-    result = Curl_base64_decode(header, (unsigned char **)&input_token.value,
-                                &rawlen);
-    if(result)
-      return result;
-
-    if(!rawlen) {
-      infof(data, "Negotiate handshake failure (empty challenge message)\n");
-
-      return CURLE_BAD_CONTENT_ENCODING;
-    }
-
-    input_token.length = rawlen;
-
-    DEBUGASSERT(input_token.value != NULL);
-  }
-
-  major_status = Curl_gss_init_sec_context(data,
-                                           &minor_status,
-                                           &neg_ctx->context,
-                                           neg_ctx->server_name,
-                                           &Curl_spnego_mech_oid,
-                                           GSS_C_NO_CHANNEL_BINDINGS,
-                                           &input_token,
-                                           &output_token,
-                                           TRUE,
-                                           NULL);
-  Curl_safefree(input_token.value);
-
-  neg_ctx->status = major_status;
-  if(GSS_ERROR(major_status)) {
-    if(output_token.value)
-      gss_release_buffer(&discard_st, &output_token);
-    Curl_gss_log_error(conn->data, minor_status,
-                       "gss_init_sec_context() failed: ");
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  if(!output_token.value || !output_token.length) {
-    if(output_token.value)
-      gss_release_buffer(&discard_st, &output_token);
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  neg_ctx->output_token = output_token;
-
-  return CURLE_OK;
+  /* Initilise the security context and decode our challenge */
+  return Curl_auth_decode_spnego_message(data, NULL, NULL, service, host,
+                                         header, neg_ctx);
 }
 
 CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy)
 {
   struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg:
     &conn->data->state.negotiate;
-  char *encoded = NULL;
+  char *base64 = NULL;
   size_t len = 0;
   char *userp;
   CURLcode result;
-  OM_uint32 discard_st;
-
-  result = Curl_base64_encode(conn->data,
-                              neg_ctx->output_token.value,
-                              neg_ctx->output_token.length,
-                              &encoded, &len);
-  if(result) {
-    gss_release_buffer(&discard_st, &neg_ctx->output_token);
-    neg_ctx->output_token.value = NULL;
-    neg_ctx->output_token.length = 0;
-    return result;
-  }
 
-  if(!encoded || !len) {
-    gss_release_buffer(&discard_st, &neg_ctx->output_token);
-    neg_ctx->output_token.value = NULL;
-    neg_ctx->output_token.length = 0;
-    return CURLE_REMOTE_ACCESS_DENIED;
-  }
+  result = Curl_auth_create_spnego_message(conn->data, neg_ctx, &base64, &len);
+  if(result)
+    return result;
 
   userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "",
-                  encoded);
+                  base64);
+
   if(proxy) {
     Curl_safefree(conn->allocptr.proxyuserpwd);
     conn->allocptr.proxyuserpwd = userp;
@@ -181,30 +93,15 @@ CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy)
     conn->allocptr.userpwd = userp;
   }
 
-  free(encoded);
+  free(base64);
 
   return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK;
 }
 
-static void cleanup(struct negotiatedata *neg_ctx)
-{
-  OM_uint32 minor_status;
-  if(neg_ctx->context != GSS_C_NO_CONTEXT)
-    gss_delete_sec_context(&minor_status, &neg_ctx->context, GSS_C_NO_BUFFER);
-
-  if(neg_ctx->output_token.value)
-    gss_release_buffer(&minor_status, &neg_ctx->output_token);
-
-  if(neg_ctx->server_name != GSS_C_NO_NAME)
-    gss_release_name(&minor_status, &neg_ctx->server_name);
-
-  memset(neg_ctx, 0, sizeof(*neg_ctx));
-}
-
 void Curl_cleanup_negotiate(struct SessionHandle *data)
 {
-  cleanup(&data->state.negotiate);
-  cleanup(&data->state.proxyneg);
+  Curl_auth_spnego_cleanup(&data->state.negotiate);
+  Curl_auth_spnego_cleanup(&data->state.proxyneg);
 }
 
 #endif /* HAVE_GSSAPI && !CURL_DISABLE_HTTP && USE_SPNEGO */
diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c
new file mode 100644
index 0000000000..9aa96dfdb9
--- /dev/null
+++ b/lib/vauth/spnego_gssapi.c
@@ -0,0 +1,257 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * RFC4178 Simple and Protected GSS-API Negotiation Mechanism
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(HAVE_GSSAPI) && defined(USE_SPNEGO)
+
+#include <curl/curl.h>
+
+#include "vauth/vauth.h"
+#include "urldata.h"
+#include "curl_base64.h"
+#include "curl_gssapi.h"
+#include "warnless.h"
+#include "curl_multibyte.h"
+#include "sendf.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/*
+ * Curl_auth_decode_spnego_message()
+ *
+ * This is used to decode an already encoded SPNEGO (Negotiate) challenge
+ * message.
+ *
+ * Parameters:
+ *
+ * data        [in]     - The session handle.
+ * userp       [in]     - The user name in the format User or Domain\User.
+ * passdwp     [in]     - The user's password.
+ * service     [in]     - The service type such as www, smtp, pop or imap.
+ * hostname    [in]     - The host name.
+ * chlg64      [in]     - The optional base64 encoded challenge message.
+ * nego        [in/out] - The Negotiate data struct being used and modified.
+ *
+ * Returns CURLE_OK on success.
+ */
+CURLcode Curl_auth_decode_spnego_message(struct SessionHandle *data,
+                                         const char *user,
+                                         const char *password,
+                                         const char *service,
+                                         const char *host,
+                                         const char *chlg64,
+                                         struct negotiatedata *nego)
+{
+  CURLcode result = CURLE_OK;
+  size_t chlglen = 0;
+  unsigned char *chlg = NULL;
+  OM_uint32 major_status;
+  OM_uint32 minor_status;
+  OM_uint32 unused_status;
+  gss_buffer_desc spn_token = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+
+  (void) user;
+  (void) password;
+
+  if(nego->context && nego->status == GSS_S_COMPLETE) {
+    /* We finished successfully our part of authentication, but server
+     * rejected it (since we're again here). Exit with an error since we
+     * can't invent anything better */
+    Curl_auth_spnego_cleanup(nego);
+    return CURLE_LOGIN_DENIED;
+  }
+
+  /* Generate our SPN */
+  if(!nego->server_name) {
+    char *spn = Curl_auth_build_gssapi_spn(service, host);
+    if(!spn)
+      return CURLE_OUT_OF_MEMORY;
+
+    /* Populate the SPN structure */
+    spn_token.value = spn;
+    spn_token.length = strlen(spn);
+
+    /* Import the SPN */
+    major_status = gss_import_name(&minor_status, &spn_token,
+                                   GSS_C_NT_HOSTBASED_SERVICE,
+                                   &nego->server_name);
+    if(GSS_ERROR(major_status)) {
+      Curl_gss_log_error(data, minor_status, "gss_import_name() failed: ");
+
+      free(spn);
+
+      return CURLE_OUT_OF_MEMORY;
+    }
+
+    free(spn);
+  }
+
+  if(chlg64 && strlen(chlg64)) {
+    /* Decode the base-64 encoded challenge message */
+    if(*chlg64 != '=') {
+      result = Curl_base64_decode(chlg64, &chlg, &chlglen);
+      if(result)
+        return result;
+    }
+
+    /* Ensure we have a valid challenge message */
+    if(!chlg) {
+      infof(data, "SPNEGO handshake failure (empty challenge message)\n");
+
+      return CURLE_BAD_CONTENT_ENCODING;
+    }
+
+    /* Setup the challenge "input" security buffer */
+    input_token.value = chlg;
+    input_token.length = chlglen;
+  }
+
+  /* Generate our challenge-response message */
+  major_status = Curl_gss_init_sec_context(data,
+                                           &minor_status,
+                                           &nego->context,
+                                           nego->server_name,
+                                           &Curl_spnego_mech_oid,
+                                           GSS_C_NO_CHANNEL_BINDINGS,
+                                           &input_token,
+                                           &output_token,
+                                           TRUE,
+                                           NULL);
+  Curl_safefree(input_token.value);
+
+  nego->status = major_status;
+  if(GSS_ERROR(major_status)) {
+    if(output_token.value)
+      gss_release_buffer(&unused_status, &output_token);
+
+    Curl_gss_log_error(data, minor_status,
+                       "gss_init_sec_context() failed: ");
+
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  if(!output_token.value || !output_token.length) {
+    if(output_token.value)
+      gss_release_buffer(&unused_status, &output_token);
+
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  nego->output_token = output_token;
+
+  return CURLE_OK;
+}
+
+/*
+ * Curl_auth_create_spnego_message()
+ *
+ * This is used to generate an already encoded SPNEGO (Negotiate) response
+ * message ready for sending to the recipient.
+ *
+ * Parameters:
+ *
+ * data        [in]     - The session handle.
+ * nego        [in/out] - The Negotiate data struct being used and modified.
+ * outptr      [in/out] - The address where a pointer to newly allocated memory
+ *                        holding the result will be stored upon completion.
+ * outlen      [out]    - The length of the output message.
+ *
+ * Returns CURLE_OK on success.
+ */
+CURLcode Curl_auth_create_spnego_message(struct SessionHandle *data,
+                                         struct negotiatedata *nego,
+                                         char **outptr, size_t *outlen)
+{
+  CURLcode result;
+  OM_uint32 minor_status;
+
+  /* Base64 encode the already generated response */
+  result = Curl_base64_encode(data,
+                              nego->output_token.value,
+                              nego->output_token.length,
+                              outptr, outlen);
+
+  if(result) {
+    gss_release_buffer(&minor_status, &nego->output_token);
+    nego->output_token.value = NULL;
+    nego->output_token.length = 0;
+
+    return result;
+  }
+
+  if(!*outptr || !*outlen) {
+    gss_release_buffer(&minor_status, &nego->output_token);
+    nego->output_token.value = NULL;
+    nego->output_token.length = 0;
+
+    return CURLE_REMOTE_ACCESS_DENIED;
+  }
+
+  return CURLE_OK;
+}
+
+/*
+ * Curl_auth_spnego_cleanup()
+ *
+ * This is used to clean up the SPNEGO (Negotiate) specific data.
+ *
+ * Parameters:
+ *
+ * nego     [in/out] - The Negotiate data struct being cleaned up.
+ *
+ */
+void Curl_auth_spnego_cleanup(struct negotiatedata* nego)
+{
+  OM_uint32 minor_status;
+
+  /* Free our security context */
+  if(nego->context != GSS_C_NO_CONTEXT) {
+    gss_delete_sec_context(&minor_status, &nego->context, GSS_C_NO_BUFFER);
+    nego->context = GSS_C_NO_CONTEXT;
+  }
+
+  /* Free the output token */
+  if(nego->output_token.value) {
+    gss_release_buffer(&minor_status, &nego->output_token);
+    nego->output_token.value = NULL;
+    nego->output_token.length = 0;
+
+  }
+
+  /* Free the SPN */
+  if(nego->server_name != GSS_C_NO_NAME) {
+    gss_release_name(&minor_status, &nego->server_name);
+    nego->server_name = GSS_C_NO_NAME;
+  }
+
+  /* Reset any variables */
+  nego->status = 0;
+}
+
+#endif /* HAVE_GSSAPI && USE_SPNEGO */
diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c
index 8bfc61522b..b60994c2d6 100644
--- a/lib/vauth/spnego_sspi.c
+++ b/lib/vauth/spnego_sspi.c
@@ -291,6 +291,7 @@ void Curl_auth_spnego_cleanup(struct negotiatedata* nego)
   Curl_safefree(nego->output_token);
 
   /* Reset any variables */
+  nego->status = 0;
   nego->token_max = 0;
 }
 
diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h
index 8a0c677658..161cb14b70 100644
--- a/lib/vauth/vauth.h
+++ b/lib/vauth/vauth.h
@@ -38,7 +38,7 @@ struct ntlmdata;
 struct kerberos5data;
 #endif
 
-#if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO)
+#if (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) && defined(USE_SPNEGO)
 struct negotiatedata;
 #endif
 
@@ -165,7 +165,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct SessionHandle *data,
 void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5);
 #endif /* USE_KERBEROS5 */
 
-#if defined(USE_WINDOWS_SSPI) && defined(USE_SPNEGO)
+#if (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)) && defined(USE_SPNEGO)
 /* This is used to decode a base64 encoded SPNEGO (Negotiate) challenge
    message */
 CURLcode Curl_auth_decode_spnego_message(struct SessionHandle *data,
@@ -185,6 +185,6 @@ CURLcode Curl_auth_create_spnego_message(struct SessionHandle *data,
 /* This is used to clean up the SPNEGO specifiec data */
 void Curl_auth_spnego_cleanup(struct negotiatedata* nego);
 
-#endif /* USE_WINDOWS_SSPI && USE_SPNEGO */
+#endif /* (HAVE_GSSAPI || USE_WINDOWS_SSPI) && USE_SPNEGO */
 
 #endif /* HEADER_CURL_VAUTH_H */
diff --git a/packages/Symbian/group/libcurl.mmp b/packages/Symbian/group/libcurl.mmp
index aae1111617..ec8e8d3552 100644
--- a/packages/Symbian/group/libcurl.mmp
+++ b/packages/Symbian/group/libcurl.mmp
@@ -42,7 +42,8 @@ SOURCE \
   curl_sasl_sspi.c smb.c curl_endian.c curl_des.c                      \
   vauth/vauth.c vauth/cleartext.c vauth/cram.c vauth/digest.c          \
   vauth/digest_sspi.c vauth/krb5_gssapi.c vauth/krb5_sspi.c            \
-  vauth/ntlm.c vauth/ntlm_sspi.c vauth/oauth2.c vauth/spnego_sspi.c
+  vauth/ntlm.c vauth/ntlm_sspi.c vauth/oauth2.c vauth/spnego_gssapi.c  \
+  vauth/spnego_sspi.c
 
 USERINCLUDE   ../../../lib ../../../include/curl
 #ifdef ENABLE_SSL
-- 
GitLab