summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2023-03-27 11:58:14 +0100
committerGitHub <noreply@github.com>2023-03-27 11:58:14 +0100
commit9bfad74dc029f163f296d09df56a42b97a38f4c1 (patch)
tree5b8b1f1e77c16a375b5f3d8dddc366643862846f
parentc058aa87dce4c67a3b86b3349beebd64b7bedcd3 (diff)
parentf15c8ac71a916bf186cd5ff81f07ca85eef82afb (diff)
downloadlibgit2-9bfad74dc029f163f296d09df56a42b97a38f4c1.tar.gz
Merge pull request #6533 from libgit2/ethomson/schannel-2
Introduce Schannel and SSPI for Windows
-rw-r--r--.github/workflows/main.yml12
-rw-r--r--.github/workflows/nightly.yml36
-rw-r--r--CMakeLists.txt6
-rw-r--r--cmake/SelectGSSAPI.cmake6
-rw-r--r--cmake/SelectHTTPSBackend.cmake23
-rw-r--r--cmake/SelectHashes.cmake4
-rw-r--r--cmake/SelectWinHTTP.cmake17
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/libgit2/libgit2.c2
-rw-r--r--src/libgit2/streams/schannel.c715
-rw-r--r--src/libgit2/streams/schannel.h28
-rw-r--r--src/libgit2/streams/socket.c88
-rw-r--r--src/libgit2/streams/socket.h2
-rw-r--r--src/libgit2/streams/tls.c5
-rw-r--r--src/libgit2/transports/auth_gssapi.c (renamed from src/libgit2/transports/auth_negotiate.c)64
-rw-r--r--src/libgit2/transports/auth_negotiate.h2
-rw-r--r--src/libgit2/transports/auth_ntlm.h2
-rw-r--r--src/libgit2/transports/auth_ntlmclient.c (renamed from src/libgit2/transports/auth_ntlm.c)24
-rw-r--r--src/libgit2/transports/auth_sspi.c341
-rw-r--r--src/libgit2/transports/winhttp.c24
-rw-r--r--src/util/fs_path.c2
-rw-r--r--src/util/git2_features.h.in1
-rw-r--r--src/util/util.c2
-rw-r--r--src/util/win32/error.c2
-rw-r--r--src/util/win32/path_w32.c16
-rw-r--r--src/util/win32/posix_w32.c2
-rw-r--r--src/util/win32/utf-conv.c148
-rw-r--r--src/util/win32/utf-conv.h95
-rw-r--r--src/util/win32/w32_util.c2
-rw-r--r--tests/clar/clar_libgit2.c4
-rw-r--r--tests/libgit2/online/clone.c13
-rw-r--r--tests/libgit2/stream/registration.c6
-rw-r--r--tests/util/link.c6
33 files changed, 1453 insertions, 248 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 0eedab87a..cdcea1644 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -130,19 +130,19 @@ jobs:
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
setup-script: osx
- - name: "Windows (amd64, Visual Studio)"
+ - name: "Windows (amd64, Visual Studio, Schannel)"
id: windows-amd64-vs
os: windows-2019
setup-script: win32
env:
ARCH: amd64
CMAKE_GENERATOR: Visual Studio 16 2019
- CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
+ CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin
BUILD_TEMP: D:\Temp
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (x86, Visual Studio)"
+ - name: "Windows (x86, Visual Studio, WinHTTP)"
id: windows-x86-vs
os: windows-2019
setup-script: win32
@@ -154,7 +154,7 @@ jobs:
BUILD_TEMP: D:\Temp
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (amd64, mingw)"
+ - name: "Windows (amd64, mingw, WinHTTP)"
id: windows-amd64-mingw
os: windows-2019
setup-script: mingw
@@ -166,14 +166,14 @@ jobs:
BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (x86, mingw)"
+ - name: "Windows (x86, mingw, Schannel)"
id: windows-x86-mingw
os: windows-2019
setup-script: mingw
env:
ARCH: x86
CMAKE_GENERATOR: MinGW Makefiles
- CMAKE_OPTIONS: -DDEPRECATE_HARD=ON
+ CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
BUILD_TEMP: D:\Temp
BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
SKIP_SSH_TESTS: true
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 5a0b7d12b..f461530ae 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -162,32 +162,39 @@ jobs:
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
setup-script: osx
- - name: "Windows (amd64, Visual Studio)"
+ - name: "Windows (amd64, Visual Studio, WinHTTP)"
os: windows-2019
env:
ARCH: amd64
CMAKE_GENERATOR: Visual Studio 16 2019
- CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON
+ CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (no mmap)"
+ - name: "Windows (x86, Visual Studio, WinHTTP)"
+ os: windows-2019
+ env:
+ ARCH: x86
+ CMAKE_GENERATOR: Visual Studio 16 2019
+ CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON
+ SKIP_SSH_TESTS: true
+ SKIP_NEGOTIATE_TESTS: true
+ - name: "Windows (amd64, Visual Studio, Schannel)"
os: windows-2019
env:
ARCH: amd64
CMAKE_GENERATOR: Visual Studio 16 2019
- CFLAGS: -DNO_MMAP
- CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON
+ CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (x86, Visual Studio)"
+ - name: "Windows (x86, Visual Studio, Schannel)"
os: windows-2019
env:
ARCH: x86
CMAKE_GENERATOR: Visual Studio 16 2019
- CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON
+ CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_BUNDLED_ZLIB=ON
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (amd64, mingw)"
+ - name: "Windows (amd64, mingw, WinHTTP)"
os: windows-2019
setup-script: mingw
env:
@@ -198,17 +205,26 @@ jobs:
BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- - name: "Windows (x86, mingw)"
+ - name: "Windows (x86, mingw, Schannel)"
os: windows-2019
setup-script: mingw
env:
ARCH: x86
CMAKE_GENERATOR: MinGW Makefiles
- CMAKE_OPTIONS: -DDEPRECATE_HARD=ON
+ CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel
BUILD_TEMP: D:\Temp
BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
+ - name: "Windows (no mmap)"
+ os: windows-2019
+ env:
+ ARCH: amd64
+ CMAKE_GENERATOR: Visual Studio 16 2019
+ CFLAGS: -DNO_MMAP
+ CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON
+ SKIP_SSH_TESTS: true
+ SKIP_NEGOTIATE_TESTS: true
- name: "Linux (Bionic, GCC, dynamically-loaded OpenSSL)"
container:
name: bionic
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94627f54d..fa5167538 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -82,12 +82,6 @@ if(MSVC)
option(WIN32_LEAKCHECK "Enable leak reporting via crtdbg" OFF)
endif()
-if(WIN32)
- # By default, libgit2 is built with WinHTTP. To use the built-in
- # HTTP transport, invoke CMake with the "-DUSE_WINHTTP=OFF" argument.
- option(USE_WINHTTP "Use Win32 WinHTTP routines" ON)
-endif()
-
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif()
diff --git a/cmake/SelectGSSAPI.cmake b/cmake/SelectGSSAPI.cmake
index 24e2d68b9..5bde11697 100644
--- a/cmake/SelectGSSAPI.cmake
+++ b/cmake/SelectGSSAPI.cmake
@@ -29,7 +29,7 @@ if(USE_GSSAPI)
list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSFRAMEWORK_LIBRARIES})
set(GIT_GSSFRAMEWORK 1)
- add_feature_info(SPNEGO GIT_GSSFRAMEWORK "SPNEGO authentication support (${USE_GSSAPI})")
+ add_feature_info(GSSAPI GIT_GSSFRAMEWORK "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})")
elseif(USE_GSSAPI STREQUAL "gssapi")
if(NOT GSSAPI_FOUND)
message(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found")
@@ -38,11 +38,11 @@ if(USE_GSSAPI)
list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSAPI_LIBRARIES})
set(GIT_GSSAPI 1)
- add_feature_info(SPNEGO GIT_GSSAPI "SPNEGO authentication support (${USE_GSSAPI})")
+ add_feature_info(GSSAPI GIT_GSSAPI "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})")
else()
message(FATAL_ERROR "Asked for backend ${USE_GSSAPI} but it wasn't found")
endif()
else()
set(GIT_GSSAPI 0)
- add_feature_info(SPNEGO NO "SPNEGO authentication support")
+ add_feature_info(GSSAPI NO "GSSAPI support for SPNEGO authentication")
endif()
diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake
index 20221bf9f..d14941643 100644
--- a/cmake/SelectHTTPSBackend.cmake
+++ b/cmake/SelectHTTPSBackend.cmake
@@ -19,7 +19,7 @@ if(USE_HTTPS)
message(STATUS "Security framework is too old, falling back to OpenSSL")
set(USE_HTTPS "OpenSSL")
endif()
- elseif(USE_WINHTTP)
+ elseif(WIN32)
set(USE_HTTPS "WinHTTP")
elseif(OPENSSL_FOUND)
set(USE_HTTPS "OpenSSL")
@@ -106,8 +106,27 @@ if(USE_HTTPS)
# https://github.com/ARMmbed/mbedtls/issues/228
# For now, pass its link flags as our own
list(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES})
+ elseif(USE_HTTPS STREQUAL "Schannel")
+ set(GIT_SCHANNEL 1)
+
+ list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32")
+ list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32")
elseif(USE_HTTPS STREQUAL "WinHTTP")
- # WinHTTP setup was handled in the WinHTTP-specific block above
+ set(GIT_WINHTTP 1)
+
+ # Since MinGW does not come with headers or an import library for winhttp,
+ # we have to include a private header and generate our own import library
+ if(MINGW)
+ add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp")
+ list(APPEND LIBGIT2_SYSTEM_LIBS winhttp)
+ list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp")
+ else()
+ list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp")
+ list(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
+ endif()
+
+ list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32")
+ list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32")
elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic")
set(GIT_OPENSSL 1)
set(GIT_OPENSSL_DYNAMIC 1)
diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake
index faf9e2ea3..5c007e587 100644
--- a/cmake/SelectHashes.cmake
+++ b/cmake/SelectHashes.cmake
@@ -13,6 +13,8 @@ if(USE_SHA1 STREQUAL ON)
elseif(USE_SHA1 STREQUAL "HTTPS")
if(USE_HTTPS STREQUAL "SecureTransport")
set(USE_SHA1 "CommonCrypto")
+ elseif(USE_HTTPS STREQUAL "Schannel")
+ set(USE_SHA1 "Win32")
elseif(USE_HTTPS STREQUAL "WinHTTP")
set(USE_SHA1 "Win32")
elseif(USE_HTTPS)
@@ -51,6 +53,8 @@ endif()
if(USE_SHA256 STREQUAL "HTTPS")
if(USE_HTTPS STREQUAL "SecureTransport")
set(USE_SHA256 "CommonCrypto")
+ elseif(USE_HTTPS STREQUAL "Schannel")
+ set(USE_SHA256 "Win32")
elseif(USE_HTTPS STREQUAL "WinHTTP")
set(USE_SHA256 "Win32")
elseif(USE_HTTPS)
diff --git a/cmake/SelectWinHTTP.cmake b/cmake/SelectWinHTTP.cmake
deleted file mode 100644
index 96e0bdbae..000000000
--- a/cmake/SelectWinHTTP.cmake
+++ /dev/null
@@ -1,17 +0,0 @@
-if(WIN32 AND USE_WINHTTP)
- set(GIT_WINHTTP 1)
-
- # Since MinGW does not come with headers or an import library for winhttp,
- # we have to include a private header and generate our own import library
- if(MINGW)
- add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp")
- list(APPEND LIBGIT2_SYSTEM_LIBS winhttp)
- list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp")
- else()
- list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp")
- list(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
- endif()
-
- list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32")
- list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32")
-endif()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e108b2e79..8fd22bc11 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -42,7 +42,6 @@ include(SelectHashes)
include(SelectHTTPParser)
include(SelectRegex)
include(SelectSSH)
-include(SelectWinHTTP)
include(SelectZlib)
#
diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c
index f225122e5..5d796b1f7 100644
--- a/src/libgit2/libgit2.c
+++ b/src/libgit2/libgit2.c
@@ -30,6 +30,7 @@
#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
+#include "streams/socket.h"
#include "transports/smart.h"
#include "transports/http.h"
#include "transports/ssh.h"
@@ -78,6 +79,7 @@ int git_libgit2_init(void)
git_merge_driver_global_init,
git_transport_ssh_global_init,
git_stream_registry_global_init,
+ git_socket_stream_global_init,
git_openssl_stream_global_init,
git_mbedtls_stream_global_init,
git_mwindow_global_init,
diff --git a/src/libgit2/streams/schannel.c b/src/libgit2/streams/schannel.c
new file mode 100644
index 000000000..f09615819
--- /dev/null
+++ b/src/libgit2/streams/schannel.c
@@ -0,0 +1,715 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "streams/schannel.h"
+
+#ifdef GIT_SCHANNEL
+
+#define SECURITY_WIN32
+
+#include <security.h>
+#include <schannel.h>
+#include <sspi.h>
+
+#include "stream.h"
+#include "streams/socket.h"
+
+#ifndef SP_PROT_TLS1_2_CLIENT
+# define SP_PROT_TLS1_2_CLIENT 2048
+#endif
+
+#ifndef SP_PROT_TLS1_3_CLIENT
+# define SP_PROT_TLS1_3_CLIENT 8192
+#endif
+
+#ifndef SECBUFFER_ALERT
+# define SECBUFFER_ALERT 17
+#endif
+
+#define READ_BLOCKSIZE (16 * 1024)
+
+typedef enum {
+ STATE_NONE = 0,
+ STATE_CRED = 1,
+ STATE_CONTEXT = 2,
+ STATE_CERTIFICATE = 3
+} schannel_state;
+
+typedef struct {
+ git_stream parent;
+ git_stream *io;
+ int owned;
+ bool connected;
+ wchar_t *host_w;
+
+ schannel_state state;
+
+ CredHandle cred;
+ CtxtHandle context;
+ SecPkgContext_StreamSizes stream_sizes;
+
+ CERT_CONTEXT *certificate;
+ const CERT_CHAIN_CONTEXT *cert_chain;
+ git_cert_x509 x509;
+
+ git_str plaintext_in;
+ git_str ciphertext_in;
+} schannel_stream;
+
+static int connect_context(schannel_stream *st)
+{
+ SCHANNEL_CRED cred = { 0 };
+ SECURITY_STATUS status = SEC_E_INTERNAL_ERROR;
+ DWORD context_flags;
+ static size_t MAX_RETRIES = 1024;
+ size_t retries;
+ ssize_t read_len;
+ int error = 0;
+
+ if (st->owned && (error = git_stream_connect(st->io)) < 0)
+ return error;
+
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.dwFlags = SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE |
+ SCH_CRED_MANUAL_CRED_VALIDATION |
+ SCH_CRED_NO_DEFAULT_CREDS |
+ SCH_CRED_NO_SERVERNAME_CHECK;
+ cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT |
+ SP_PROT_TLS1_3_CLIENT;
+
+ if (AcquireCredentialsHandleW(NULL, SCHANNEL_NAME_W,
+ SECPKG_CRED_OUTBOUND, NULL, &cred, NULL,
+ NULL, &st->cred, NULL) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_OS, "could not acquire credentials handle");
+ return -1;
+ }
+
+ st->state = STATE_CRED;
+
+ context_flags = ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_STREAM;
+
+ for (retries = 0; retries < MAX_RETRIES; retries++) {
+ SecBuffer input_buf[] = {
+ { (unsigned long)st->ciphertext_in.size,
+ SECBUFFER_TOKEN,
+ st->ciphertext_in.size ? st->ciphertext_in.ptr : NULL },
+ { 0, SECBUFFER_EMPTY, NULL }
+ };
+ SecBuffer output_buf[] = { { 0, SECBUFFER_TOKEN, NULL },
+ { 0, SECBUFFER_ALERT, NULL } };
+
+ SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 2, input_buf };
+ SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 2, output_buf };
+
+ status = InitializeSecurityContextW(&st->cred,
+ retries ? &st->context : NULL, st->host_w,
+ context_flags, 0, 0, retries ? &input_buf_desc : NULL, 0,
+ retries ? NULL : &st->context, &output_buf_desc,
+ &context_flags, NULL);
+
+ if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) {
+ st->state = STATE_CONTEXT;
+
+ if (output_buf[0].cbBuffer > 0) {
+ error = git_stream__write_full(st->io,
+ output_buf[0].pvBuffer,
+ output_buf[0].cbBuffer, 0);
+
+ FreeContextBuffer(output_buf[0].pvBuffer);
+ }
+
+ /* handle any leftover, unprocessed data */
+ if (input_buf[1].BufferType == SECBUFFER_EXTRA) {
+ GIT_ASSERT(st->ciphertext_in.size > input_buf[1].cbBuffer);
+
+ git_str_consume_bytes(&st->ciphertext_in,
+ st->ciphertext_in.size - input_buf[1].cbBuffer);
+ } else {
+ git_str_clear(&st->ciphertext_in);
+ }
+
+ if (error < 0 || status == SEC_E_OK)
+ break;
+ } else if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ /* we need additional data from the client; */
+ if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) {
+ error = -1;
+ break;
+ }
+
+ if ((read_len = git_stream_read(st->io,
+ st->ciphertext_in.ptr + st->ciphertext_in.size,
+ (st->ciphertext_in.asize - st->ciphertext_in.size))) < 0) {
+ error = -1;
+ break;
+ }
+
+ GIT_ASSERT((size_t)read_len <=
+ st->ciphertext_in.asize - st->ciphertext_in.size);
+ st->ciphertext_in.size += read_len;
+ } else {
+ git_error_set(GIT_ERROR_OS,
+ "could not initialize security context");
+ error = -1;
+ break;
+ }
+
+ GIT_ASSERT(st->ciphertext_in.size < ULONG_MAX);
+ }
+
+ if (retries == MAX_RETRIES) {
+ git_error_set(GIT_ERROR_SSL,
+ "could not initialize security context: too many retries");
+ error = -1;
+ }
+
+ if (!error) {
+ if (QueryContextAttributesW(&st->context,
+ SECPKG_ATTR_STREAM_SIZES,
+ &st->stream_sizes) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_SSL,
+ "could not query stream sizes");
+ error = -1;
+ }
+ }
+
+ return error;
+}
+
+static int set_certificate_lookup_error(DWORD status)
+{
+ switch (status) {
+ case CERT_TRUST_IS_NOT_TIME_VALID:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate is expired or not yet valid");
+ break;
+ case CERT_TRUST_IS_REVOKED:
+ git_error_set(GIT_ERROR_SSL, "certificate is revoked");
+ break;
+ case CERT_TRUST_IS_NOT_SIGNATURE_VALID:
+ case CERT_TRUST_IS_NOT_VALID_FOR_USAGE:
+ case CERT_TRUST_INVALID_EXTENSION:
+ case CERT_TRUST_INVALID_POLICY_CONSTRAINTS:
+ case CERT_TRUST_INVALID_BASIC_CONSTRAINTS:
+ case CERT_TRUST_INVALID_NAME_CONSTRAINTS:
+ case CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT:
+ case CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT:
+ case CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT:
+ case CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT:
+ case CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY:
+ case CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT:
+ git_error_set(GIT_ERROR_SSL, "certificate is not valid");
+ break;
+ case CERT_TRUST_IS_UNTRUSTED_ROOT:
+ case CERT_TRUST_IS_CYCLIC:
+ case CERT_TRUST_IS_EXPLICIT_DISTRUST:
+ git_error_set(GIT_ERROR_SSL, "certificate is not trusted");
+ break;
+ case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate revocation status could not be verified");
+ break;
+ case CERT_TRUST_IS_OFFLINE_REVOCATION:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate revocation is offline or stale");
+ break;
+ case CERT_TRUST_HAS_WEAK_SIGNATURE:
+ git_error_set(GIT_ERROR_SSL, "certificate has a weak signature");
+ break;
+ default:
+ git_error_set(GIT_ERROR_SSL,
+ "unknown certificate lookup failure: %d", status);
+ return -1;
+ }
+
+ return GIT_ECERTIFICATE;
+}
+
+static int set_certificate_validation_error(DWORD status)
+{
+ switch (status) {
+ case TRUST_E_CERT_SIGNATURE:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate cannot be verified");
+ break;
+ case CRYPT_E_REVOKED:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate or signature has been revoked");
+ break;
+ case CERT_E_UNTRUSTEDROOT:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate root is not trusted");
+ break;
+ case CERT_E_UNTRUSTEDTESTROOT:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate root is a test certificate");
+ break;
+ case CERT_E_CHAINING:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate chain is invalid");
+ break;
+ case CERT_E_WRONG_USAGE:
+ case CERT_E_PURPOSE:
+ git_error_set(GIT_ERROR_SSL,
+ "the certificate is not valid for this usage");
+ break;
+ case CERT_E_EXPIRED:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate is expired or not yet valid");
+ break;
+ case CERT_E_INVALID_NAME:
+ case CERT_E_CN_NO_MATCH:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate is not valid for this hostname");
+ break;
+ case CERT_E_INVALID_POLICY:
+ case TRUST_E_BASIC_CONSTRAINTS:
+ case CERT_E_CRITICAL:
+ case CERT_E_VALIDITYPERIODNESTING:
+ git_error_set(GIT_ERROR_SSL, "certificate is not valid");
+ break;
+ case CRYPT_E_NO_REVOCATION_CHECK:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate revocation status could not be verified");
+ break;
+ case CRYPT_E_REVOCATION_OFFLINE:
+ git_error_set(GIT_ERROR_SSL,
+ "certificate revocation is offline or stale");
+ break;
+ case CERT_E_ROLE:
+ git_error_set(GIT_ERROR_SSL, "certificate authority is not valid");
+ break;
+ default:
+ git_error_set(GIT_ERROR_SSL,
+ "unknown certificate policy checking failure: %d",
+ status);
+ return -1;
+ }
+
+ return GIT_ECERTIFICATE;
+}
+
+static int check_certificate(schannel_stream* st)
+{
+ CERT_CHAIN_PARA cert_chain_parameters;
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_policy_parameters;
+ CERT_CHAIN_POLICY_PARA cert_policy_parameters =
+ { sizeof(CERT_CHAIN_POLICY_PARA), 0, &ssl_policy_parameters };
+ CERT_CHAIN_POLICY_STATUS cert_policy_status;
+
+ memset(&cert_chain_parameters, 0, sizeof(CERT_CHAIN_PARA));
+ cert_chain_parameters.cbSize = sizeof(CERT_CHAIN_PARA);
+
+ if (QueryContextAttributesW(&st->context,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ &st->certificate) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_OS,
+ "could not query remote certificate context");
+ return -1;
+ }
+
+ /* TODO: do we really want to do revokcation checking ? */
+ if (!CertGetCertificateChain(NULL, st->certificate, NULL,
+ st->certificate->hCertStore, &cert_chain_parameters,
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
+ NULL, &st->cert_chain)) {
+ git_error_set(GIT_ERROR_OS, "could not query remote certificate chain");
+ CertFreeCertificateContext(st->certificate);
+ return -1;
+ }
+
+ st->state = STATE_CERTIFICATE;
+
+ /* Set up the x509 certificate data for future callbacks */
+
+ st->x509.parent.cert_type = GIT_CERT_X509;
+ st->x509.data = st->certificate->pbCertEncoded;
+ st->x509.len = st->certificate->cbCertEncoded;
+
+ /* Handle initial certificate validation */
+
+ if (st->cert_chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR)
+ return set_certificate_lookup_error(st->cert_chain->TrustStatus.dwErrorStatus);
+
+ ssl_policy_parameters.cbSize = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA);
+ ssl_policy_parameters.dwAuthType = AUTHTYPE_SERVER;
+ ssl_policy_parameters.pwszServerName = st->host_w;
+
+ if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
+ st->cert_chain, &cert_policy_parameters,
+ &cert_policy_status)) {
+ git_error_set(GIT_ERROR_OS, "could not verify certificate chain policy");
+ return -1;
+ }
+
+ if (cert_policy_status.dwError != SEC_E_OK)
+ return set_certificate_validation_error(cert_policy_status.dwError);
+
+ return 0;
+}
+
+static int schannel_connect(git_stream *stream)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ int error;
+
+ GIT_ASSERT(st->state == STATE_NONE);
+
+ if ((error = connect_context(st)) < 0 ||
+ (error = check_certificate(st)) < 0)
+ return error;
+
+ st->connected = 1;
+ return 0;
+}
+
+static int schannel_certificate(git_cert **out, git_stream *stream)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+
+ *out = &st->x509.parent;
+ return 0;
+}
+
+static int schannel_set_proxy(
+ git_stream *stream,
+ const git_proxy_options *proxy_options)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ return git_stream_set_proxy(st->io, proxy_options);
+}
+
+static ssize_t schannel_write(
+ git_stream *stream,
+ const char *data,
+ size_t data_len,
+ int flags)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ SecBuffer encrypt_buf[3];
+ SecBufferDesc encrypt_buf_desc = { SECBUFFER_VERSION, 3, encrypt_buf };
+ git_str ciphertext_out = GIT_STR_INIT;
+ ssize_t total_len = 0;
+
+ GIT_UNUSED(flags);
+
+ if (data_len > SSIZE_MAX)
+ data_len = SSIZE_MAX;
+
+ git_str_init(&ciphertext_out,
+ st->stream_sizes.cbHeader +
+ st->stream_sizes.cbMaximumMessage +
+ st->stream_sizes.cbTrailer);
+
+ while (data_len > 0) {
+ size_t message_len = min(data_len, st->stream_sizes.cbMaximumMessage);
+ size_t ciphertext_len, ciphertext_written = 0;
+
+ encrypt_buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ encrypt_buf[0].cbBuffer = st->stream_sizes.cbHeader;
+ encrypt_buf[0].pvBuffer = ciphertext_out.ptr;
+
+ encrypt_buf[1].BufferType = SECBUFFER_DATA;
+ encrypt_buf[1].cbBuffer = (unsigned long)message_len;
+ encrypt_buf[1].pvBuffer =
+ ciphertext_out.ptr + st->stream_sizes.cbHeader;
+
+ encrypt_buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ encrypt_buf[2].cbBuffer = st->stream_sizes.cbTrailer;
+ encrypt_buf[2].pvBuffer =
+ ciphertext_out.ptr + st->stream_sizes.cbHeader +
+ message_len;
+
+ memcpy(ciphertext_out.ptr + st->stream_sizes.cbHeader, data, message_len);
+
+ if (EncryptMessage(&st->context, 0, &encrypt_buf_desc, 0) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_OS, "could not encrypt tls message");
+ total_len = -1;
+ goto done;
+ }
+
+ ciphertext_len = encrypt_buf[0].cbBuffer +
+ encrypt_buf[1].cbBuffer +
+ encrypt_buf[2].cbBuffer;
+
+ while (ciphertext_written < ciphertext_len) {
+ ssize_t chunk_len = git_stream_write(st->io,
+ ciphertext_out.ptr + ciphertext_written,
+ ciphertext_len - ciphertext_written, 0);
+
+ if (chunk_len < 0) {
+ total_len = -1;
+ goto done;
+ }
+
+ ciphertext_len -= chunk_len;
+ ciphertext_written += chunk_len;
+ }
+
+ total_len += message_len;
+
+ data += message_len;
+ data_len -= message_len;
+ }
+
+done:
+ git_str_dispose(&ciphertext_out);
+ return total_len;
+}
+
+static ssize_t schannel_read(git_stream *stream, void *_data, size_t data_len)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ char *data = (char *)_data;
+ SecBuffer decrypt_buf[4];
+ SecBufferDesc decrypt_buf_desc = { SECBUFFER_VERSION, 4, decrypt_buf };
+ SECURITY_STATUS status;
+ ssize_t chunk_len, total_len = 0;
+
+ if (data_len > SSIZE_MAX)
+ data_len = SSIZE_MAX;
+
+ /*
+ * Loop until we have some bytes to return - we may have decrypted
+ * bytes queued or ciphertext from the wire that we can decrypt and
+ * return. Return any queued bytes if they're available to avoid a
+ * network read, which may block. We may return less than the
+ * caller requested, and they can retry for an actual network
+ */
+ while ((size_t)total_len < data_len) {
+ if (st->plaintext_in.size > 0) {
+ size_t copy_len = min(st->plaintext_in.size, data_len);
+
+ memcpy(data, st->plaintext_in.ptr, copy_len);
+ git_str_consume_bytes(&st->plaintext_in, copy_len);
+
+ data += copy_len;
+ data_len -= copy_len;
+
+ total_len += copy_len;
+
+ continue;
+ }
+
+ if (st->ciphertext_in.size > 0) {
+ decrypt_buf[0].BufferType = SECBUFFER_DATA;
+ decrypt_buf[0].cbBuffer = (unsigned long)min(st->ciphertext_in.size, ULONG_MAX);
+ decrypt_buf[0].pvBuffer = st->ciphertext_in.ptr;
+
+ decrypt_buf[1].BufferType = SECBUFFER_EMPTY;
+ decrypt_buf[1].cbBuffer = 0;
+ decrypt_buf[1].pvBuffer = NULL;
+
+ decrypt_buf[2].BufferType = SECBUFFER_EMPTY;
+ decrypt_buf[2].cbBuffer = 0;
+ decrypt_buf[2].pvBuffer = NULL;
+
+ decrypt_buf[3].BufferType = SECBUFFER_EMPTY;
+ decrypt_buf[3].cbBuffer = 0;
+ decrypt_buf[3].pvBuffer = NULL;
+
+ status = DecryptMessage(&st->context, &decrypt_buf_desc, 0, NULL);
+
+ if (status == SEC_E_OK) {
+ GIT_ASSERT(decrypt_buf[0].BufferType == SECBUFFER_STREAM_HEADER);
+ GIT_ASSERT(decrypt_buf[1].BufferType == SECBUFFER_DATA);
+ GIT_ASSERT(decrypt_buf[2].BufferType == SECBUFFER_STREAM_TRAILER);
+
+ if (git_str_put(&st->plaintext_in, decrypt_buf[1].pvBuffer, decrypt_buf[1].cbBuffer) < 0) {
+ total_len = -1;
+ goto done;
+ }
+
+ if (decrypt_buf[3].BufferType == SECBUFFER_EXTRA) {
+ git_str_consume_bytes(&st->ciphertext_in, (st->ciphertext_in.size - decrypt_buf[3].cbBuffer));
+ } else {
+ git_str_clear(&st->ciphertext_in);
+ }
+
+ continue;
+ } else if (status == SEC_E_CONTEXT_EXPIRED) {
+ break;
+ } else if (status != SEC_E_INCOMPLETE_MESSAGE) {
+ git_error_set(GIT_ERROR_SSL, "could not decrypt tls message");
+ total_len = -1;
+ goto done;
+ }
+ }
+
+ if (total_len != 0)
+ break;
+
+ if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) {
+ total_len = -1;
+ goto done;
+ }
+
+ if ((chunk_len = git_stream_read(st->io, st->ciphertext_in.ptr + st->ciphertext_in.size, st->ciphertext_in.asize - st->ciphertext_in.size)) < 0) {
+ total_len = -1;
+ goto done;
+ }
+
+ st->ciphertext_in.size += chunk_len;
+ }
+
+done:
+ return total_len;
+}
+
+static int schannel_close(git_stream *stream)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+ int error = 0;
+
+ if (st->connected) {
+ SecBuffer shutdown_buf;
+ SecBufferDesc shutdown_buf_desc =
+ { SECBUFFER_VERSION, 1, &shutdown_buf };
+ DWORD shutdown_message = SCHANNEL_SHUTDOWN, shutdown_flags;
+
+ shutdown_buf.BufferType = SECBUFFER_TOKEN;
+ shutdown_buf.cbBuffer = sizeof(DWORD);
+ shutdown_buf.pvBuffer = &shutdown_message;
+
+ if (ApplyControlToken(&st->context, &shutdown_buf_desc) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_SSL, "could not shutdown stream");
+ error = -1;
+ }
+
+ shutdown_buf.BufferType = SECBUFFER_TOKEN;
+ shutdown_buf.cbBuffer = 0;
+ shutdown_buf.pvBuffer = NULL;
+
+ shutdown_flags = ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_STREAM;
+
+ if (InitializeSecurityContext(&st->cred, &st->context,
+ NULL, shutdown_flags, 0, 0,
+ &shutdown_buf_desc, 0, NULL,
+ &shutdown_buf_desc, &shutdown_flags,
+ NULL) == SEC_E_OK) {
+ if (shutdown_buf.cbBuffer > 0) {
+ if (git_stream__write_full(st->io,
+ shutdown_buf.pvBuffer,
+ shutdown_buf.cbBuffer, 0) < 0)
+ error = -1;
+
+ FreeContextBuffer(shutdown_buf.pvBuffer);
+ }
+ }
+ }
+
+ st->connected = false;
+
+ if (st->owned && git_stream_close(st->io) < 0)
+ error = -1;
+
+ return error;
+}
+
+static void schannel_free(git_stream *stream)
+{
+ schannel_stream *st = (schannel_stream *)stream;
+
+ if (st->state >= STATE_CERTIFICATE) {
+ CertFreeCertificateContext(st->certificate);
+ CertFreeCertificateChain(st->cert_chain);
+ }
+
+ if (st->state >= STATE_CONTEXT)
+ DeleteSecurityContext(&st->context);
+
+ if (st->state >= STATE_CRED)
+ FreeCredentialsHandle(&st->cred);
+
+ st->state = STATE_NONE;
+
+ git_str_dispose(&st->ciphertext_in);
+ git_str_dispose(&st->plaintext_in);
+
+ git__free(st->host_w);
+
+ if (st->owned)
+ git_stream_free(st->io);
+
+ git__free(st);
+}
+
+static int schannel_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host,
+ int owned)
+{
+ schannel_stream *st;
+
+ st = git__calloc(1, sizeof(schannel_stream));
+ GIT_ERROR_CHECK_ALLOC(st);
+
+ st->io = in;
+ st->owned = owned;
+
+ if (git_utf8_to_16_alloc(&st->host_w, host) < 0) {
+ git__free(st);
+ return -1;
+ }
+
+ st->parent.version = GIT_STREAM_VERSION;
+ st->parent.encrypted = 1;
+ st->parent.proxy_support = git_stream_supports_proxy(st->io);
+ st->parent.connect = schannel_connect;
+ st->parent.certificate = schannel_certificate;
+ st->parent.set_proxy = schannel_set_proxy;
+ st->parent.read = schannel_read;
+ st->parent.write = schannel_write;
+ st->parent.close = schannel_close;
+ st->parent.free = schannel_free;
+
+ *out = (git_stream *)st;
+ return 0;
+}
+
+extern int git_schannel_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port)
+{
+ git_stream *stream;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(host);
+ GIT_ASSERT_ARG(port);
+
+ if ((error = git_socket_stream_new(&stream, host, port)) < 0)
+ return error;
+
+ if ((error = schannel_stream_wrap(out, stream, host, 1)) < 0) {
+ git_stream_close(stream);
+ git_stream_free(stream);
+ }
+
+ return error;
+}
+
+extern int git_schannel_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host)
+{
+ return schannel_stream_wrap(out, in, host, 0);
+}
+
+#endif
diff --git a/src/libgit2/streams/schannel.h b/src/libgit2/streams/schannel.h
new file mode 100644
index 000000000..3584970d1
--- /dev/null
+++ b/src/libgit2/streams/schannel.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_steams_schannel_h__
+#define INCLUDE_steams_schannel_h__
+
+#include "common.h"
+
+#include "git2/sys/stream.h"
+
+#ifdef GIT_SCHANNEL
+
+extern int git_schannel_stream_new(
+ git_stream **out,
+ const char *host,
+ const char *port);
+
+extern int git_schannel_stream_wrap(
+ git_stream **out,
+ git_stream *in,
+ const char *host);
+
+#endif
+
+#endif
diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c
index 908e8c02f..8f23e746e 100644
--- a/src/libgit2/streams/socket.c
+++ b/src/libgit2/streams/socket.c
@@ -10,22 +10,23 @@
#include "posix.h"
#include "netops.h"
#include "registry.h"
+#include "runtime.h"
#include "stream.h"
#ifndef _WIN32
-# include <sys/types.h>
-# include <sys/socket.h>
-# include <sys/select.h>
-# include <sys/time.h>
-# include <netdb.h>
-# include <netinet/in.h>
-# include <arpa/inet.h>
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <sys/time.h>
+# include <netdb.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#else
-# include <winsock2.h>
-# include <ws2tcpip.h>
-# ifdef _MSC_VER
-# pragma comment(lib, "ws2_32")
-# endif
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
#endif
#ifdef GIT_WIN32
@@ -54,11 +55,8 @@ static int close_socket(GIT_SOCKET s)
return 0;
#ifdef GIT_WIN32
- if (SOCKET_ERROR == closesocket(s))
- return -1;
-
- if (0 != WSACleanup()) {
- git_error_set(GIT_ERROR_OS, "winsock cleanup failed");
+ if (closesocket(s) != 0) {
+ net_set_error("could not close socket");
return -1;
}
@@ -77,23 +75,6 @@ static int socket_connect(git_stream *stream)
GIT_SOCKET s = INVALID_SOCKET;
int ret;
-#ifdef GIT_WIN32
- /* on win32, the WSA context needs to be initialized
- * before any socket calls can be performed */
- WSADATA wsd;
-
- if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
- git_error_set(GIT_ERROR_OS, "winsock init failed");
- return -1;
- }
-
- if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
- WSACleanup();
- git_error_set(GIT_ERROR_OS, "winsock init failed");
- return -1;
- }
-#endif
-
memset(&hints, 0x0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
@@ -240,3 +221,42 @@ int git_socket_stream_new(
return init(out, host, port);
}
+
+#ifdef GIT_WIN32
+
+static void socket_stream_global_shutdown(void)
+{
+ WSACleanup();
+}
+
+int git_socket_stream_global_init(void)
+{
+ WORD winsock_version;
+ WSADATA wsa_data;
+
+ winsock_version = MAKEWORD(2, 2);
+
+ if (WSAStartup(winsock_version, &wsa_data) != 0) {
+ git_error_set(GIT_ERROR_OS, "could not initialize Windows Socket Library");
+ return -1;
+ }
+
+ if (LOBYTE(wsa_data.wVersion) != 2 ||
+ HIBYTE(wsa_data.wVersion) != 2) {
+ git_error_set(GIT_ERROR_SSL, "Windows Socket Library does not support Winsock 2.2");
+ return -1;
+ }
+
+ return git_runtime_shutdown_register(socket_stream_global_shutdown);
+}
+
+#else
+
+#include "stream.h"
+
+int git_socket_stream_global_init(void)
+{
+ return 0;
+}
+
+ #endif
diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h
index 3235f3167..300e70893 100644
--- a/src/libgit2/streams/socket.h
+++ b/src/libgit2/streams/socket.h
@@ -20,4 +20,6 @@ typedef struct {
extern int git_socket_stream_new(git_stream **out, const char *host, const char *port);
+extern int git_socket_stream_global_init(void);
+
#endif
diff --git a/src/libgit2/streams/tls.c b/src/libgit2/streams/tls.c
index e063a33f9..246ac9ca7 100644
--- a/src/libgit2/streams/tls.c
+++ b/src/libgit2/streams/tls.c
@@ -13,6 +13,7 @@
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "streams/stransport.h"
+#include "streams/schannel.h"
int git_tls_stream_new(git_stream **out, const char *host, const char *port)
{
@@ -33,6 +34,8 @@ int git_tls_stream_new(git_stream **out, const char *host, const char *port)
init = git_openssl_stream_new;
#elif defined(GIT_MBEDTLS)
init = git_mbedtls_stream_new;
+#elif defined(GIT_SCHANNEL)
+ init = git_schannel_stream_new;
#endif
} else {
return error;
@@ -63,6 +66,8 @@ int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host)
wrap = git_openssl_stream_wrap;
#elif defined(GIT_MBEDTLS)
wrap = git_mbedtls_stream_wrap;
+#elif defined(GIT_SCHANNEL)
+ wrap = git_schannel_stream_wrap;
#endif
}
diff --git a/src/libgit2/transports/auth_negotiate.c b/src/libgit2/transports/auth_gssapi.c
index 6380504be..500553841 100644
--- a/src/libgit2/transports/auth_negotiate.c
+++ b/src/libgit2/transports/auth_gssapi.c
@@ -20,13 +20,13 @@
#include <krb5.h>
#endif
-static gss_OID_desc negotiate_oid_spnego =
+static gss_OID_desc gssapi_oid_spnego =
{ 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
-static gss_OID_desc negotiate_oid_krb5 =
+static gss_OID_desc gssapi_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 };
+static gss_OID gssapi_oids[] =
+ { &gssapi_oid_spnego, &gssapi_oid_krb5, NULL };
typedef struct {
git_http_auth_context parent;
@@ -36,9 +36,9 @@ typedef struct {
char *challenge;
gss_ctx_id_t gss_context;
gss_OID oid;
-} http_auth_negotiate_context;
+} http_auth_gssapi_context;
-static void negotiate_err_set(
+static void gssapi_err_set(
OM_uint32 status_major,
OM_uint32 status_minor,
const char *message)
@@ -58,11 +58,11 @@ static void negotiate_err_set(
}
}
-static int negotiate_set_challenge(
+static int gssapi_set_challenge(
git_http_auth_context *c,
const char *challenge)
{
- http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
GIT_ASSERT_ARG(ctx);
GIT_ASSERT_ARG(challenge);
@@ -76,7 +76,7 @@ static int negotiate_set_challenge(
return 0;
}
-static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
+static void gssapi_context_dispose(http_auth_gssapi_context *ctx)
{
OM_uint32 status_minor;
@@ -92,12 +92,12 @@ static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
ctx->challenge = NULL;
}
-static int negotiate_next_token(
+static int gssapi_next_token(
git_str *buf,
git_http_auth_context *c,
git_credential *cred)
{
- http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
OM_uint32 status_major, status_minor;
gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
input_token = GSS_C_EMPTY_BUFFER,
@@ -126,7 +126,7 @@ static int negotiate_next_token(
GSS_C_NT_HOSTBASED_SERVICE, &server);
if (GSS_ERROR(status_major)) {
- negotiate_err_set(status_major, status_minor,
+ gssapi_err_set(status_major, status_minor,
"could not parse principal");
error = -1;
goto done;
@@ -152,10 +152,10 @@ static int negotiate_next_token(
input_token.length = input_buf.size;
input_token_ptr = &input_token;
} else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
- negotiate_context_dispose(ctx);
+ gssapi_context_dispose(ctx);
}
- mech = &negotiate_oid_spnego;
+ mech = &gssapi_oid_spnego;
status_major = gss_init_sec_context(
&status_minor,
@@ -173,14 +173,14 @@ static int negotiate_next_token(
NULL);
if (GSS_ERROR(status_major)) {
- negotiate_err_set(status_major, status_minor, "negotiate failure");
+ gssapi_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) {
- negotiate_context_dispose(ctx);
+ gssapi_context_dispose(ctx);
ctx->complete = 1;
goto done;
}
@@ -204,20 +204,20 @@ done:
return error;
}
-static int negotiate_is_complete(git_http_auth_context *c)
+static int gssapi_is_complete(git_http_auth_context *c)
{
- http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
GIT_ASSERT_ARG(ctx);
return (ctx->complete == 1);
}
-static void negotiate_context_free(git_http_auth_context *c)
+static void gssapi_context_free(git_http_auth_context *c)
{
- http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+ http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c;
- negotiate_context_dispose(ctx);
+ gssapi_context_dispose(ctx);
ctx->configured = 0;
ctx->complete = 0;
@@ -226,8 +226,8 @@ static void negotiate_context_free(git_http_auth_context *c)
git__free(ctx);
}
-static int negotiate_init_context(
- http_auth_negotiate_context *ctx,
+static int gssapi_init_context(
+ http_auth_gssapi_context *ctx,
const git_net_url *url)
{
OM_uint32 status_major, status_minor;
@@ -239,13 +239,13 @@ static int negotiate_init_context(
status_major = gss_indicate_mechs(&status_minor, &mechanism_list);
if (GSS_ERROR(status_major)) {
- negotiate_err_set(status_major, status_minor,
+ gssapi_err_set(status_major, status_minor,
"could not query mechanisms");
return -1;
}
if (mechanism_list) {
- for (oid = negotiate_oids; *oid; oid++) {
+ for (oid = gssapi_oids; *oid; oid++) {
for (i = 0; i < mechanism_list->count; i++) {
item = &mechanism_list->elements[i];
@@ -285,14 +285,14 @@ int git_http_auth_negotiate(
git_http_auth_context **out,
const git_net_url *url)
{
- http_auth_negotiate_context *ctx;
+ http_auth_gssapi_context *ctx;
*out = NULL;
- ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
+ ctx = git__calloc(1, sizeof(http_auth_gssapi_context));
GIT_ERROR_CHECK_ALLOC(ctx);
- if (negotiate_init_context(ctx, url) < 0) {
+ if (gssapi_init_context(ctx, url) < 0) {
git__free(ctx);
return -1;
}
@@ -300,10 +300,10 @@ int git_http_auth_negotiate(
ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
ctx->parent.connection_affinity = 1;
- ctx->parent.set_challenge = negotiate_set_challenge;
- ctx->parent.next_token = negotiate_next_token;
- ctx->parent.is_complete = negotiate_is_complete;
- ctx->parent.free = negotiate_context_free;
+ ctx->parent.set_challenge = gssapi_set_challenge;
+ ctx->parent.next_token = gssapi_next_token;
+ ctx->parent.is_complete = gssapi_is_complete;
+ ctx->parent.free = gssapi_context_free;
*out = (git_http_auth_context *)ctx;
diff --git a/src/libgit2/transports/auth_negotiate.h b/src/libgit2/transports/auth_negotiate.h
index 34aff295b..4360785c5 100644
--- a/src/libgit2/transports/auth_negotiate.h
+++ b/src/libgit2/transports/auth_negotiate.h
@@ -12,7 +12,7 @@
#include "git2.h"
#include "auth.h"
-#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK)
+#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) || defined(GIT_WIN32)
extern int git_http_auth_negotiate(
git_http_auth_context **out,
diff --git a/src/libgit2/transports/auth_ntlm.h b/src/libgit2/transports/auth_ntlm.h
index 40689498c..33406ae94 100644
--- a/src/libgit2/transports/auth_ntlm.h
+++ b/src/libgit2/transports/auth_ntlm.h
@@ -13,7 +13,7 @@
/* NTLM requires a full request/challenge/response */
#define GIT_AUTH_STEPS_NTLM 2
-#ifdef GIT_NTLM
+#if defined(GIT_NTLM) || defined(GIT_WIN32)
#if defined(GIT_OPENSSL)
# define CRYPT_OPENSSL
diff --git a/src/libgit2/transports/auth_ntlm.c b/src/libgit2/transports/auth_ntlmclient.c
index f49ce101a..6f26a6179 100644
--- a/src/libgit2/transports/auth_ntlm.c
+++ b/src/libgit2/transports/auth_ntlmclient.c
@@ -23,7 +23,7 @@ typedef struct {
bool complete;
} http_auth_ntlm_context;
-static int ntlm_set_challenge(
+static int ntlmclient_set_challenge(
git_http_auth_context *c,
const char *challenge)
{
@@ -40,7 +40,7 @@ static int ntlm_set_challenge(
return 0;
}
-static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred)
+static int ntlmclient_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred)
{
git_credential_userpass_plaintext *cred;
const char *sep, *username;
@@ -76,7 +76,7 @@ done:
return error;
}
-static int ntlm_next_token(
+static int ntlmclient_next_token(
git_str *buf,
git_http_auth_context *c,
git_credential *cred)
@@ -104,7 +104,7 @@ static int ntlm_next_token(
*/
ctx->complete = true;
- if (cred && ntlm_set_credentials(ctx, cred) != 0)
+ if (cred && ntlmclient_set_credentials(ctx, cred) != 0)
goto done;
if (challenge_len < 4) {
@@ -162,7 +162,7 @@ done:
return error;
}
-static int ntlm_is_complete(git_http_auth_context *c)
+static int ntlmclient_is_complete(git_http_auth_context *c)
{
http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
@@ -170,7 +170,7 @@ static int ntlm_is_complete(git_http_auth_context *c)
return (ctx->complete == true);
}
-static void ntlm_context_free(git_http_auth_context *c)
+static void ntlmclient_context_free(git_http_auth_context *c)
{
http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
@@ -179,7 +179,7 @@ static void ntlm_context_free(git_http_auth_context *c)
git__free(ctx);
}
-static int ntlm_init_context(
+static int ntlmclient_init_context(
http_auth_ntlm_context *ctx,
const git_net_url *url)
{
@@ -206,7 +206,7 @@ int git_http_auth_ntlm(
ctx = git__calloc(1, sizeof(http_auth_ntlm_context));
GIT_ERROR_CHECK_ALLOC(ctx);
- if (ntlm_init_context(ctx, url) < 0) {
+ if (ntlmclient_init_context(ctx, url) < 0) {
git__free(ctx);
return -1;
}
@@ -214,10 +214,10 @@ int git_http_auth_ntlm(
ctx->parent.type = GIT_HTTP_AUTH_NTLM;
ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT;
ctx->parent.connection_affinity = 1;
- ctx->parent.set_challenge = ntlm_set_challenge;
- ctx->parent.next_token = ntlm_next_token;
- ctx->parent.is_complete = ntlm_is_complete;
- ctx->parent.free = ntlm_context_free;
+ ctx->parent.set_challenge = ntlmclient_set_challenge;
+ ctx->parent.next_token = ntlmclient_next_token;
+ ctx->parent.is_complete = ntlmclient_is_complete;
+ ctx->parent.free = ntlmclient_context_free;
*out = (git_http_auth_context *)ctx;
diff --git a/src/libgit2/transports/auth_sspi.c b/src/libgit2/transports/auth_sspi.c
new file mode 100644
index 000000000..f8269365d
--- /dev/null
+++ b/src/libgit2/transports/auth_sspi.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "auth_ntlm.h"
+#include "auth_negotiate.h"
+
+#ifdef GIT_WIN32
+
+#define SECURITY_WIN32
+
+#include "git2.h"
+#include "auth.h"
+#include "git2/sys/credential.h"
+
+#include <windows.h>
+#include <security.h>
+
+typedef struct {
+ git_http_auth_context parent;
+ wchar_t *target;
+
+ const char *package_name;
+ size_t package_name_len;
+ wchar_t *package_name_w;
+ SecPkgInfoW *package_info;
+ SEC_WINNT_AUTH_IDENTITY_W identity;
+ CredHandle cred;
+ CtxtHandle context;
+
+ int has_identity : 1,
+ has_credentials : 1,
+ has_context : 1,
+ complete : 1;
+ git_str challenge;
+} http_auth_sspi_context;
+
+static void sspi_reset_context(http_auth_sspi_context *ctx)
+{
+ if (ctx->has_identity) {
+ git__free(ctx->identity.User);
+ git__free(ctx->identity.Domain);
+ git__free(ctx->identity.Password);
+
+ memset(&ctx->identity, 0, sizeof(SEC_WINNT_AUTH_IDENTITY_W));
+
+ ctx->has_identity = 0;
+ }
+
+ if (ctx->has_credentials) {
+ FreeCredentialsHandle(&ctx->cred);
+ memset(&ctx->cred, 0, sizeof(CredHandle));
+
+ ctx->has_credentials = 0;
+ }
+
+ if (ctx->has_context) {
+ DeleteSecurityContext(&ctx->context);
+ memset(&ctx->context, 0, sizeof(CtxtHandle));
+
+ ctx->has_context = 0;
+ }
+
+ ctx->complete = 0;
+
+ git_str_dispose(&ctx->challenge);
+}
+
+static int sspi_set_challenge(
+ git_http_auth_context *c,
+ const char *challenge)
+{
+ http_auth_sspi_context *ctx = (http_auth_sspi_context *)c;
+ size_t challenge_len = strlen(challenge);
+
+ git_str_clear(&ctx->challenge);
+
+ if (strncmp(challenge, ctx->package_name, ctx->package_name_len) != 0) {
+ git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name);
+ return -1;
+ }
+
+ /*
+ * A package type indicator without a base64 payload indicates the
+ * mechanism; it's not an actual challenge. Ignore it.
+ */
+ if (challenge[ctx->package_name_len] == 0) {
+ return 0;
+ } else if (challenge[ctx->package_name_len] != ' ') {
+ git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name);
+ return -1;
+ }
+
+ if (git_str_decode_base64(&ctx->challenge,
+ challenge + (ctx->package_name_len + 1),
+ challenge_len - (ctx->package_name_len + 1)) < 0) {
+ git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name);
+ return -1;
+ }
+
+ GIT_ASSERT(ctx->challenge.size <= ULONG_MAX);
+ return 0;
+}
+
+static int create_identity(
+ SEC_WINNT_AUTH_IDENTITY_W **out,
+ http_auth_sspi_context *ctx,
+ git_credential *cred)
+{
+ git_credential_userpass_plaintext *userpass;
+ wchar_t *username = NULL, *domain = NULL, *password = NULL;
+ int username_len = 0, domain_len = 0, password_len = 0;
+ const char *sep;
+
+ if (cred->credtype == GIT_CREDENTIAL_DEFAULT) {
+ *out = NULL;
+ return 0;
+ }
+
+ if (cred->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) {
+ git_error_set(GIT_ERROR_NET, "unknown credential type: %d", cred->credtype);
+ return -1;
+ }
+
+ userpass = (git_credential_userpass_plaintext *)cred;
+
+ if ((sep = strchr(userpass->username, '\\')) != NULL) {
+ GIT_ASSERT(sep - userpass->username < INT_MAX);
+
+ username_len = git_utf8_to_16_alloc(&username, sep + 1);
+ domain_len = git_utf8_to_16_alloc_with_len(&domain,
+ userpass->username, (int)(sep - userpass->username));
+ } else {
+ username_len = git_utf8_to_16_alloc(&username,
+ userpass->username);
+ }
+
+ password_len = git_utf8_to_16_alloc(&password, userpass->password);
+
+ if (username_len < 0 || domain_len < 0 || password_len < 0) {
+ git__free(username);
+ git__free(domain);
+ git__free(password);
+ return -1;
+ }
+
+ ctx->identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ ctx->identity.User = username;
+ ctx->identity.UserLength = (unsigned long)username_len;
+ ctx->identity.Password = password;
+ ctx->identity.PasswordLength = (unsigned long)password_len;
+ ctx->identity.Domain = domain;
+ ctx->identity.DomainLength = (unsigned long)domain_len;
+
+ ctx->has_identity = 1;
+
+ *out = &ctx->identity;
+
+ return 0;
+}
+
+static int sspi_next_token(
+ git_str *buf,
+ git_http_auth_context *c,
+ git_credential *cred)
+{
+ http_auth_sspi_context *ctx = (http_auth_sspi_context *)c;
+ SEC_WINNT_AUTH_IDENTITY_W *identity = NULL;
+ TimeStamp timestamp;
+ DWORD context_flags;
+ SecBuffer input_buf = { 0, SECBUFFER_TOKEN, NULL };
+ SecBuffer output_buf = { 0, SECBUFFER_TOKEN, NULL };
+ SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 1, &input_buf };
+ SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 1, &output_buf };
+ SECURITY_STATUS status;
+
+ if (ctx->complete)
+ sspi_reset_context(ctx);
+
+ if (!ctx->has_context) {
+ if (create_identity(&identity, ctx, cred) < 0)
+ return -1;
+
+ status = AcquireCredentialsHandleW(NULL, ctx->package_name_w,
+ SECPKG_CRED_BOTH, NULL, identity, NULL,
+ NULL, &ctx->cred, &timestamp);
+
+ if (status != SEC_E_OK) {
+ git_error_set(GIT_ERROR_OS, "could not acquire credentials");
+ return -1;
+ }
+
+ ctx->has_credentials = 1;
+ }
+
+ context_flags = ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_REQ_MUTUAL_AUTH;
+
+ if (ctx->challenge.size > 0) {
+ input_buf.BufferType = SECBUFFER_TOKEN;
+ input_buf.cbBuffer = (unsigned long)ctx->challenge.size;
+ input_buf.pvBuffer = ctx->challenge.ptr;
+ }
+
+ status = InitializeSecurityContextW(&ctx->cred,
+ ctx->has_context ? &ctx->context : NULL,
+ ctx->target,
+ context_flags,
+ 0,
+ SECURITY_NETWORK_DREP,
+ ctx->has_context ? &input_buf_desc : NULL,
+ 0,
+ ctx->has_context ? NULL : &ctx->context,
+ &output_buf_desc,
+ &context_flags,
+ NULL);
+
+ if (status == SEC_I_COMPLETE_AND_CONTINUE ||
+ status == SEC_I_COMPLETE_NEEDED)
+ status = CompleteAuthToken(&ctx->context, &output_buf_desc);
+
+ if (status == SEC_E_OK) {
+ ctx->complete = 1;
+ } else if (status != SEC_I_CONTINUE_NEEDED) {
+ git_error_set(GIT_ERROR_OS, "could not initialize security context");
+ return -1;
+ }
+
+ ctx->has_context = 1;
+ git_str_clear(&ctx->challenge);
+
+ if (output_buf.cbBuffer > 0) {
+ git_str_put(buf, ctx->package_name, ctx->package_name_len);
+ git_str_putc(buf, ' ');
+ git_str_encode_base64(buf, output_buf.pvBuffer, output_buf.cbBuffer);
+
+ FreeContextBuffer(output_buf.pvBuffer);
+
+ if (git_str_oom(buf))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sspi_is_complete(git_http_auth_context *c)
+{
+ http_auth_sspi_context *ctx = (http_auth_sspi_context *)c;
+
+ return ctx->complete;
+}
+
+static void sspi_context_free(git_http_auth_context *c)
+{
+ http_auth_sspi_context *ctx = (http_auth_sspi_context *)c;
+
+ sspi_reset_context(ctx);
+
+ FreeContextBuffer(ctx->package_info);
+ git__free(ctx->target);
+ git__free(ctx);
+}
+
+static int sspi_init_context(
+ git_http_auth_context **out,
+ git_http_auth_t type,
+ const git_net_url *url)
+{
+ http_auth_sspi_context *ctx;
+ git_str target = GIT_STR_INIT;
+
+ *out = NULL;
+
+ ctx = git__calloc(1, sizeof(http_auth_sspi_context));
+ GIT_ERROR_CHECK_ALLOC(ctx);
+
+ switch (type) {
+ case GIT_HTTP_AUTH_NTLM:
+ ctx->package_name = "NTLM";
+ ctx->package_name_len = CONST_STRLEN("NTLM");
+ ctx->package_name_w = L"NTLM";
+ ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT |
+ GIT_CREDENTIAL_DEFAULT;
+ break;
+ case GIT_HTTP_AUTH_NEGOTIATE:
+ ctx->package_name = "Negotiate";
+ ctx->package_name_len = CONST_STRLEN("Negotiate");
+ ctx->package_name_w = L"Negotiate";
+ ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
+ break;
+ default:
+ git_error_set(GIT_ERROR_NET, "unknown SSPI auth type: %d", ctx->parent.type);
+ git__free(ctx);
+ return -1;
+ }
+
+ if (QuerySecurityPackageInfoW(ctx->package_name_w, &ctx->package_info) != SEC_E_OK) {
+ git_error_set(GIT_ERROR_OS, "could not query security package");
+ git__free(ctx);
+ return -1;
+ }
+
+ if (git_str_printf(&target, "http/%s", url->host) < 0 ||
+ git_utf8_to_16_alloc(&ctx->target, target.ptr) < 0) {
+ FreeContextBuffer(ctx->package_info);
+ git__free(ctx);
+ return -1;
+ }
+
+ ctx->parent.type = type;
+ ctx->parent.connection_affinity = 1;
+ ctx->parent.set_challenge = sspi_set_challenge;
+ ctx->parent.next_token = sspi_next_token;
+ ctx->parent.is_complete = sspi_is_complete;
+ ctx->parent.free = sspi_context_free;
+
+ *out = (git_http_auth_context *)ctx;
+
+ git_str_dispose(&target);
+ return 0;
+}
+
+int git_http_auth_negotiate(
+ git_http_auth_context **out,
+ const git_net_url *url)
+{
+ return sspi_init_context(out, GIT_HTTP_AUTH_NEGOTIATE, url);
+}
+
+int git_http_auth_ntlm(
+ git_http_auth_context **out,
+ const git_net_url *url)
+{
+ return sspi_init_context(out, GIT_HTTP_AUTH_NTLM, url);
+}
+
+#endif /* GIT_WIN32 */
diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c
index 098227607..de24a2a41 100644
--- a/src/libgit2/transports/winhttp.c
+++ b/src/libgit2/transports/winhttp.c
@@ -158,10 +158,10 @@ static int apply_userpass_credentials(HINTERNET request, DWORD target, int mecha
goto done;
}
- if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0)
+ if ((error = user_len = git_utf8_to_16_alloc(&user, c->username)) < 0)
goto done;
- if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0)
+ if ((error = pass_len = git_utf8_to_16_alloc(&pass, c->password)) < 0)
goto done;
if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) {
@@ -242,7 +242,7 @@ static int acquire_fallback_cred(
HRESULT hCoInitResult;
/* Convert URL to wide characters */
- if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
+ if (git_utf8_to_16_alloc(&wide_url, url) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
return -1;
}
@@ -397,7 +397,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
return -1;
/* Convert URL to wide characters */
- if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) {
+ if (git_utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert string to wide form");
goto on_error;
}
@@ -473,7 +473,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
}
/* Convert URL to wide characters */
- error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
+ error = git_utf8_to_16_alloc(&proxy_wide, processed_url.ptr);
git_str_dispose(&processed_url);
if (error < 0)
goto on_error;
@@ -531,7 +531,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
s->service) < 0)
goto on_error;
- if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
+ if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters");
goto on_error;
}
@@ -548,7 +548,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
s->service) < 0)
goto on_error;
- if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
+ if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters");
goto on_error;
}
@@ -568,7 +568,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]);
/* Convert header to wide characters */
- if ((error = git__utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0)
+ if ((error = git_utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0)
goto on_error;
if (!WinHttpAddRequestHeaders(s->request, custom_header_wide, (ULONG)-1L,
@@ -783,7 +783,7 @@ static int winhttp_connect(
}
/* Prepare host */
- if (git__utf8_to_16_alloc(&wide_host, host) < 0) {
+ if (git_utf8_to_16_alloc(&wide_host, host) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
goto on_error;
}
@@ -792,7 +792,7 @@ static int winhttp_connect(
if (git_http__user_agent(&ua) < 0)
goto on_error;
- if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
+ if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
goto on_error;
}
@@ -1182,7 +1182,7 @@ replay:
}
/* Convert the Location header to UTF-8 */
- if (git__utf16_to_8_alloc(&location8, location) < 0) {
+ if (git_utf8_from_16_alloc(&location8, location) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8");
git__free(location);
return -1;
@@ -1254,7 +1254,7 @@ replay:
else
p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
- if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
+ if (git_utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters");
return -1;
}
diff --git a/src/util/fs_path.c b/src/util/fs_path.c
index b52867e77..e03fcf7c7 100644
--- a/src/util/fs_path.c
+++ b/src/util/fs_path.c
@@ -2015,7 +2015,7 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable)
git_win32_path fullpath_w, executable_w;
int error;
- if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
+ if (git_utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0)
return -1;
error = git_win32_path_find_executable(fullpath_w, executable_w);
diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in
index fbf0cab60..1575be641 100644
--- a/src/util/git2_features.h.in
+++ b/src/util/git2_features.h.in
@@ -41,6 +41,7 @@
#cmakedefine GIT_OPENSSL_DYNAMIC 1
#cmakedefine GIT_SECURE_TRANSPORT 1
#cmakedefine GIT_MBEDTLS 1
+#cmakedefine GIT_SCHANNEL 1
#cmakedefine GIT_SHA1_COLLISIONDETECT 1
#cmakedefine GIT_SHA1_WIN32 1
diff --git a/src/util/util.c b/src/util/util.c
index aee95fddf..9c9f2c040 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -743,7 +743,7 @@ int git__getenv(git_str *out, const char *name)
git_str_clear(out);
- if (git__utf8_to_16_alloc(&wide_name, name) < 0)
+ if (git_utf8_to_16_alloc(&wide_name, name) < 0)
return -1;
if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) {
diff --git a/src/util/win32/error.c b/src/util/win32/error.c
index 3a52fb5a9..dfd6fa1e8 100644
--- a/src/util/win32/error.c
+++ b/src/util/win32/error.c
@@ -43,7 +43,7 @@ char *git_win32_get_error_message(DWORD error_code)
(LPWSTR)&lpMsgBuf, 0, NULL)) {
/* Convert the message to UTF-8. If this fails, we will
* return NULL, which is a condition expected by the caller */
- if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0)
+ if (git_utf8_from_16_alloc(&utf8_msg, lpMsgBuf) < 0)
utf8_msg = NULL;
LocalFree(lpMsgBuf);
diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c
index d9fc8292b..7a559e45c 100644
--- a/src/util/win32/path_w32.c
+++ b/src/util/win32/path_w32.c
@@ -336,13 +336,13 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
/* See if this is an absolute path (beginning with a drive letter) */
if (git_fs_path_is_absolute(src)) {
- if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0)
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0)
goto on_error;
}
/* File-prefixed NT-style paths beginning with \\?\ */
else if (path__is_nt_namespace(src)) {
/* Skip the NT prefix, the destination already contains it */
- if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0)
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0)
goto on_error;
}
/* UNC paths */
@@ -351,7 +351,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
dest += 4;
/* Skip the leading "\\" */
- if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0)
+ if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0)
goto on_error;
}
/* Absolute paths omitting the drive letter */
@@ -365,7 +365,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
}
/* Skip the drive letter specification ("C:") */
- if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0)
+ if (git_utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0)
goto on_error;
}
/* Relative paths */
@@ -377,7 +377,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src)
dest[cwd_len++] = L'\\';
- if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0)
+ if (git_utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0)
goto on_error;
}
@@ -404,7 +404,7 @@ int git_win32_path_relative_from_utf8(git_win32_path out, const char *src)
return git_win32_path_from_utf8(out, src);
}
- if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0)
+ if ((len = git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0)
return -1;
for (p = dest; p < (dest + len); p++) {
@@ -433,7 +433,7 @@ int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
}
}
- if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0)
+ if ((len = git_utf8_from_16(out, GIT_WIN_PATH_UTF8, src)) < 0)
return len;
git_fs_path_mkposix(dest);
@@ -471,7 +471,7 @@ char *git_win32_path_8dot3_name(const char *path)
if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL)
return NULL;
- if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0)
+ if ((len = git_utf8_from_16(shortname, namelen + 1, start)) < 0)
return NULL;
return shortname;
diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c
index 5862e5c9a..3fec469a6 100644
--- a/src/util/win32/posix_w32.c
+++ b/src/util/win32/posix_w32.c
@@ -649,7 +649,7 @@ int p_getcwd(char *buffer_out, size_t size)
git_win32_path_remove_namespace(cwd, wcslen(cwd));
/* Convert the working directory back to UTF-8 */
- if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
+ if (git_utf8_from_16(buffer_out, size, cwd) < 0) {
DWORD code = GetLastError();
if (code == ERROR_INSUFFICIENT_BUFFER)
diff --git a/src/util/win32/utf-conv.c b/src/util/win32/utf-conv.c
index 4bde3023a..ad35c0c35 100644
--- a/src/util/win32/utf-conv.c
+++ b/src/util/win32/utf-conv.c
@@ -15,108 +15,114 @@ GIT_INLINE(void) git__set_errno(void)
errno = EINVAL;
}
-/**
- * Converts a UTF-8 string to wide characters.
- *
- * @param dest The buffer to receive the wide string.
- * @param dest_size The size of the buffer, in characters.
- * @param src The UTF-8 string to convert.
- * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
- */
-int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_to_16_with_len(dest, dest_size, src, -1);
+}
+
+int git_utf8_to_16_with_len(
+ wchar_t *dest,
+ size_t _dest_size,
+ const char *src,
+ int src_len)
{
+ int dest_size = (int)min(_dest_size, INT_MAX);
int len;
- /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
- * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
- * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
- if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0)
+ /*
+ * Subtract 1 from the result to turn 0 into -1 (an error code) and
+ * to not count the NULL terminator as part of the string's length.
+ * MultiByteToWideChar never returns int's minvalue, so underflow
+ * is not possible.
+ */
+ len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ src, src_len, dest, dest_size) - 1;
+
+ if (len < 0)
git__set_errno();
return len;
}
-/**
- * Converts a wide string to UTF-8.
- *
- * @param dest The buffer to receive the UTF-8 string.
- * @param dest_size The size of the buffer, in bytes.
- * @param src The wide string to convert.
- * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
- */
-int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
+int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src)
{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_from_16_with_len(dest, dest_size, src, -1);
+}
+
+int git_utf8_from_16_with_len(
+ char *dest,
+ size_t _dest_size,
+ const wchar_t *src,
+ int src_len)
+{
+ int dest_size = (int)min(_dest_size, INT_MAX);
int len;
- /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
- * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
- * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
- if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0)
+ /*
+ * Subtract 1 from the result to turn 0 into -1 (an error code) and
+ * to not count the NULL terminator as part of the string's length.
+ * WideCharToMultiByte never returns int's minvalue, so underflow
+ * is not possible.
+ */
+ len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, dest, dest_size, NULL, NULL) - 1;
+
+ if (len < 0)
git__set_errno();
return len;
}
-/**
- * Converts a UTF-8 string to wide characters.
- * Memory is allocated to hold the converted string.
- * The caller is responsible for freeing the string with git__free.
- *
- * @param dest Receives a pointer to the wide string.
- * @param src The UTF-8 string to convert.
- * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
- */
-int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
+int git_utf8_to_16_alloc(wchar_t **dest, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_to_16_alloc_with_len(dest, src, -1);
+}
+
+int git_utf8_to_16_alloc_with_len(wchar_t **dest, const char *src, int src_len)
{
int utf16_size;
*dest = NULL;
- /* Length of -1 indicates NULL termination of the input string */
- utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ src, src_len, NULL, 0);
if (!utf16_size) {
git__set_errno();
return -1;
}
- if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) {
- errno = ENOMEM;
- return -1;
- }
+ *dest = git__mallocarray(utf16_size, sizeof(wchar_t));
+ GIT_ERROR_CHECK_ALLOC(*dest);
- utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
-
- if (!utf16_size) {
- git__set_errno();
+ utf16_size = git_utf8_to_16_with_len(*dest, (size_t)utf16_size,
+ src, src_len);
+ if (utf16_size < 0) {
git__free(*dest);
*dest = NULL;
}
- /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
- * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
- * so underflow is not possible */
- return utf16_size - 1;
+ return utf16_size;
}
-/**
- * Converts a wide string to UTF-8.
- * Memory is allocated to hold the converted string.
- * The caller is responsible for freeing the string with git__free.
- *
- * @param dest Receives a pointer to the UTF-8 string.
- * @param src The wide string to convert.
- * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
- */
-int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
+int git_utf8_from_16_alloc(char **dest, const wchar_t *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. */
+ return git_utf8_from_16_alloc_with_len(dest, src, -1);
+}
+
+int git_utf8_from_16_alloc_with_len(char **dest, const wchar_t *src, int src_len)
{
int utf8_size;
*dest = NULL;
- /* Length of -1 indicates NULL termination of the input string */
- utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL);
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, NULL, 0, NULL, NULL);
if (!utf8_size) {
git__set_errno();
@@ -124,23 +130,15 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
}
*dest = git__malloc(utf8_size);
+ GIT_ERROR_CHECK_ALLOC(*dest);
- if (!*dest) {
- errno = ENOMEM;
- return -1;
- }
-
- utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL);
-
- if (!utf8_size) {
- git__set_errno();
+ utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
+ src, src_len, *dest, utf8_size, NULL, NULL);
+ if (utf8_size < 0) {
git__free(*dest);
*dest = NULL;
}
- /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
- * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
- * so underflow is not possible */
- return utf8_size - 1;
+ return utf8_size;
}
diff --git a/src/util/win32/utf-conv.h b/src/util/win32/utf-conv.h
index 120d647ef..301f5a6d3 100644
--- a/src/util/win32/utf-conv.h
+++ b/src/util/win32/utf-conv.h
@@ -16,14 +16,45 @@
#endif
/**
+ * Converts a NUL-terminated UTF-8 string to wide characters. This is a
+ * convenience function for `git_utf8_to_16_with_len`.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
+
+/**
* Converts a UTF-8 string to wide characters.
*
* @param dest The buffer to receive the wide string.
* @param dest_size The size of the buffer, in characters.
* @param src The UTF-8 string to convert.
- * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ * @param src_len The length of the string to convert.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16_with_len(
+ wchar_t *dest,
+ size_t dest_size,
+ const char *src,
+ int src_len);
+
+/**
+ * Converts a NUL-terminated wide string to UTF-8. This is a convenience
+ * function for `git_utf8_from_16_with_len`.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @param src_len The length of the string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
*/
-int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
+int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src);
/**
* Converts a wide string to UTF-8.
@@ -31,30 +62,66 @@ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
* @param dest The buffer to receive the UTF-8 string.
* @param dest_size The size of the buffer, in bytes.
* @param src The wide string to convert.
- * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ * @param src_len The length of the string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
*/
-int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src);
+int git_utf8_from_16_with_len(char *dest, size_t dest_size, const wchar_t *src, int src_len);
/**
- * Converts a UTF-8 string to wide characters.
- * Memory is allocated to hold the converted string.
- * The caller is responsible for freeing the string with git__free.
+ * Converts a UTF-8 string to wide characters. Memory is allocated to hold
+ * the converted string. The caller is responsible for freeing the string
+ * with git__free.
*
* @param dest Receives a pointer to the wide string.
* @param src The UTF-8 string to convert.
- * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
*/
-int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
+int git_utf8_to_16_alloc(wchar_t **dest, const char *src);
/**
- * Converts a wide string to UTF-8.
- * Memory is allocated to hold the converted string.
- * The caller is responsible for freeing the string with git__free.
+ * Converts a UTF-8 string to wide characters. Memory is allocated to hold
+ * the converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @param src_len The length of the string.
+ * @return The length of the wide string, in characters
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_to_16_alloc_with_len(
+ wchar_t **dest,
+ const char *src,
+ int src_len);
+
+/**
+ * Converts a wide string to UTF-8. Memory is allocated to hold the
+ * converted string. The caller is responsible for freeing the string
+ * with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
+ */
+int git_utf8_from_16_alloc(char **dest, const wchar_t *src);
+
+/**
+ * Converts a wide string to UTF-8. Memory is allocated to hold the
+ * converted string. The caller is responsible for freeing the string
+ * with git__free.
*
* @param dest Receives a pointer to the UTF-8 string.
* @param src The wide string to convert.
- * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ * @param src_len The length of the wide string.
+ * @return The length of the UTF-8 string, in bytes
+ * (not counting the NULL terminator), or < 0 for failure
*/
-int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
+int git_utf8_from_16_alloc_with_len(
+ char **dest,
+ const wchar_t *src,
+ int src_len);
#endif
diff --git a/src/util/win32/w32_util.c b/src/util/win32/w32_util.c
index fe4b75bae..f5b006a19 100644
--- a/src/util/win32/w32_util.c
+++ b/src/util/win32/w32_util.c
@@ -115,7 +115,7 @@ int git_win32__file_attribute_to_stat(
/* st_size gets the UTF-8 length of the target name, in bytes,
* not counting the NULL terminator */
- if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) {
+ if ((st->st_size = git_utf8_from_16(NULL, 0, target)) < 0) {
git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path);
return -1;
}
diff --git a/tests/clar/clar_libgit2.c b/tests/clar/clar_libgit2.c
index 54122997d..a1b92fc33 100644
--- a/tests/clar/clar_libgit2.c
+++ b/tests/clar/clar_libgit2.c
@@ -103,10 +103,10 @@ int cl_setenv(const char *name, const char *value)
{
wchar_t *wide_name, *wide_value = NULL;
- cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
+ cl_assert(git_utf8_to_16_alloc(&wide_name, name) >= 0);
if (value) {
- cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
+ cl_assert(git_utf8_to_16_alloc(&wide_value, value) >= 0);
cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
} else {
/* Windows XP returns 0 (failed) when passing NULL for lpValue when
diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c
index 1a4cdb520..b635739b6 100644
--- a/tests/libgit2/online/clone.c
+++ b/tests/libgit2/online/clone.c
@@ -580,6 +580,17 @@ static int succeed_certificate_check(git_cert *cert, int valid, const char *host
return 0;
}
+static int x509_succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
+{
+ GIT_UNUSED(valid);
+ GIT_UNUSED(payload);
+
+ cl_assert_equal_s("github.com", host);
+ cl_assert_equal_i(GIT_CERT_X509, cert->cert_type);
+
+ return 0;
+}
+
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
@@ -901,7 +912,7 @@ void test_online_clone__certificate_invalid(void)
void test_online_clone__certificate_valid(void)
{
- g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
+ g_options.fetch_opts.callbacks.certificate_check = x509_succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
diff --git a/tests/libgit2/stream/registration.c b/tests/libgit2/stream/registration.c
index bf3c20502..ccaecee8c 100644
--- a/tests/libgit2/stream/registration.c
+++ b/tests/libgit2/stream/registration.c
@@ -81,10 +81,10 @@ void test_stream_registration__tls(void)
cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL));
error = git_tls_stream_new(&stream, "localhost", "443");
- /* We don't have TLS support enabled, or we're on Windows,
- * which has no arbitrary TLS stream support.
+ /* We don't have TLS support enabled, or we're on Windows
+ * with WinHTTP, which is not actually TLS stream support.
*/
-#if defined(GIT_WIN32) || !defined(GIT_HTTPS)
+#if defined(GIT_WINHTTP) || !defined(GIT_HTTPS)
cl_git_fail_with(-1, error);
#else
cl_git_pass(error);
diff --git a/tests/util/link.c b/tests/util/link.c
index 46cafada7..5909e26e3 100644
--- a/tests/util/link.c
+++ b/tests/util/link.c
@@ -98,7 +98,7 @@ static void do_junction(const char *old, const char *new)
git_str_putc(&unparsed_buf, '\\');
- subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
+ subst_utf16_len = git_utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf));
subst_byte_len = subst_utf16_len * sizeof(WCHAR);
print_utf16_len = subst_utf16_len - 4;
@@ -124,11 +124,11 @@ static void do_junction(const char *old, const char *new)
subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer;
print_utf16 = subst_utf16 + subst_utf16_len + 1;
- ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
+ ret = git_utf8_to_16(subst_utf16, subst_utf16_len + 1,
git_str_cstr(&unparsed_buf));
cl_assert_equal_i(subst_utf16_len, ret);
- ret = git__utf8_to_16(print_utf16,
+ ret = git_utf8_to_16(print_utf16,
print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4);
cl_assert_equal_i(print_utf16_len, ret);