summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-07-31 18:43:20 -0400
committerEdward Thomson <ethomson@edwardthomson.com>2014-08-15 11:12:42 -0400
commit315cb38e1e32e77037e82c6cbdc383c151041112 (patch)
treeef6f113d3df5acfe713a1c0b26b2e3234c7c8870
parente003f83a5840d6e9966f1830768771bcd205ba52 (diff)
downloadlibgit2-315cb38e1e32e77037e82c6cbdc383c151041112.tar.gz
Add GSSAPI support for SPNEGO/Kerberos auth over HTTP
-rw-r--r--CMakeLists.txt11
-rw-r--r--cmake/Modules/FindGSSAPI.cmake324
-rw-r--r--src/transports/http.c317
3 files changed, 622 insertions, 30 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6f1a97edb..54b0c8af1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,7 @@ OPTION( ANDROID "Build for android NDK" OFF )
OPTION( USE_ICONV "Link with and use iconv library" OFF )
OPTION( USE_SSH "Link with libssh to enable SSH support" ON )
+OPTION( USE_GSSAPI "Link with libgssapi for SPNEGO auth" ON )
OPTION( VALGRIND "Configure build for valgrind" OFF )
IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
@@ -208,6 +209,14 @@ IF (LIBSSH2_FOUND)
SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
ENDIF()
+# Optional external dependency: libgssapi
+IF (USE_GSSAPI)
+ FIND_PACKAGE(GSSAPI)
+ENDIF()
+IF (GSSAPI_FOUND)
+ ADD_DEFINITIONS(-DGIT_GSSAPI)
+ENDIF()
+
# Optional external dependency: iconv
IF (USE_ICONV)
FIND_PACKAGE(Iconv)
@@ -387,6 +396,7 @@ ENDIF()
ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC})
TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES})
TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${GSSAPI_LIBRARIES})
TARGET_LINK_LIBRARIES(git2 ${ICONV_LIBRARIES})
TARGET_OS_LIBRARIES(git2)
@@ -453,6 +463,7 @@ IF (BUILD_CLAR)
TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES})
TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${GSSAPI_LIBRARIES})
TARGET_LINK_LIBRARIES(libgit2_clar ${ICONV_LIBRARIES})
TARGET_OS_LIBRARIES(libgit2_clar)
MSVC_SPLIT_SOURCES(libgit2_clar)
diff --git a/cmake/Modules/FindGSSAPI.cmake b/cmake/Modules/FindGSSAPI.cmake
new file mode 100644
index 000000000..8520d35df
--- /dev/null
+++ b/cmake/Modules/FindGSSAPI.cmake
@@ -0,0 +1,324 @@
+# - Try to find GSSAPI
+# Once done this will define
+#
+# KRB5_CONFIG - Path to krb5-config
+# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI
+#
+# Read-Only variables:
+# GSSAPI_FLAVOR_MIT - set to TURE if MIT Kerberos has been found
+# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found
+# GSSAPI_FOUND - system has GSSAPI
+# GSSAPI_INCLUDE_DIR - the GSSAPI include directory
+# GSSAPI_LIBRARIES - Link these to use GSSAPI
+# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI
+#
+#=============================================================================
+# Copyright (c) 2013 Andreas Schneider <asn@cryptomilk.org>
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+#
+
+find_path(GSSAPI_ROOT_DIR
+ NAMES
+ include/gssapi.h
+ include/gssapi/gssapi.h
+ HINTS
+ ${_GSSAPI_ROOT_HINTS}
+ PATHS
+ ${_GSSAPI_ROOT_PATHS}
+)
+mark_as_advanced(GSSAPI_ROOT_DIR)
+
+if (UNIX)
+ find_program(KRB5_CONFIG
+ NAMES
+ krb5-config
+ PATHS
+ ${GSSAPI_ROOT_DIR}/bin
+ /opt/local/bin)
+ mark_as_advanced(KRB5_CONFIG)
+
+ if (KRB5_CONFIG)
+ # Check if we have MIT KRB5
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --vendor
+ RESULT_VARIABLE
+ _GSSAPI_VENDOR_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_VENDOR_STRING)
+
+ if (_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*")
+ set(GSSAPI_FLAVOR_MIT TRUE)
+ else()
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --libs gssapi
+ RESULT_VARIABLE
+ _GSSAPI_LIBS_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_LIBS_STRING)
+
+ if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*")
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ endif()
+ endif()
+
+ # Get the include dir
+ execute_process(
+ COMMAND
+ ${KRB5_CONFIG} --cflags gssapi
+ RESULT_VARIABLE
+ _GSSAPI_INCLUDE_RESULT
+ OUTPUT_VARIABLE
+ _GSSAPI_INCLUDE_STRING)
+ string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}")
+ string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}")
+ endif()
+
+ if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL)
+ # Check for HEIMDAL
+ find_package(PkgConfig)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(_GSSAPI heimdal-gssapi)
+ endif (PKG_CONFIG_FOUND)
+
+ if (_GSSAPI_FOUND)
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ else()
+ find_path(_GSSAPI_ROKEN
+ NAMES
+ roken.h
+ PATHS
+ ${GSSAPI_ROOT_DIR}/include
+ ${_GSSAPI_INCLUDEDIR})
+ if (_GSSAPI_ROKEN)
+ set(GSSAPI_FLAVOR_HEIMDAL TRUE)
+ endif()
+ endif ()
+ endif()
+endif (UNIX)
+
+find_path(GSSAPI_INCLUDE_DIR
+ NAMES
+ gssapi.h
+ gssapi/gssapi.h
+ PATHS
+ ${GSSAPI_ROOT_DIR}/include
+ ${_GSSAPI_INCLUDEDIR}
+)
+
+if (GSSAPI_FLAVOR_MIT)
+ find_library(GSSAPI_LIBRARY
+ NAMES
+ gssapi_krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(KRB5_LIBRARY
+ NAMES
+ krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(K5CRYPTO_LIBRARY
+ NAMES
+ k5crypto
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(COM_ERR_LIBRARY
+ NAMES
+ com_err
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ if (GSSAPI_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${GSSAPI_LIBRARY}
+ )
+ endif (GSSAPI_LIBRARY)
+
+ if (KRB5_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${KRB5_LIBRARY}
+ )
+ endif (KRB5_LIBRARY)
+
+ if (K5CRYPTO_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${K5CRYPTO_LIBRARY}
+ )
+ endif (K5CRYPTO_LIBRARY)
+
+ if (COM_ERR_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${COM_ERR_LIBRARY}
+ )
+ endif (COM_ERR_LIBRARY)
+endif (GSSAPI_FLAVOR_MIT)
+
+if (GSSAPI_FLAVOR_HEIMDAL)
+ find_library(GSSAPI_LIBRARY
+ NAMES
+ gssapi
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(KRB5_LIBRARY
+ NAMES
+ krb5
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HCRYPTO_LIBRARY
+ NAMES
+ hcrypto
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(COM_ERR_LIBRARY
+ NAMES
+ com_err
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HEIMNTLM_LIBRARY
+ NAMES
+ heimntlm
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(HX509_LIBRARY
+ NAMES
+ hx509
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(ASN1_LIBRARY
+ NAMES
+ asn1
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(WIND_LIBRARY
+ NAMES
+ wind
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ find_library(ROKEN_LIBRARY
+ NAMES
+ roken
+ PATHS
+ ${GSSAPI_ROOT_DIR}/lib
+ ${_GSSAPI_LIBDIR}
+ )
+
+ if (GSSAPI_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${GSSAPI_LIBRARY}
+ )
+ endif (GSSAPI_LIBRARY)
+
+ if (KRB5_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${KRB5_LIBRARY}
+ )
+ endif (KRB5_LIBRARY)
+
+ if (HCRYPTO_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HCRYPTO_LIBRARY}
+ )
+ endif (HCRYPTO_LIBRARY)
+
+ if (COM_ERR_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${COM_ERR_LIBRARY}
+ )
+ endif (COM_ERR_LIBRARY)
+
+ if (HEIMNTLM_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HEIMNTLM_LIBRARY}
+ )
+ endif (HEIMNTLM_LIBRARY)
+
+ if (HX509_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${HX509_LIBRARY}
+ )
+ endif (HX509_LIBRARY)
+
+ if (ASN1_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${ASN1_LIBRARY}
+ )
+ endif (ASN1_LIBRARY)
+
+ if (WIND_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${WIND_LIBRARY}
+ )
+ endif (WIND_LIBRARY)
+
+ if (ROKEN_LIBRARY)
+ set(GSSAPI_LIBRARIES
+ ${GSSAPI_LIBRARIES}
+ ${WIND_LIBRARY}
+ )
+ endif (ROKEN_LIBRARY)
+endif (GSSAPI_FLAVOR_HEIMDAL)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR)
+
+if (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
+ set(GSSAPI_FOUND TRUE)
+endif (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
+
+# show the GSSAPI_INCLUDE_DIRS and GSSAPI_LIBRARIES variables only in the advanced view
+mark_as_advanced(GSSAPI_INCLUDE_DIRS GSSAPI_LIBRARIES)
diff --git a/src/transports/http.c b/src/transports/http.c
index ffa293ec0..c43f6c548 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -12,6 +12,11 @@
#include "netops.h"
#include "smart.h"
+#ifdef GIT_GSSAPI
+# include <gssapi.h>
+# include <krb5.h>
+#endif
+
static const char *upload_pack_service = "upload-pack";
static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
static const char *upload_pack_service_url = "/git-upload-pack";
@@ -21,6 +26,17 @@ static const char *receive_pack_service_url = "/git-receive-pack";
static const char *get_verb = "GET";
static const char *post_verb = "POST";
static const char *basic_authtype = "Basic";
+static const char *negotiate_authtype = "Negotiate";
+
+#ifdef GIT_GSSAPI
+static gss_OID_desc negotiate_oid_spnego =
+ { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
+static gss_OID_desc negotiate_oid_krb5 =
+ { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+
+static gss_OID negotiate_oids[] =
+ { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
+#endif
#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
@@ -37,6 +53,7 @@ enum last_cb {
typedef enum {
GIT_HTTP_AUTH_BASIC = 1,
+ GIT_HTTP_AUTH_NEGOTIATE = 2,
} http_authmechanism_t;
typedef struct {
@@ -61,6 +78,7 @@ typedef struct {
git_cred *cred;
git_cred *url_cred;
http_authmechanism_t auth_mechanism;
+ char *auth_challenge;
bool connected;
/* Parser structures */
@@ -76,6 +94,14 @@ typedef struct {
enum last_cb last_cb;
int parse_error;
unsigned parse_finished : 1;
+
+#ifdef GIT_GSSAPI
+ unsigned negotiate_configured : 1,
+ negotiate_complete : 1;
+ git_buf negotiate_target;
+ gss_ctx_id_t negotiate_context;
+ gss_OID negotiate_oid;
+#endif
} http_subtransport;
typedef struct {
@@ -88,12 +114,17 @@ typedef struct {
size_t *bytes_read;
} parser_context;
-static int apply_basic_credential(git_buf *buf, git_cred *cred)
+static int apply_basic_credential(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
{
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
git_buf raw = GIT_BUF_INIT;
int error = -1;
+ GIT_UNUSED(transport);
+
git_buf_printf(&raw, "%s:%s", c->username, c->password);
if (git_buf_oom(&raw) ||
@@ -112,6 +143,188 @@ on_error:
return error;
}
+#ifdef GIT_GSSAPI
+
+static void negotiate_err_set(
+ OM_uint32 status_major,
+ OM_uint32 status_minor,
+ const char *message)
+{
+ gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
+ OM_uint32 status_display, context = 0;
+
+ if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
+ giterr_set(GITERR_NET, "%s: %.*s (%d.%d)",
+ message, (int)buffer.length, (const char *)buffer.value,
+ status_major, status_minor);
+ gss_release_buffer(&status_minor, &buffer);
+ } else {
+ giterr_set(GITERR_NET, "%s: unknown negotiate error (%d.%d)",
+ message, status_major, status_minor);
+ }
+}
+
+static int negotiate_configure(http_subtransport *transport)
+{
+ OM_uint32 status_major, status_minor;
+ gss_OID item, *oid;
+ gss_OID_set mechanism_list;
+ size_t i;
+
+ /* Query supported mechanisms looking for SPNEGO) */
+ if (GSS_ERROR(status_major =
+ gss_indicate_mechs(&status_minor, &mechanism_list))) {
+ negotiate_err_set(status_major, status_minor,
+ "could not query mechanisms");
+ return -1;
+ }
+
+ if (mechanism_list) {
+ for (oid = negotiate_oids; *oid; oid++) {
+ for (i = 0; i < mechanism_list->count; i++) {
+ item = &mechanism_list->elements[i];
+
+ if (item->length == (*oid)->length &&
+ memcmp(item->elements, (*oid)->elements, item->length) == 0) {
+ transport->negotiate_oid = *oid;
+ break;
+ }
+
+ }
+
+ if (transport->negotiate_oid)
+ break;
+ }
+ }
+
+ gss_release_oid_set(&status_minor, &mechanism_list);
+
+ if (!transport->negotiate_oid) {
+ giterr_set(GITERR_NET, "Negotiate authentication is not supported");
+ return -1;
+ }
+
+ git_buf_puts(&transport->negotiate_target, "HTTP@");
+ git_buf_puts(&transport->negotiate_target, transport->connection_data.host);
+
+ if (git_buf_oom(&transport->negotiate_target))
+ return -1;
+
+ transport->negotiate_context = GSS_C_NO_CONTEXT;
+ transport->negotiate_configured = 1;
+ return 0;
+}
+
+static int negotiate_next_token(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
+{
+ OM_uint32 status_major, status_minor;
+ gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
+ input_token = GSS_C_EMPTY_BUFFER,
+ output_token = GSS_C_EMPTY_BUFFER;
+ gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
+ git_buf input_buf = GIT_BUF_INIT;
+ gss_name_t server = NULL;
+ gss_OID mech;
+ size_t challenge_len;
+ int error = 0;
+
+ GIT_UNUSED(cred);
+
+ target_buffer.value = (void *)transport->negotiate_target.ptr;
+ target_buffer.length = transport->negotiate_target.size;
+
+ status_major = gss_import_name(&status_minor, &target_buffer,
+ GSS_C_NT_HOSTBASED_SERVICE, &server);
+
+ if (GSS_ERROR(status_major)) {
+ negotiate_err_set(status_major, status_minor,
+ "Could not parse principal");
+ error = -1;
+ goto done;
+ }
+
+ challenge_len = transport->auth_challenge ?
+ strlen(transport->auth_challenge) : 0;
+ assert(challenge_len >= 9);
+
+ if (challenge_len > 9) {
+ if (git_buf_decode_base64(&input_buf,
+ transport->auth_challenge + 10, challenge_len - 10) < 0) {
+ giterr_set(GITERR_NET, "Invalid negotiate challenge from server");
+ error = -1;
+ goto done;
+ }
+
+ input_token.value = input_buf.ptr;
+ input_token.length = input_buf.size;
+ input_token_ptr = &input_token;
+ } else if (transport->negotiate_context != GSS_C_NO_CONTEXT) {
+ giterr_set(GITERR_NET, "Could not restart authentication");
+ error = -1;
+ goto done;
+ }
+
+ mech = &negotiate_oid_spnego;
+
+ if (GSS_ERROR(status_major = gss_init_sec_context(
+ &status_minor,
+ GSS_C_NO_CREDENTIAL,
+ &transport->negotiate_context,
+ server,
+ mech,
+ GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ input_token_ptr,
+ NULL,
+ &output_token,
+ NULL,
+ NULL))) {
+ negotiate_err_set(status_major, status_minor, "Negotiate failure");
+ error = -1;
+ goto done;
+ }
+
+ /* This message merely told us auth was complete; we do not respond. */
+ if (status_major == GSS_S_COMPLETE) {
+ transport->negotiate_complete = 1;
+ goto done;
+ }
+
+ git_buf_puts(buf, "Authorization: Negotiate ");
+ git_buf_encode_base64(buf, output_token.value, output_token.length);
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ error = -1;
+
+done:
+ gss_release_name(&status_minor, &server);
+ gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
+ git_buf_free(&input_buf);
+ return error;
+}
+
+static int apply_negotiate_credential(
+ git_buf *buf,
+ http_subtransport *transport,
+ git_cred *cred)
+{
+ if (!transport->negotiate_configured && negotiate_configure(transport) < 0)
+ return -1;
+
+ if (transport->negotiate_complete)
+ return 0;
+
+ return negotiate_next_token(buf, transport, cred);
+}
+
+#endif /* GIT_GSSAPI */
+
static int gen_request(
git_buf *buf,
http_stream *s,
@@ -137,17 +350,26 @@ static int gen_request(
git_buf_puts(buf, "Accept: */*\r\n");
/* Apply credentials to the request */
+#ifdef GIT_GSSAPI
+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
+ (t->auth_mechanism & GIT_HTTP_AUTH_NEGOTIATE)) {
+ if (apply_negotiate_credential(buf, t, t->cred) < 0)
+ return -1;
+ } else
+#endif
+
if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
- apply_basic_credential(buf, t->cred) < 0)
- return -1;
+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC) {
+ if (apply_basic_credential(buf, t, t->cred) < 0)
+ return -1;
+ }
/* Use url-parsed basic auth if username and password are both provided */
if (!t->cred && t->connection_data.user && t->connection_data.pass) {
if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred,
t->connection_data.user, t->connection_data.pass) < 0)
return -1;
- if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
+ if (apply_basic_credential(buf, t, t->url_cred) < 0) return -1;
}
git_buf_puts(buf, "\r\n");
@@ -158,19 +380,32 @@ static int gen_request(
return 0;
}
-static int parse_unauthorized_response(
+static int parse_authenticate_response(
git_vector *www_authenticate,
int *allowed_types,
- http_authmechanism_t *auth_mechanism)
+ http_authmechanism_t *auth_mechanism,
+ char **auth_challenge)
{
unsigned i;
char *entry;
git_vector_foreach(www_authenticate, i, entry) {
- if (!strncmp(entry, basic_authtype, 5) &&
+ if (!strncmp(entry, negotiate_authtype, 9) &&
+ (entry[9] == '\0' || entry[9] == ' ')) {
+ *allowed_types |= GIT_CREDTYPE_DEFAULT;
+ *auth_mechanism = GIT_HTTP_AUTH_NEGOTIATE;
+
+ *auth_challenge = git__strdup(entry);
+ GITERR_CHECK_ALLOC(*auth_challenge);
+ }
+
+ else if (!strncmp(entry, basic_authtype, 5) &&
(entry[5] == '\0' || entry[5] == ' ')) {
*allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
*auth_mechanism = GIT_HTTP_AUTH_BASIC;
+
+ *auth_challenge = git__strdup(entry);
+ GITERR_CHECK_ALLOC(*auth_challenge);
}
}
@@ -248,7 +483,7 @@ static int on_headers_complete(http_parser *parser)
http_subtransport *t = ctx->t;
http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- int error = 0, no_callback = 0;
+ int error = 0, no_callback = 0, allowed_auth_types = 0;
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
@@ -256,26 +491,31 @@ static int on_headers_complete(http_parser *parser)
if (on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
- /* Check for an authentication failure. */
+ /* Capture authentication headers which may be a 401 (authentication
+ * is not complete) or a 200 (simply informing us that auth *is*
+ * complete.)
+ */
+ git__free(t->auth_challenge);
+ t->auth_challenge = NULL;
- if (parser->status_code == 401 &&
- get_verb == s->verb) {
+ if (parse_authenticate_response(&t->www_authenticate,
+ &allowed_auth_types,
+ &t->auth_mechanism,
+ &t->auth_challenge) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 && get_verb == s->verb) {
if (!t->owner->cred_acquire_cb) {
no_callback = 1;
} else {
- int allowed_types = 0;
-
- if (parse_unauthorized_response(&t->www_authenticate,
- &allowed_types, &t->auth_mechanism) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- if (allowed_types &&
- (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+ if (allowed_auth_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_auth_types))) {
error = t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
t->connection_data.user,
- allowed_types,
+ allowed_auth_types,
t->owner->cred_acquire_payload);
if (error == GIT_PASSTHROUGH) {
@@ -511,10 +751,8 @@ replay:
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -613,10 +851,8 @@ static int http_stream_write_chunked(
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, 0) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
git_buf_free(&request);
@@ -688,10 +924,8 @@ static int http_stream_write_single(
clear_parser_state(t);
- if (gen_request(&request, s, len) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
+ if (gen_request(&request, s, len) < 0)
return -1;
- }
if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
goto on_error;
@@ -855,6 +1089,24 @@ static int http_action(
return -1;
}
+static void clear_negotiate_state(http_subtransport *t)
+{
+#ifdef GIT_GSSAPI
+ OM_uint32 status_minor;
+
+ if (t->negotiate_context != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(&status_minor, &t->negotiate_context, GSS_C_NO_BUFFER);
+ t->negotiate_context = GSS_C_NO_CONTEXT;
+ }
+
+ git_buf_free(&t->negotiate_target);
+
+ t->negotiate_configured = 0;
+ t->negotiate_complete = 0;
+ t->negotiate_oid = NULL;
+#endif
+}
+
static int http_close(git_smart_subtransport *subtransport)
{
http_subtransport *t = (http_subtransport *) subtransport;
@@ -876,6 +1128,11 @@ static int http_close(git_smart_subtransport *subtransport)
t->url_cred = NULL;
}
+ git__free(t->auth_challenge);
+ t->auth_challenge = NULL;
+
+ clear_negotiate_state(t);
+
gitno_connection_data_free_ptrs(&t->connection_data);
return 0;