summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2019-06-11 08:07:48 +0100
committerGitHub <noreply@github.com>2019-06-11 08:07:48 +0100
commit110b5895e0b1c2874d6de01996477a1b7544f22b (patch)
tree20c0ec3bdb8779d71ea82d17d593cd5564cdba75
parentf4584a1e8bf3095b90f5564c0f89878ef79855c5 (diff)
parent7ea8630e04bd0b5f86c6fcc73899433317b8f0fa (diff)
downloadlibgit2-110b5895e0b1c2874d6de01996477a1b7544f22b.tar.gz
Merge pull request #5052 from libgit2/ethomson/netrefactor
Add NTLM support for HTTP(s) servers and proxies
-rw-r--r--CMakeLists.txt4
-rw-r--r--ci/test.ps135
-rwxr-xr-xci/test.sh26
-rw-r--r--deps/ntlmclient/CMakeLists.txt18
-rw-r--r--deps/ntlmclient/compat.h33
-rw-r--r--deps/ntlmclient/crypt.h64
-rw-r--r--deps/ntlmclient/crypt_commoncrypto.c120
-rw-r--r--deps/ntlmclient/crypt_commoncrypto.h18
-rw-r--r--deps/ntlmclient/crypt_mbedtls.c145
-rw-r--r--deps/ntlmclient/crypt_mbedtls.h18
-rw-r--r--deps/ntlmclient/crypt_openssl.c130
-rw-r--r--deps/ntlmclient/crypt_openssl.h21
-rw-r--r--deps/ntlmclient/ntlm.c1420
-rw-r--r--deps/ntlmclient/ntlm.h174
-rw-r--r--deps/ntlmclient/ntlmclient.h320
-rw-r--r--deps/ntlmclient/unicode.h36
-rw-r--r--deps/ntlmclient/unicode_builtin.c445
-rw-r--r--deps/ntlmclient/unicode_iconv.c201
-rw-r--r--deps/ntlmclient/utf8.h1257
-rw-r--r--deps/ntlmclient/util.c21
-rw-r--r--deps/ntlmclient/util.h14
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/features.h.in2
-rw-r--r--src/net.c184
-rw-r--r--src/net.h36
-rw-r--r--src/netops.c211
-rw-r--r--src/netops.h28
-rw-r--r--src/transports/auth.c16
-rw-r--r--src/transports/auth.h15
-rw-r--r--src/transports/auth_negotiate.c23
-rw-r--r--src/transports/auth_negotiate.h2
-rw-r--r--src/transports/auth_ntlm.c222
-rw-r--r--src/transports/auth_ntlm.h35
-rw-r--r--src/transports/git.c27
-rw-r--r--src/transports/http.c670
-rw-r--r--src/transports/http.h2
-rw-r--r--src/transports/ssh.c56
-rw-r--r--src/transports/winhttp.c408
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/network/redirect.c113
-rw-r--r--tests/network/urlparse.c262
-rw-r--r--tests/online/clone.c28
42 files changed, 5957 insertions, 914 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1630a7b27..007963c10 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,6 +67,10 @@ OPTION(USE_BUNDLED_ZLIB "Use the bundled version of zlib" OFF)
OPTION(DEPRECATE_HARD "Do not include deprecated functions in the library" OFF)
SET(REGEX_BACKEND "" CACHE STRING "Regular expression implementation. One of regcomp_l, pcre2, pcre, regcomp, or builtin.")
+IF (UNIX)
+ OPTION(USE_NTLMCLIENT "Enable NTLM support on Unix." ON )
+ENDIF()
+
IF (UNIX AND NOT APPLE)
OPTION(ENABLE_REPRODUCIBLE_BUILDS "Enable reproducible builds" OFF)
ENDIF()
diff --git a/ci/test.ps1 b/ci/test.ps1
index 06e0ab228..68b53e269 100644
--- a/ci/test.ps1
+++ b/ci/test.ps1
@@ -38,18 +38,25 @@ Write-Host "## Configuring test environment"
Write-Host "##############################################################################"
if (-not $Env:SKIP_PROXY_TESTS) {
+ Invoke-WebRequest -Method GET -Uri https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar -OutFile poxyproxy.jar
+
+ Write-Host ""
+ Write-Host "Starting HTTP proxy (Basic)..."
+ javaw -jar poxyproxy.jar --port 8080 --credentials foo:bar --auth-type basic --quiet
+
Write-Host ""
- Write-Host "Starting HTTP proxy..."
- Invoke-WebRequest -Method GET -Uri https://github.com/ethomson/poxyproxy/releases/download/v0.4.0/poxyproxy-0.4.0.jar -OutFile poxyproxy.jar
- javaw -jar poxyproxy.jar -d --port 8080 --credentials foo:bar --quiet
+ Write-Host "Starting HTTP proxy (NTLM)..."
+ javaw -jar poxyproxy.jar --port 8090 --credentials foo:bar --auth-type ntlm --quiet
}
-Write-Host ""
-Write-Host "##############################################################################"
-Write-Host "## Running (offline) tests"
-Write-Host "##############################################################################"
+if (-not $Env:SKIP_OFFLINE_TESTS) {
+ Write-Host ""
+ Write-Host "##############################################################################"
+ Write-Host "## Running (offline) tests"
+ Write-Host "##############################################################################"
-run_test offline
+ run_test offline
+}
if ($Env:RUN_INVASIVE_TESTS) {
Write-Host ""
@@ -76,14 +83,24 @@ if (-not $Env:SKIP_ONLINE_TESTS) {
}
if (-not $Env:SKIP_PROXY_TESTS) {
+ # Test HTTP Basic authentication
Write-Host ""
- Write-Host "Running proxy tests"
+ Write-Host "Running proxy tests (Basic authentication)"
Write-Host ""
$Env:GITTEST_REMOTE_PROXY_HOST="localhost:8080"
$Env:GITTEST_REMOTE_PROXY_USER="foo"
$Env:GITTEST_REMOTE_PROXY_PASS="bar"
+ run_test proxy
+ # Test NTLM authentication
+ Write-Host ""
+ Write-Host "Running proxy tests (NTLM authentication)"
+ Write-Host ""
+
+ $Env:GITTEST_REMOTE_PROXY_HOST="localhost:8090"
+ $Env:GITTEST_REMOTE_PROXY_USER="foo"
+ $Env:GITTEST_REMOTE_PROXY_PASS="bar"
run_test proxy
$Env:GITTEST_REMOTE_PROXY_HOST=$null
diff --git a/ci/test.sh b/ci/test.sh
index 136ff2581..e3caa8086 100755
--- a/ci/test.sh
+++ b/ci/test.sh
@@ -78,9 +78,15 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then
fi
if [ -z "$SKIP_PROXY_TESTS" ]; then
- echo "Starting HTTP proxy..."
- curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.4.0/poxyproxy-0.4.0.jar >poxyproxy.jar
- java -jar poxyproxy.jar -d --address 127.0.0.1 --port 8080 --credentials foo:bar --quiet &
+ curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar >poxyproxy.jar
+
+ echo ""
+ echo "Starting HTTP proxy (Basic)..."
+ java -jar poxyproxy.jar --address 127.0.0.1 --port 8080 --credentials foo:bar --auth-type basic --quiet &
+
+ echo ""
+ echo "Starting HTTP proxy (NTLM)..."
+ java -jar poxyproxy.jar --address 127.0.0.1 --port 8090 --credentials foo:bar --auth-type ntlm --quiet &
fi
if [ -z "$SKIP_SSH_TESTS" ]; then
@@ -175,7 +181,7 @@ fi
if [ -z "$SKIP_PROXY_TESTS" ]; then
echo ""
- echo "Running proxy tests"
+ echo "Running proxy tests (Basic authentication)"
echo ""
export GITTEST_REMOTE_PROXY_HOST="localhost:8080"
@@ -185,6 +191,18 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then
unset GITTEST_REMOTE_PROXY_HOST
unset GITTEST_REMOTE_PROXY_USER
unset GITTEST_REMOTE_PROXY_PASS
+
+ echo ""
+ echo "Running proxy tests (NTLM authentication)"
+ echo ""
+
+ export GITTEST_REMOTE_PROXY_HOST="localhost:8090"
+ export GITTEST_REMOTE_PROXY_USER="foo"
+ export GITTEST_REMOTE_PROXY_PASS="bar"
+ run_test proxy
+ unset GITTEST_REMOTE_PROXY_HOST
+ unset GITTEST_REMOTE_PROXY_USER
+ unset GITTEST_REMOTE_PROXY_PASS
fi
if [ -z "$SKIP_SSH_TESTS" ]; then
diff --git a/deps/ntlmclient/CMakeLists.txt b/deps/ntlmclient/CMakeLists.txt
new file mode 100644
index 000000000..393257daf
--- /dev/null
+++ b/deps/ntlmclient/CMakeLists.txt
@@ -0,0 +1,18 @@
+FILE(GLOB SRC_NTLMCLIENT "ntlm.c" "unicode_builtin.c" "util.c")
+
+ADD_DEFINITIONS(-DNTLM_STATIC=1)
+
+IF (HTTPS_BACKEND STREQUAL "SecureTransport")
+ ADD_DEFINITIONS(-DCRYPT_COMMONCRYPTO)
+ SET(SRC_NTLMCLIENT_CRYPTO "crypt_commoncrypto.c")
+ELSEIF (HTTPS_BACKEND STREQUAL "OpenSSL")
+ ADD_DEFINITIONS(-DCRYPT_OPENSSL)
+ SET(SRC_NTLMCLIENT_CRYPTO "crypt_openssl.c")
+ELSEIF (HTTPS_BACKEND STREQUAL "mbedTLS")
+ ADD_DEFINITIONS(-DCRYPT_MBEDTLS)
+ SET(SRC_NTLMCLIENT_CRYPTO "crypt_mbedtls.c")
+ELSE ()
+ MESSAGE(FATAL_ERROR "Unable to use libgit2's HTTPS backend (${HTTPS_BACKEND}) for NTLM crypto")
+ENDIF()
+
+ADD_LIBRARY(ntlmclient OBJECT ${SRC_NTLMCLIENT} ${SRC_NTLMCLIENT_CRYPTO})
diff --git a/deps/ntlmclient/compat.h b/deps/ntlmclient/compat.h
new file mode 100644
index 000000000..efdf34514
--- /dev/null
+++ b/deps/ntlmclient/compat.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_COMPAT_H__
+#define PRIVATE_COMPAT_H__
+
+#if defined (_MSC_VER)
+ typedef unsigned char bool;
+# ifndef true
+# define true 1
+# endif
+# ifndef false
+# define false 0
+# endif
+#else
+# include <stdbool.h>
+#endif
+
+#ifdef __linux__
+# include <endian.h>
+# define htonll htobe64
+#endif
+
+#ifndef MIN
+# define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+#endif /* PRIVATE_COMPAT_H__ */
diff --git a/deps/ntlmclient/crypt.h b/deps/ntlmclient/crypt.h
new file mode 100644
index 000000000..48be39946
--- /dev/null
+++ b/deps/ntlmclient/crypt.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_CRYPT_COMMON_H__
+#define PRIVATE_CRYPT_COMMON_H__
+
+#if defined(CRYPT_OPENSSL)
+# include "crypt_openssl.h"
+#elif defined(CRYPT_MBEDTLS)
+# include "crypt_mbedtls.h"
+#elif defined(CRYPT_COMMONCRYPTO)
+# include "crypt_commoncrypto.h"
+#else
+# error "no crypto support"
+#endif
+
+#define CRYPT_DES_BLOCKSIZE 8
+#define CRYPT_MD4_DIGESTSIZE 16
+#define CRYPT_MD5_DIGESTSIZE 16
+
+typedef unsigned char ntlm_des_block[CRYPT_DES_BLOCKSIZE];
+
+extern bool ntlm_random_bytes(
+ ntlm_client *ntlm,
+ unsigned char *out,
+ size_t len);
+
+extern bool ntlm_des_encrypt(
+ ntlm_des_block *out,
+ ntlm_des_block *plaintext,
+ ntlm_des_block *key);
+
+extern bool ntlm_md4_digest(
+ unsigned char out[CRYPT_MD4_DIGESTSIZE],
+ const unsigned char *in,
+ size_t in_len);
+
+extern ntlm_hmac_ctx *ntlm_hmac_ctx_init(void);
+
+extern bool ntlm_hmac_ctx_reset(ntlm_hmac_ctx *ctx);
+
+extern bool ntlm_hmac_md5_init(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *key,
+ size_t key_len);
+
+extern bool ntlm_hmac_md5_update(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *data,
+ size_t data_len);
+
+extern bool ntlm_hmac_md5_final(
+ unsigned char *out,
+ size_t *out_len,
+ ntlm_hmac_ctx *ctx);
+
+extern void ntlm_hmac_ctx_free(ntlm_hmac_ctx *ctx);
+
+#endif /* PRIVATE_CRYPT_COMMON_H__ */
diff --git a/deps/ntlmclient/crypt_commoncrypto.c b/deps/ntlmclient/crypt_commoncrypto.c
new file mode 100644
index 000000000..54a0f097b
--- /dev/null
+++ b/deps/ntlmclient/crypt_commoncrypto.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <CommonCrypto/CommonCrypto.h>
+
+#include "ntlm.h"
+#include "crypt.h"
+
+bool ntlm_random_bytes(
+ ntlm_client *ntlm,
+ unsigned char *out,
+ size_t len)
+{
+ int fd, ret;
+ size_t total = 0;
+
+ if ((fd = open("/dev/urandom", O_RDONLY)) < 0) {
+ ntlm_client_set_errmsg(ntlm, strerror(errno));
+ return false;
+ }
+
+ while (total < len) {
+ if ((ret = read(fd, out, (len - total))) < 0) {
+ ntlm_client_set_errmsg(ntlm, strerror(errno));
+ return false;
+ } else if (ret == 0) {
+ ntlm_client_set_errmsg(ntlm, "unexpected eof on random device");
+ return false;
+ }
+
+ total += ret;
+ }
+
+ close(fd);
+ return true;
+}
+
+bool ntlm_des_encrypt(
+ ntlm_des_block *out,
+ ntlm_des_block *plaintext,
+ ntlm_des_block *key)
+{
+ size_t written;
+
+ CCCryptorStatus result = CCCrypt(kCCEncrypt,
+ kCCAlgorithmDES, kCCOptionECBMode,
+ key, sizeof(ntlm_des_block), NULL,
+ plaintext, sizeof(ntlm_des_block),
+ out, sizeof(ntlm_des_block), &written);
+
+ return (result == kCCSuccess) ? true : false;
+}
+
+bool ntlm_md4_digest(
+ unsigned char out[CRYPT_MD4_DIGESTSIZE],
+ const unsigned char *in,
+ size_t in_len)
+{
+ return !!CC_MD4(in, in_len, out);
+}
+
+ntlm_hmac_ctx *ntlm_hmac_ctx_init(void)
+{
+ return calloc(1, sizeof(ntlm_hmac_ctx));
+}
+
+bool ntlm_hmac_ctx_reset(ntlm_hmac_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(ntlm_hmac_ctx));
+ return true;
+}
+
+bool ntlm_hmac_md5_init(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *key,
+ size_t key_len)
+{
+ CCHmacInit(&ctx->native, kCCHmacAlgMD5, key, key_len);
+ return true;
+}
+
+bool ntlm_hmac_md5_update(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *data,
+ size_t data_len)
+{
+ CCHmacUpdate(&ctx->native, data, data_len);
+ return true;
+}
+
+bool ntlm_hmac_md5_final(
+ unsigned char *out,
+ size_t *out_len,
+ ntlm_hmac_ctx *ctx)
+{
+ if (*out_len < CRYPT_MD5_DIGESTSIZE)
+ return false;
+
+ CCHmacFinal(&ctx->native, out);
+
+ *out_len = CRYPT_MD5_DIGESTSIZE;
+ return true;
+}
+
+void ntlm_hmac_ctx_free(ntlm_hmac_ctx *ctx)
+{
+ free(ctx);
+}
diff --git a/deps/ntlmclient/crypt_commoncrypto.h b/deps/ntlmclient/crypt_commoncrypto.h
new file mode 100644
index 000000000..e4075c9f6
--- /dev/null
+++ b/deps/ntlmclient/crypt_commoncrypto.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_CRYPT_COMMONCRYPTO_H__
+#define PRIVATE_CRYPT_COMMONCRYPTO_H__
+
+#include <CommonCrypto/CommonCrypto.h>
+
+typedef struct {
+ CCHmacContext native;
+} ntlm_hmac_ctx;
+
+#endif /* PRIVATE_CRYPT_COMMONCRYPTO_H__ */
diff --git a/deps/ntlmclient/crypt_mbedtls.c b/deps/ntlmclient/crypt_mbedtls.c
new file mode 100644
index 000000000..bbab02d7d
--- /dev/null
+++ b/deps/ntlmclient/crypt_mbedtls.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/des.h"
+#include "mbedtls/entropy.h"
+#include "mbedtls/md4.h"
+
+#include "ntlm.h"
+#include "crypt.h"
+
+bool ntlm_random_bytes(
+ ntlm_client *ntlm,
+ unsigned char *out,
+ size_t len)
+{
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_context entropy;
+ bool ret = true;
+
+ const unsigned char personalization[] = {
+ 0xec, 0xb5, 0xd1, 0x0b, 0x8f, 0x15, 0x1f, 0xc2,
+ 0xe4, 0x8e, 0xec, 0x36, 0xf7, 0x0a, 0x45, 0x9a,
+ 0x1f, 0xe1, 0x35, 0x58, 0xb1, 0xcb, 0xfd, 0x8a,
+ 0x57, 0x5c, 0x75, 0x7d, 0x2f, 0xc9, 0x70, 0xac
+ };
+
+ mbedtls_ctr_drbg_init(&ctr_drbg);
+ mbedtls_entropy_init(&entropy);
+
+ if (mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
+ &entropy, personalization, sizeof(personalization)) ||
+ mbedtls_ctr_drbg_random(&ctr_drbg, out, len)) {
+ ntlm_client_set_errmsg(ntlm, "random generation failed");
+ ret = false;
+ }
+
+ mbedtls_entropy_free(&entropy);
+ mbedtls_ctr_drbg_free(&ctr_drbg);
+
+ return ret;
+}
+
+bool ntlm_des_encrypt(
+ ntlm_des_block *out,
+ ntlm_des_block *plaintext,
+ ntlm_des_block *key)
+{
+ mbedtls_des_context ctx;
+ bool success = false;
+
+ mbedtls_des_init(&ctx);
+
+ if (mbedtls_des_setkey_enc(&ctx, *key) ||
+ mbedtls_des_crypt_ecb(&ctx, *plaintext, *out))
+ goto done;
+
+ success = true;
+
+done:
+ mbedtls_des_free(&ctx);
+ return success;
+}
+
+bool ntlm_md4_digest(
+ unsigned char out[CRYPT_MD4_DIGESTSIZE],
+ const unsigned char *in,
+ size_t in_len)
+{
+ mbedtls_md4_context ctx;
+
+ mbedtls_md4_init(&ctx);
+ mbedtls_md4_starts(&ctx);
+ mbedtls_md4_update(&ctx, in, in_len);
+ mbedtls_md4_finish(&ctx, out);
+ mbedtls_md4_free(&ctx);
+
+ return true;
+}
+
+ntlm_hmac_ctx *ntlm_hmac_ctx_init(void)
+{
+ ntlm_hmac_ctx *ctx;
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
+
+ if ((ctx = calloc(1, sizeof(ntlm_hmac_ctx))) == NULL)
+ return NULL;
+
+ mbedtls_md_init(&ctx->mbed);
+
+ if (mbedtls_md_setup(&ctx->mbed, info, 1) != 0) {
+ free(ctx);
+ return false;
+ }
+
+ return ctx;
+}
+
+bool ntlm_hmac_ctx_reset(ntlm_hmac_ctx *ctx)
+{
+ return !mbedtls_md_hmac_reset(&ctx->mbed);
+}
+
+bool ntlm_hmac_md5_init(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *key,
+ size_t key_len)
+{
+ return !mbedtls_md_hmac_starts(&ctx->mbed, key, key_len);
+}
+
+bool ntlm_hmac_md5_update(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *in,
+ size_t in_len)
+{
+ return !mbedtls_md_hmac_update(&ctx->mbed, in, in_len);
+}
+
+bool ntlm_hmac_md5_final(
+ unsigned char *out,
+ size_t *out_len,
+ ntlm_hmac_ctx *ctx)
+{
+ if (*out_len < CRYPT_MD5_DIGESTSIZE)
+ return false;
+
+ return !mbedtls_md_hmac_finish(&ctx->mbed, out);
+}
+
+void ntlm_hmac_ctx_free(ntlm_hmac_ctx *ctx)
+{
+ if (ctx) {
+ mbedtls_md_free(&ctx->mbed);
+ free(ctx);
+ }
+}
diff --git a/deps/ntlmclient/crypt_mbedtls.h b/deps/ntlmclient/crypt_mbedtls.h
new file mode 100644
index 000000000..eb49a4596
--- /dev/null
+++ b/deps/ntlmclient/crypt_mbedtls.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_CRYPT_MBEDTLS_H__
+#define PRIVATE_CRYPT_MBEDTLS_H__
+
+#include "mbedtls/md.h"
+
+typedef struct {
+ mbedtls_md_context_t mbed;
+} ntlm_hmac_ctx;
+
+#endif /* PRIVATE_CRYPT_MBEDTLS_H__ */
diff --git a/deps/ntlmclient/crypt_openssl.c b/deps/ntlmclient/crypt_openssl.c
new file mode 100644
index 000000000..785be10e5
--- /dev/null
+++ b/deps/ntlmclient/crypt_openssl.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/rand.h>
+#include <openssl/des.h>
+#include <openssl/md4.h>
+#include <openssl/hmac.h>
+#include <openssl/err.h>
+
+#include "ntlm.h"
+#include "compat.h"
+#include "util.h"
+#include "crypt.h"
+
+bool ntlm_random_bytes(
+ ntlm_client *ntlm,
+ unsigned char *out,
+ size_t len)
+{
+ int rc = RAND_bytes(out, len);
+
+ if (rc != 1) {
+ ntlm_client_set_errmsg(ntlm, ERR_lib_error_string(ERR_get_error()));
+ return false;
+ }
+
+ return true;
+}
+
+bool ntlm_des_encrypt(
+ ntlm_des_block *out,
+ ntlm_des_block *plaintext,
+ ntlm_des_block *key)
+{
+ DES_key_schedule keysched;
+
+ memset(out, 0, sizeof(ntlm_des_block));
+
+ DES_set_key(key, &keysched);
+ DES_ecb_encrypt(plaintext, out, &keysched, DES_ENCRYPT);
+
+ return true;
+}
+
+bool ntlm_md4_digest(
+ unsigned char out[CRYPT_MD4_DIGESTSIZE],
+ const unsigned char *in,
+ size_t in_len)
+{
+ MD4(in, in_len, out);
+ return true;
+}
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static inline void HMAC_CTX_free(HMAC_CTX *ctx)
+{
+ if (ctx)
+ HMAC_CTX_cleanup(ctx);
+
+ free(ctx);
+}
+
+static inline int HMAC_CTX_reset(HMAC_CTX *ctx)
+{
+ HMAC_CTX_cleanup(ctx);
+ memzero(ctx, sizeof(HMAC_CTX));
+ return 1;
+}
+
+static inline HMAC_CTX *HMAC_CTX_new(void)
+{
+ return calloc(1, sizeof(HMAC_CTX));
+}
+#endif
+
+ntlm_hmac_ctx *ntlm_hmac_ctx_init(void)
+{
+ return HMAC_CTX_new();
+}
+
+bool ntlm_hmac_ctx_reset(ntlm_hmac_ctx *ctx)
+{
+ return HMAC_CTX_reset(ctx);
+}
+
+bool ntlm_hmac_md5_init(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *key,
+ size_t key_len)
+{
+ return HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL);
+}
+
+bool ntlm_hmac_md5_update(
+ ntlm_hmac_ctx *ctx,
+ const unsigned char *in,
+ size_t in_len)
+{
+ return HMAC_Update(ctx, in, in_len);
+}
+
+bool ntlm_hmac_md5_final(
+ unsigned char *out,
+ size_t *out_len,
+ ntlm_hmac_ctx *ctx)
+{
+ unsigned int len;
+
+ if (*out_len < CRYPT_MD5_DIGESTSIZE)
+ return false;
+
+ if (!HMAC_Final(ctx, out, &len))
+ return false;
+
+ *out_len = len;
+ return true;
+}
+
+void ntlm_hmac_ctx_free(ntlm_hmac_ctx *ctx)
+{
+ HMAC_CTX_free(ctx);
+}
diff --git a/deps/ntlmclient/crypt_openssl.h b/deps/ntlmclient/crypt_openssl.h
new file mode 100644
index 000000000..4195db9a5
--- /dev/null
+++ b/deps/ntlmclient/crypt_openssl.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_CRYPT_OPENSSL_H__
+#define PRIVATE_CRYPT_OPENSSL_H__
+
+#include <openssl/hmac.h>
+
+/* OpenSSL 1.1.0 uses opaque structs, we'll reuse these. */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+typedef struct hmac_ctx_st ntlm_hmac_ctx;
+#else
+# define ntlm_hmac_ctx HMAC_CTX
+#endif
+
+#endif /* PRIVATE_CRYPT_OPENSSL_H__ */
diff --git a/deps/ntlmclient/ntlm.c b/deps/ntlmclient/ntlm.c
new file mode 100644
index 000000000..9d0f3b8e3
--- /dev/null
+++ b/deps/ntlmclient/ntlm.c
@@ -0,0 +1,1420 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include "ntlm.h"
+#include "unicode.h"
+#include "utf8.h"
+#include "crypt.h"
+#include "compat.h"
+#include "util.h"
+
+unsigned char ntlm_client_signature[] = NTLM_SIGNATURE;
+
+static bool supports_unicode(ntlm_client *ntlm)
+{
+ return (ntlm->flags & NTLM_CLIENT_DISABLE_UNICODE) ?
+ false : true;
+}
+
+static inline bool increment_size(size_t *out, size_t incr)
+{
+ if (SIZE_MAX - *out < incr) {
+ *out = (size_t)-1;
+ return false;
+ }
+
+ *out = *out + incr;
+ return true;
+}
+
+ntlm_client *ntlm_client_init(ntlm_client_flags flags)
+{
+ ntlm_client *ntlm = NULL;
+
+ if ((ntlm = malloc(sizeof(ntlm_client))) == NULL)
+ return NULL;
+
+ memset(ntlm, 0, sizeof(ntlm_client));
+
+ ntlm->flags = flags;
+
+ if ((ntlm->hmac_ctx = ntlm_hmac_ctx_init()) == NULL ||
+ (ntlm->unicode_ctx = ntlm_unicode_ctx_init(ntlm)) == NULL) {
+ ntlm_hmac_ctx_free(ntlm->hmac_ctx);
+ ntlm_unicode_ctx_free(ntlm->unicode_ctx);
+ free(ntlm);
+ return NULL;
+ }
+
+ return ntlm;
+}
+
+void ntlm_client_set_errmsg(ntlm_client *ntlm, const char *errmsg)
+{
+ ntlm->state = NTLM_STATE_ERROR;
+ ntlm->errmsg = errmsg;
+}
+
+const char *ntlm_client_errmsg(ntlm_client *ntlm)
+{
+ assert(ntlm);
+ return ntlm->errmsg ? ntlm->errmsg : "no error";
+}
+
+int ntlm_client_set_version(
+ ntlm_client *ntlm,
+ uint8_t major,
+ uint8_t minor,
+ uint16_t build)
+{
+ assert(ntlm);
+
+ ntlm->host_version.major = major;
+ ntlm->host_version.minor = minor;
+ ntlm->host_version.build = build;
+ ntlm->host_version.reserved = 0x0f000000;
+
+ ntlm->flags |= NTLM_ENABLE_HOSTVERSION;
+
+ return 0;
+}
+
+int ntlm_client_set_hostname(
+ ntlm_client *ntlm,
+ const char *hostname,
+ const char *domain)
+{
+ assert(ntlm);
+
+ free(ntlm->hostname);
+ free(ntlm->hostdomain);
+ free(ntlm->hostname_utf16);
+
+ ntlm->hostname = NULL;
+ ntlm->hostdomain = NULL;
+ ntlm->hostname_utf16 = NULL;
+
+ if (hostname && (ntlm->hostname = strdup(hostname)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ if (domain && (ntlm->hostdomain = strdup(domain)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ if (hostname && supports_unicode(ntlm) && !ntlm_unicode_utf8_to_16(
+ &ntlm->hostname_utf16,
+ &ntlm->hostname_utf16_len,
+ ntlm->unicode_ctx,
+ hostname,
+ strlen(hostname)))
+ return -1;
+
+ return 0;
+}
+
+static void free_credentials(ntlm_client *ntlm)
+{
+ if (ntlm->password)
+ memzero(ntlm->password, strlen(ntlm->password));
+
+ if (ntlm->password_utf16)
+ memzero(ntlm->password_utf16, ntlm->password_utf16_len);
+
+ free(ntlm->username);
+ free(ntlm->username_upper);
+ free(ntlm->userdomain);
+ free(ntlm->password);
+
+ free(ntlm->username_utf16);
+ free(ntlm->username_upper_utf16);
+ free(ntlm->userdomain_utf16);
+ free(ntlm->password_utf16);
+
+ ntlm->username = NULL;
+ ntlm->username_upper = NULL;
+ ntlm->userdomain = NULL;
+ ntlm->password = NULL;
+
+ ntlm->username_utf16 = NULL;
+ ntlm->username_upper_utf16 = NULL;
+ ntlm->userdomain_utf16 = NULL;
+ ntlm->password_utf16 = NULL;
+}
+
+int ntlm_client_set_credentials(
+ ntlm_client *ntlm,
+ const char *username,
+ const char *domain,
+ const char *password)
+{
+ assert(ntlm);
+
+ free_credentials(ntlm);
+
+ if ((username && (ntlm->username = strdup(username)) == NULL) ||
+ (domain && (ntlm->userdomain = strdup(domain)) == NULL) ||
+ (password && (ntlm->password = strdup(password)) == NULL)) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ if (username && supports_unicode(ntlm)) {
+ if ((ntlm->username_upper = strdup(username)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+ utf8upr(ntlm->username_upper);
+
+ if (!ntlm_unicode_utf8_to_16(
+ &ntlm->username_utf16,
+ &ntlm->username_utf16_len,
+ ntlm->unicode_ctx,
+ ntlm->username,
+ strlen(ntlm->username)))
+ return -1;
+
+ if (!ntlm_unicode_utf8_to_16(
+ &ntlm->username_upper_utf16,
+ &ntlm->username_upper_utf16_len,
+ ntlm->unicode_ctx,
+ ntlm->username_upper,
+ strlen(ntlm->username_upper)))
+ return -1;
+ }
+
+ if (domain && supports_unicode(ntlm) && !ntlm_unicode_utf8_to_16(
+ &ntlm->userdomain_utf16,
+ &ntlm->userdomain_utf16_len,
+ ntlm->unicode_ctx,
+ ntlm->userdomain,
+ strlen(ntlm->userdomain)))
+ return -1;
+
+ return 0;
+}
+
+int ntlm_client_set_target(ntlm_client *ntlm, const char *target)
+{
+ assert(ntlm);
+
+ free(ntlm->target);
+ free(ntlm->target_utf16);
+
+ ntlm->target = NULL;
+ ntlm->target_utf16 = NULL;
+
+ if (target) {
+ if ((ntlm->target = strdup(target)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ if (supports_unicode(ntlm) && !ntlm_unicode_utf8_to_16(
+ &ntlm->target_utf16,
+ &ntlm->target_utf16_len,
+ ntlm->unicode_ctx,
+ ntlm->target,
+ strlen(ntlm->target)))
+ return -1;
+ }
+
+ return 0;
+}
+
+int ntlm_client_set_nonce(ntlm_client *ntlm, uint64_t nonce)
+{
+ assert(ntlm);
+ ntlm->nonce = nonce;
+ return 0;
+}
+
+int ntlm_client_set_timestamp(ntlm_client *ntlm, uint64_t timestamp)
+{
+ assert(ntlm);
+ ntlm->timestamp = timestamp;
+ return 0;
+}
+
+static inline bool write_buf(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ const unsigned char *buf,
+ size_t len)
+{
+ if (out->len - out->pos < len) {
+ ntlm_client_set_errmsg(ntlm, "out of buffer space");
+ return false;
+ }
+
+ memcpy(&out->buf[out->pos], buf, len);
+ out->pos += len;
+ return true;
+}
+
+static inline bool write_byte(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ uint8_t value)
+{
+ if (out->len - out->pos < 1) {
+ ntlm_client_set_errmsg(ntlm, "out of buffer space");
+ return false;
+ }
+
+ out->buf[out->pos++] = value;
+ return true;
+}
+
+static inline bool write_int16(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ uint16_t value)
+{
+ if (out->len - out->pos < 2) {
+ ntlm_client_set_errmsg(ntlm, "out of buffer space");
+ return false;
+ }
+
+ out->buf[out->pos++] = (value & 0x000000ff);
+ out->buf[out->pos++] = (value & 0x0000ff00) >> 8;
+ return true;
+}
+
+static inline bool write_int32(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ uint32_t value)
+{
+ if (out->len - out->pos < 2) {
+ ntlm_client_set_errmsg(ntlm, "out of buffer space");
+ return false;
+ }
+
+ out->buf[out->pos++] = (value & 0x000000ff);
+ out->buf[out->pos++] = (value & 0x0000ff00) >> 8;
+ out->buf[out->pos++] = (value & 0x00ff0000) >> 16;
+ out->buf[out->pos++] = (value & 0xff000000) >> 24;
+ return true;
+}
+
+static inline bool write_version(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ ntlm_version *version)
+{
+ return write_byte(ntlm, out, version->major) &&
+ write_byte(ntlm, out, version->minor) &&
+ write_int16(ntlm, out, version->build) &&
+ write_int32(ntlm, out, version->reserved);
+}
+
+static inline bool write_bufinfo(
+ ntlm_client *ntlm,
+ ntlm_buf *out,
+ size_t len,
+ size_t offset)
+{
+ if (len > UINT16_MAX) {
+ ntlm_client_set_errmsg(ntlm, "invalid string, too long");
+ return false;
+ }
+
+ if (offset > UINT32_MAX) {
+ ntlm_client_set_errmsg(ntlm, "invalid string, invalid offset");
+ return false;
+ }
+
+ return write_int16(ntlm, out, (uint16_t)len) &&
+ write_int16(ntlm, out, (uint16_t)len) &&
+ write_int32(ntlm, out, (uint32_t)offset);
+}
+
+static inline bool read_buf(
+ unsigned char *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message,
+ size_t len)
+{
+ if (message->len - message->pos < len) {
+ ntlm_client_set_errmsg(ntlm, "truncated message");
+ return false;
+ }
+
+ memcpy(out, &message->buf[message->pos], len);
+ message->pos += len;
+
+ return true;
+}
+
+static inline bool read_byte(
+ uint8_t *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ if (message->len - message->pos < 1) {
+ ntlm_client_set_errmsg(ntlm, "truncated message");
+ return false;
+ }
+
+ *out = message->buf[message->pos++];
+ return true;
+}
+
+static inline bool read_int16(
+ uint16_t *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ if (message->len - message->pos < 2) {
+ ntlm_client_set_errmsg(ntlm, "truncated message");
+ return false;
+ }
+
+ *out =
+ ((message->buf[message->pos] & 0xff)) |
+ ((message->buf[message->pos+1] & 0xff) << 8);
+
+ message->pos += 2;
+ return true;
+}
+
+static inline bool read_int32(
+ uint32_t *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ if (message->len - message->pos < 4) {
+ ntlm_client_set_errmsg(ntlm, "truncated message");
+ return false;
+ }
+
+ *out =
+ ((message->buf[message->pos] & 0xff)) |
+ ((message->buf[message->pos+1] & 0xff) << 8) |
+ ((message->buf[message->pos+2] & 0xff) << 16) |
+ ((message->buf[message->pos+3] & 0xff) << 24);
+
+ message->pos += 4;
+ return true;
+}
+
+static inline bool read_int64(
+ uint64_t *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ if (message->len - message->pos < 8) {
+ ntlm_client_set_errmsg(ntlm, "truncated message");
+ return false;
+ }
+
+ *out =
+ ((uint64_t)(message->buf[message->pos] & 0xff)) |
+ ((uint64_t)(message->buf[message->pos+1] & 0xff) << 8) |
+ ((uint64_t)(message->buf[message->pos+2] & 0xff) << 16) |
+ ((uint64_t)(message->buf[message->pos+3] & 0xff) << 24) |
+ ((uint64_t)(message->buf[message->pos+4] & 0xff) << 32) |
+ ((uint64_t)(message->buf[message->pos+5] & 0xff) << 40) |
+ ((uint64_t)(message->buf[message->pos+6] & 0xff) << 48) |
+ ((uint64_t)(message->buf[message->pos+7] & 0xff) << 56);
+
+ message->pos += 8;
+ return true;
+}
+
+static inline bool read_version(
+ ntlm_version *out,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ return read_byte(&out->major, ntlm, message) &&
+ read_byte(&out->minor, ntlm, message) &&
+ read_int16(&out->build, ntlm, message) &&
+ read_int32(&out->reserved, ntlm, message);
+}
+
+static inline bool read_bufinfo(
+ uint16_t *out_len,
+ uint32_t *out_offset,
+ ntlm_client *ntlm,
+ ntlm_buf *message)
+{
+ uint16_t allocated;
+
+ return read_int16(out_len, ntlm, message) &&
+ read_int16(&allocated, ntlm, message) &&
+ read_int32(out_offset, ntlm, message);
+}
+
+static inline bool read_string_unicode(
+ char **out,
+ ntlm_client *ntlm,
+ ntlm_buf *message,
+ uint8_t string_len)
+{
+ size_t out_len;
+ int ret = ntlm_unicode_utf16_to_8(out,
+ &out_len,
+ ntlm->unicode_ctx,
+ (char *)&message->buf[message->pos],
+ string_len);
+
+ message->pos += string_len;
+
+ return ret;
+}
+
+static inline bool read_string_ascii(
+ char **out,
+ ntlm_client *ntlm,
+ ntlm_buf *message,
+ uint8_t string_len)
+{
+ char *str;
+
+ if ((str = malloc(string_len + 1)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return false;
+ }
+
+ memcpy(str, &message->buf[message->pos], string_len);
+ str[string_len] = '\0';
+
+ message->pos += string_len;
+
+ *out = str;
+ return true;
+}
+
+static inline bool read_string(
+ char **out,
+ ntlm_client *ntlm,
+ ntlm_buf *message,
+ uint8_t string_len,
+ bool unicode)
+{
+ if (unicode)
+ return read_string_unicode(out, ntlm, message, string_len);
+ else
+ return read_string_ascii(out, ntlm, message, string_len);
+}
+
+static inline bool read_target_info(
+ char **server_out,
+ char **domain_out,
+ char **server_dns_out,
+ char **domain_dns_out,
+ ntlm_client *ntlm,
+ ntlm_buf *message,
+ bool unicode)
+{
+ uint16_t block_type, block_len;
+ bool done = false;
+
+ *server_out = NULL;
+ *domain_out = NULL;
+ *server_dns_out = NULL;
+ *domain_dns_out = NULL;
+
+ while (!done && (message->len - message->pos) >= 4) {
+ if (!read_int16(&block_type, ntlm, message) ||
+ !read_int16(&block_len, ntlm, message)) {
+ ntlm_client_set_errmsg(ntlm, "truncated target info block");
+ return false;
+ }
+
+ if (!block_type && block_len) {
+ ntlm_client_set_errmsg(ntlm, "invalid target info block");
+ return -1;
+ }
+
+ switch (block_type) {
+ case NTLM_TARGET_INFO_DOMAIN:
+ if (!read_string(domain_out, ntlm, message, block_len, unicode))
+ return -1;
+ break;
+ case NTLM_TARGET_INFO_SERVER:
+ if (!read_string(server_out, ntlm, message, block_len, unicode))
+ return -1;
+ break;
+ case NTLM_TARGET_INFO_DOMAIN_DNS:
+ if (!read_string(domain_dns_out, ntlm, message, block_len, unicode))
+ return -1;
+ break;
+ case NTLM_TARGET_INFO_SERVER_DNS:
+ if (!read_string(server_dns_out, ntlm, message, block_len, unicode))
+ return -1;
+ break;
+ case NTLM_TARGET_INFO_END:
+ done = true;
+ break;
+ default:
+ ntlm_client_set_errmsg(ntlm, "unknown target info block type");
+ return -1;
+ }
+ }
+
+ if (message->len != message->pos) {
+ ntlm_client_set_errmsg(ntlm,
+ "invalid extra data in target info section");
+ return false;
+ }
+
+ return true;
+}
+
+int ntlm_client_negotiate(
+ const unsigned char **out,
+ size_t *out_len,
+ ntlm_client *ntlm)
+{
+ size_t hostname_len, hostname_offset, domain_len, domain_offset;
+ uint32_t flags = 0;
+
+ assert(out && out_len && ntlm);
+
+ *out = NULL;
+ *out_len = 0;
+
+ if (ntlm->state != NTLM_STATE_NEGOTIATE) {
+ ntlm_client_set_errmsg(ntlm, "ntlm handle in invalid state");
+ return -1;
+ }
+
+ flags |= NTLM_NEGOTIATE_OEM;
+
+ if (supports_unicode(ntlm))
+ flags |= NTLM_NEGOTIATE_UNICODE;
+
+ if (!(ntlm->flags & NTLM_CLIENT_DISABLE_NTLM2) ||
+ (ntlm->flags & NTLM_CLIENT_ENABLE_NTLM))
+ flags |= NTLM_NEGOTIATE_NTLM;
+
+ if (!(ntlm->flags & NTLM_CLIENT_DISABLE_REQUEST_TARGET))
+ flags |= NTLM_NEGOTIATE_REQUEST_TARGET;
+
+ hostname_len = ntlm->hostname ? strlen(ntlm->hostname) : 0;
+ domain_len = ntlm->hostdomain ? strlen(ntlm->hostdomain) : 0;
+
+ /* Minimum header size */
+ ntlm->negotiate.len = 16;
+
+ /* Include space for security buffer descriptors */
+ if (domain_len)
+ increment_size(&ntlm->negotiate.len, 8);
+
+ if (hostname_len)
+ increment_size(&ntlm->negotiate.len, 8);
+
+ if (ntlm->flags & NTLM_ENABLE_HOSTVERSION)
+ increment_size(&ntlm->negotiate.len, 8);
+
+ /* Location of security buffers */
+ if (hostname_len) {
+ flags |= NTLM_NEGOTIATE_WORKSTATION_SUPPLIED;
+ hostname_offset = ntlm->negotiate.len;
+ increment_size(&ntlm->negotiate.len, hostname_len);
+ }
+
+ if (domain_len) {
+ flags |= NTLM_NEGOTIATE_DOMAIN_SUPPLIED;
+ domain_offset = ntlm->negotiate.len;
+ increment_size(&ntlm->negotiate.len, domain_len);
+ }
+
+ if (ntlm->negotiate.len == (size_t)-1) {
+ ntlm_client_set_errmsg(ntlm, "message too large");
+ return -1;
+ }
+
+ if ((ntlm->negotiate.buf = malloc(ntlm->negotiate.len)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ memset(ntlm->negotiate.buf, 0, ntlm->negotiate.len);
+
+ if (!write_buf(ntlm, &ntlm->negotiate,
+ ntlm_client_signature, sizeof(ntlm_client_signature)) ||
+ !write_int32(ntlm, &ntlm->negotiate, 1) ||
+ !write_int32(ntlm, &ntlm->negotiate, flags))
+ return -1;
+
+ /* Domain information */
+ if (domain_len > 0 &&
+ !write_bufinfo(ntlm, &ntlm->negotiate, domain_len, domain_offset))
+ return -1;
+
+ /* Workstation information */
+ if (hostname_len > 0 &&
+ !write_bufinfo(ntlm, &ntlm->negotiate, hostname_len, hostname_offset))
+ return -1;
+
+ /* Version number */
+ if (!!(ntlm->flags & NTLM_ENABLE_HOSTVERSION) &&
+ !write_version(ntlm, &ntlm->negotiate, &ntlm->host_version))
+ return -1;
+
+ if (hostname_len > 0) {
+ assert(hostname_offset == ntlm->negotiate.pos);
+ if (!write_buf(ntlm, &ntlm->negotiate,
+ (const unsigned char *)ntlm->hostname, hostname_len))
+ return -1;
+ }
+
+ if (domain_len > 0) {
+ assert(domain_offset == ntlm->negotiate.pos);
+ if (!write_buf(ntlm, &ntlm->negotiate,
+ (const unsigned char *)ntlm->hostdomain, domain_len))
+ return -1;
+ }
+
+ assert(ntlm->negotiate.pos == ntlm->negotiate.len);
+
+ ntlm->state = NTLM_STATE_CHALLENGE;
+
+ *out = ntlm->negotiate.buf;
+ *out_len = ntlm->negotiate.len;
+
+ return 0;
+}
+
+int ntlm_client_set_challenge(
+ ntlm_client *ntlm,
+ const unsigned char *challenge_msg,
+ size_t challenge_msg_len)
+{
+ unsigned char signature[8];
+ ntlm_buf challenge;
+ uint32_t type_indicator, header_end;
+ uint16_t name_len, info_len = 0;
+ uint32_t name_offset, info_offset = 0;
+ bool unicode, has_target_info = false;
+
+ assert(ntlm && (challenge_msg || !challenge_msg_len));
+
+ if (ntlm->state != NTLM_STATE_NEGOTIATE &&
+ ntlm->state != NTLM_STATE_CHALLENGE) {
+ ntlm_client_set_errmsg(ntlm, "ntlm handle in invalid state");
+ return -1;
+ }
+
+ challenge.buf = (unsigned char *)challenge_msg;
+ challenge.len = challenge_msg_len;
+ challenge.pos = 0;
+
+ if (!read_buf(signature, ntlm, &challenge, 8) ||
+ !read_int32(&type_indicator, ntlm, &challenge) ||
+ !read_bufinfo(&name_len, &name_offset, ntlm, &challenge) ||
+ !read_int32(&ntlm->challenge.flags, ntlm, &challenge) ||
+ !read_int64(&ntlm->challenge.nonce, ntlm, &challenge))
+ return -1;
+
+ if (memcmp(signature,
+ ntlm_client_signature, sizeof(ntlm_client_signature)) != 0) {
+ ntlm_client_set_errmsg(ntlm, "invalid message signature");
+ return -1;
+ }
+
+ if (type_indicator != 2) {
+ ntlm_client_set_errmsg(ntlm, "invalid message indicator");
+ return -1;
+ }
+
+ /*
+ * If there's additional space before the data section, that's the
+ * target information description section.
+ */
+ header_end = challenge.len;
+
+ if (name_offset && name_offset < header_end)
+ header_end = name_offset;
+
+ if ((header_end - challenge.pos) >= 16) {
+ has_target_info = true;
+ }
+
+ if (!has_target_info &&
+ (ntlm->challenge.flags & NTLM_NEGOTIATE_TARGET_INFO)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; expected target info");
+ return -1;
+ }
+
+ /*
+ * If there's a target info section then advanced over the reserved
+ * space and read the target information.
+ */
+ if (has_target_info) {
+ uint64_t reserved;
+
+ if (!read_int64(&reserved, ntlm, &challenge)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; expected reserved space");
+ return -1;
+ }
+
+ if (reserved != 0) {
+ ntlm_client_set_errmsg(ntlm,
+ "invalid message; expected reserved space to be empty");
+ return -1;
+ }
+
+ if (!read_bufinfo(&info_len, &info_offset, ntlm, &challenge)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; expected target info");
+ return -1;
+ }
+ }
+
+ unicode = !!(ntlm->challenge.flags & NTLM_NEGOTIATE_UNICODE);
+
+ /*
+ * If there's still additional space before the data section,
+ * that's the server's version information.
+ */
+ if (info_offset && info_offset < header_end)
+ header_end = info_offset;
+
+ if (ntlm->challenge.flags & NTLM_NEGOTIATE_VERSION) {
+ if ((header_end - challenge.pos) != sizeof(ntlm_version) ||
+ !read_version(&ntlm->challenge.target_version,
+ ntlm, &challenge)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; expected version");
+ return -1;
+ }
+ }
+
+ /* validate data section */
+ if ((name_offset && name_offset < challenge.pos) ||
+ challenge.len < name_len ||
+ (challenge.len - name_len) < name_offset) {
+ ntlm_client_set_errmsg(ntlm,
+ "invalid message; invalid target name buffer");
+ return -1;
+ }
+ if ((info_offset && info_offset < challenge.pos) ||
+ challenge.len < info_len ||
+ (challenge.len - info_len) < info_offset) {
+ ntlm_client_set_errmsg(ntlm,
+ "invalid message; invalid target info buffer");
+ return -1;
+ }
+
+ /* advance to the data section */
+ if (name_len && name_offset) {
+ challenge.pos = name_offset;
+
+ if (!read_string(&ntlm->challenge.target,
+ ntlm, &challenge, name_len, unicode)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; truncated target name");
+ return -1;
+ }
+ }
+
+ if (info_len && info_offset) {
+ ntlm_buf info_buf;
+
+ challenge.pos = info_offset;
+
+ /* create a copy of the target info; we need the literal data */
+ if ((ntlm->challenge.target_info = malloc(info_len)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ if (!read_buf(ntlm->challenge.target_info,
+ ntlm, &challenge, info_len)) {
+ ntlm_client_set_errmsg(ntlm,
+ "truncated message; truncated target info");
+ return -1;
+ }
+
+ info_buf.buf = ntlm->challenge.target_info;
+ info_buf.pos = 0;
+ info_buf.len = info_len;
+
+ /* then set up the target info and parse it */
+ if (!read_target_info(&ntlm->challenge.target_server,
+ &ntlm->challenge.target_domain,
+ &ntlm->challenge.target_server_dns,
+ &ntlm->challenge.target_domain_dns,
+ ntlm, &info_buf, unicode))
+ return -1;
+
+ ntlm->challenge.target_info_len = info_len;
+ }
+
+ ntlm->state = NTLM_STATE_RESPONSE;
+
+ return 0;
+}
+
+uint64_t ntlm_client_challenge_nonce(ntlm_client *ntlm)
+{
+ return ntlm->challenge.nonce;
+}
+
+const char *ntlm_client_target(ntlm_client *ntlm)
+{
+ return ntlm->challenge.target;
+}
+
+const char *ntlm_client_target_server(ntlm_client *ntlm)
+{
+ return ntlm->challenge.target_server;
+}
+
+const char *ntlm_client_target_domain(ntlm_client *ntlm)
+{
+ return ntlm->challenge.target_domain;
+}
+
+const char *ntlm_client_target_server_dns(ntlm_client *ntlm)
+{
+ return ntlm->challenge.target_server_dns;
+}
+
+const char *ntlm_client_target_domain_dns(ntlm_client *ntlm)
+{
+ return ntlm->challenge.target_domain_dns;
+}
+
+#define EVEN_PARITY(a) \
+ (!!((a) & 0x01ll) ^ !!((a) & 0x02ll) ^ \
+ !!((a) & 0x04ll) ^ !!((a) & 0x08ll) ^ \
+ !!((a) & 0x10ll) ^ !!((a) & 0x20ll) ^ \
+ !!((a) & 0x40ll) ^ !!((a) & 0x80ll))
+
+static void generate_odd_parity(ntlm_des_block *block)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(ntlm_des_block); i++)
+ (*block)[i] |= (1 ^ EVEN_PARITY((*block)[i]));
+}
+
+static void des_key_from_password(
+ ntlm_des_block *out,
+ const unsigned char *plaintext,
+ size_t plaintext_len)
+{
+ size_t i;
+
+ plaintext_len = MIN(plaintext_len, 7);
+
+ memset(*out, 0, sizeof(ntlm_des_block));
+
+ for (i = 0; i < plaintext_len; i++) {
+ size_t j = (7 - i);
+ uint8_t mask = (0xff >> j);
+
+ (*out)[i] |= ((plaintext[i] & (0xff - mask)) >> i);
+ (*out)[i+1] |= ((plaintext[i] & mask) << j);
+ }
+
+ generate_odd_parity(out);
+}
+
+static inline bool generate_lm_hash(
+ ntlm_des_block out[2],
+ const char *password)
+{
+ /* LM encrypts this known plaintext using the password as a key */
+ ntlm_des_block plaintext = NTLM_LM_PLAINTEXT;
+ ntlm_des_block keystr1, keystr2;
+ size_t keystr1_len, keystr2_len;
+ ntlm_des_block key1, key2;
+ size_t password_len, i;
+
+ /* Copy the first 14 characters of the password, uppercased */
+ memset(&keystr1, 0, sizeof(keystr1));
+ memset(&keystr2, 0, sizeof(keystr2));
+
+ password_len = password ? strlen(password) : 0;
+
+ /* Split the password into two 7 byte chunks */
+ keystr1_len = MIN(7, password_len);
+ keystr2_len = (password_len > 7) ? MIN(14, password_len) - 7 : 0;
+
+ for (i = 0; i < keystr1_len; i++)
+ keystr1[i] = (unsigned char)toupper(password[i]);
+ for (i = 0; i < keystr2_len; i++)
+ keystr2[i] = (unsigned char)toupper(password[i+7]);
+
+ /* DES encrypt the LM constant using the password as the key */
+ des_key_from_password(&key1, keystr1, keystr1_len);
+ des_key_from_password(&key2, keystr2, keystr2_len);
+
+ return ntlm_des_encrypt(&out[0], &plaintext, &key1) &&
+ ntlm_des_encrypt(&out[1], &plaintext, &key2);
+}
+
+static void des_keys_from_lm_hash(ntlm_des_block out[3], ntlm_des_block lm_hash[2])
+{
+ ntlm_des_block split[3];
+
+ memcpy(&split[0][0], &lm_hash[0][0], 7);
+
+ memcpy(&split[1][0], &lm_hash[0][7], 1);
+ memcpy(&split[1][1], &lm_hash[1][0], 6);
+
+ memcpy(&split[2][0], &lm_hash[1][6], 2);
+
+ des_key_from_password(&out[0], split[0], 7);
+ des_key_from_password(&out[1], split[1], 7);
+ des_key_from_password(&out[2], split[2], 2);
+}
+
+static bool generate_lm_response(ntlm_client *ntlm)
+{
+ ntlm_des_block lm_hash[2], key[3], lm_response[3];
+ ntlm_des_block *challenge = (ntlm_des_block *)&ntlm->challenge.nonce;
+
+ /* Generate the LM hash from the password */
+ if (!generate_lm_hash(lm_hash, ntlm->password))
+ return false;
+
+ /* Convert that LM hash to three DES keys */
+ des_keys_from_lm_hash(key, lm_hash);
+
+ /* Finally, encrypt the challenge with each of these keys */
+ if (!ntlm_des_encrypt(&lm_response[0], challenge, &key[0]) ||
+ !ntlm_des_encrypt(&lm_response[1], challenge, &key[1]) ||
+ !ntlm_des_encrypt(&lm_response[2], challenge, &key[2]))
+ return false;
+
+ memcpy(&ntlm->lm_response[0], lm_response[0], 8);
+ memcpy(&ntlm->lm_response[8], lm_response[1], 8);
+ memcpy(&ntlm->lm_response[16], lm_response[2], 8);
+
+ ntlm->lm_response_len = sizeof(ntlm->lm_response);
+
+ return true;
+}
+
+static bool generate_ntlm_hash(
+ unsigned char out[NTLM_NTLM_HASH_LEN], ntlm_client *ntlm)
+{
+ /* Generate the LM hash from the (Unicode) password */
+ if (ntlm->password && !ntlm_unicode_utf8_to_16(
+ &ntlm->password_utf16,
+ &ntlm->password_utf16_len,
+ ntlm->unicode_ctx,
+ ntlm->password,
+ strlen(ntlm->password)))
+ return false;
+
+ return ntlm_md4_digest(out,
+ (const unsigned char *)ntlm->password_utf16,
+ ntlm->password_utf16_len);
+}
+
+static bool generate_ntlm_response(ntlm_client *ntlm)
+{
+ unsigned char ntlm_hash[NTLM_NTLM_HASH_LEN] = {0};
+ ntlm_des_block key[3], ntlm_response[3];
+ ntlm_des_block *challenge =
+ (ntlm_des_block *)&ntlm->challenge.nonce;
+
+ if (!generate_ntlm_hash(ntlm_hash, ntlm))
+ return false;
+
+ /* Convert that LM hash to three DES keys */
+ des_key_from_password(&key[0], &ntlm_hash[0], 7);
+ des_key_from_password(&key[1], &ntlm_hash[7], 7);
+ des_key_from_password(&key[2], &ntlm_hash[14], 2);
+
+ /* Finally, encrypt the challenge with each of these keys */
+ if (!ntlm_des_encrypt(&ntlm_response[0], challenge, &key[0]) ||
+ !ntlm_des_encrypt(&ntlm_response[1], challenge, &key[1]) ||
+ !ntlm_des_encrypt(&ntlm_response[2], challenge, &key[2]))
+ return false;
+
+ memcpy(&ntlm->ntlm_response[0], ntlm_response[0], 8);
+ memcpy(&ntlm->ntlm_response[8], ntlm_response[1], 8);
+ memcpy(&ntlm->ntlm_response[16], ntlm_response[2], 8);
+
+ ntlm->ntlm_response_len = sizeof(ntlm->ntlm_response);
+ return true;
+}
+
+static bool generate_ntlm2_hash(
+ unsigned char out[NTLM_NTLM2_HASH_LEN], ntlm_client *ntlm)
+{
+ unsigned char ntlm_hash[NTLM_NTLM_HASH_LEN] = {0};
+ const unsigned char *username = NULL, *target = NULL;
+ size_t username_len = 0, target_len = 0, out_len = NTLM_NTLM2_HASH_LEN;
+
+ if (!generate_ntlm_hash(ntlm_hash, ntlm))
+ return false;
+
+ if (ntlm->username_upper_utf16) {
+ username = (const unsigned char *)ntlm->username_upper_utf16;
+ username_len = ntlm->username_upper_utf16_len;
+ }
+
+ if (ntlm->target_utf16) {
+ target = (const unsigned char *)ntlm->target_utf16;
+ target_len = ntlm->target_utf16_len;
+ }
+
+ if (!ntlm_hmac_ctx_reset(ntlm->hmac_ctx) ||
+ !ntlm_hmac_md5_init(ntlm->hmac_ctx, ntlm_hash, sizeof(ntlm_hash)) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx, username, username_len) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx, target, target_len) ||
+ !ntlm_hmac_md5_final(out, &out_len, ntlm->hmac_ctx)) {
+ ntlm_client_set_errmsg(ntlm, "failed to create HMAC-MD5");
+ return false;
+ }
+
+ assert(out_len == NTLM_NTLM2_HASH_LEN);
+ return true;
+}
+
+static bool generate_ntlm2_challengehash(
+ unsigned char out[16],
+ ntlm_client *ntlm,
+ unsigned char ntlm2_hash[NTLM_NTLM2_HASH_LEN],
+ const unsigned char *blob,
+ size_t blob_len)
+{
+ size_t out_len = 16;
+
+ if (!ntlm_hmac_ctx_reset(ntlm->hmac_ctx) ||
+ !ntlm_hmac_md5_init(ntlm->hmac_ctx,
+ ntlm2_hash, NTLM_NTLM2_HASH_LEN) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx,
+ (const unsigned char *)&ntlm->challenge.nonce, 8) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx, blob, blob_len) ||
+ !ntlm_hmac_md5_final(out, &out_len, ntlm->hmac_ctx)) {
+ ntlm_client_set_errmsg(ntlm, "failed to create HMAC-MD5");
+ return false;
+ }
+
+ assert(out_len == 16);
+ return true;
+}
+
+static bool generate_lm2_response(ntlm_client *ntlm,
+ unsigned char ntlm2_hash[NTLM_NTLM2_HASH_LEN])
+{
+ unsigned char lm2_challengehash[16];
+ size_t lm2_len = 16;
+ uint64_t local_nonce;
+
+ local_nonce = htonll(ntlm->nonce);
+
+ if (!ntlm_hmac_ctx_reset(ntlm->hmac_ctx) ||
+ !ntlm_hmac_md5_init(ntlm->hmac_ctx,
+ ntlm2_hash, NTLM_NTLM2_HASH_LEN) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx,
+ (const unsigned char *)&ntlm->challenge.nonce, 8) ||
+ !ntlm_hmac_md5_update(ntlm->hmac_ctx,
+ (const unsigned char *)&local_nonce, 8) ||
+ !ntlm_hmac_md5_final(lm2_challengehash, &lm2_len, ntlm->hmac_ctx)) {
+ ntlm_client_set_errmsg(ntlm, "failed to create HMAC-MD5");
+ return false;
+ }
+
+ assert(lm2_len == 16);
+
+ memcpy(&ntlm->lm_response[0], lm2_challengehash, 16);
+ memcpy(&ntlm->lm_response[16], &local_nonce, 8);
+
+ ntlm->lm_response_len = 24;
+ return true;
+}
+
+static bool generate_timestamp(ntlm_client *ntlm)
+{
+ if (!ntlm->timestamp)
+ ntlm->timestamp = (time(NULL) + 11644473600) * 10000000;
+
+ return true;
+}
+
+static bool generate_nonce(ntlm_client *ntlm)
+{
+ unsigned char buf[8];
+
+ if (ntlm->nonce)
+ return true;
+
+ if (!ntlm_random_bytes(ntlm, buf, 8))
+ return false;
+
+ memcpy(&ntlm->nonce, buf, sizeof(uint64_t));
+ return true;
+}
+
+static bool generate_ntlm2_response(ntlm_client *ntlm)
+{
+ size_t blob_len, ntlm2_response_len;
+ uint32_t signature;
+ uint64_t timestamp, nonce;
+ unsigned char ntlm2_hash[NTLM_NTLM2_HASH_LEN];
+ unsigned char challengehash[16];
+ unsigned char *blob;
+
+ if (!generate_timestamp(ntlm) ||
+ !generate_nonce(ntlm) ||
+ !generate_ntlm2_hash(ntlm2_hash, ntlm))
+ return false;
+
+ blob_len = ntlm->challenge.target_info_len + 32;
+ ntlm2_response_len = blob_len + 16;
+
+ if ((ntlm->ntlm2_response = malloc(ntlm2_response_len)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return false;
+ }
+
+ /* position the blob in the response; we'll use it then return it */
+ blob = ntlm->ntlm2_response + 16;
+
+ /* the blob's integer values are in network byte order */
+ signature = htonl(0x01010000);
+ timestamp = htonll(ntlm->timestamp);
+ nonce = htonll(ntlm->nonce);
+
+ /* construct the blob */
+ memcpy(&blob[0], &signature, 4);
+ memset(&blob[4], 0, 4);
+ memcpy(&blob[8], &timestamp, 8);
+ memcpy(&blob[16], &nonce, 8);
+ memset(&blob[24], 0, 4);
+ memcpy(&blob[28], ntlm->challenge.target_info, ntlm->challenge.target_info_len);
+ memset(&blob[28 + ntlm->challenge.target_info_len], 0, 4);
+
+ if (!generate_ntlm2_challengehash(challengehash, ntlm, ntlm2_hash, blob, blob_len))
+ return false;
+
+ memcpy(ntlm->ntlm2_response, challengehash, 16);
+ ntlm->ntlm2_response_len = ntlm2_response_len;
+
+ if (!generate_lm2_response(ntlm, ntlm2_hash))
+ return false;
+
+ return true;
+}
+
+int ntlm_client_response(
+ const unsigned char **out,
+ size_t *out_len,
+ ntlm_client *ntlm)
+{
+ unsigned char *domain, *username, *hostname, *ntlm_rep, *session;
+ size_t lm_rep_len, lm_rep_offset, ntlm_rep_len, ntlm_rep_offset,
+ domain_len, domain_offset, username_len, username_offset,
+ hostname_len, hostname_offset, session_len, session_offset;
+ uint32_t flags = 0;
+ bool unicode;
+
+ assert(out && out_len && ntlm);
+
+ *out = NULL;
+ *out_len = 0;
+
+ if (ntlm->state != NTLM_STATE_RESPONSE) {
+ ntlm_client_set_errmsg(ntlm, "ntlm handle in invalid state");
+ return -1;
+ }
+
+ /*
+ * Minimum message size is 64 bytes:
+ * 8 byte signature,
+ * 4 byte message indicator,
+ * 6x8 byte security buffers
+ * 4 byte flags
+ */
+ ntlm->response.len = 64;
+
+ unicode = supports_unicode(ntlm) &&
+ (ntlm->challenge.flags & NTLM_NEGOTIATE_UNICODE);
+
+ if (unicode)
+ flags |= NTLM_NEGOTIATE_UNICODE;
+ else
+ flags |= NTLM_NEGOTIATE_OEM;
+
+ if (unicode) {
+ domain = (unsigned char *)ntlm->userdomain_utf16;
+ domain_len = ntlm->userdomain_utf16_len;
+
+ username = (unsigned char *)ntlm->username_utf16;
+ username_len = ntlm->username_utf16_len;
+
+ hostname = (unsigned char *)ntlm->hostname_utf16;
+ hostname_len = ntlm->hostname_utf16_len;
+ } else {
+ domain = (unsigned char *)ntlm->userdomain;
+ domain_len = ntlm->userdomain ? strlen(ntlm->userdomain) : 0;
+
+ username = (unsigned char *)ntlm->username;
+ username_len = ntlm->username ? strlen(ntlm->username) : 0;
+
+ hostname = (unsigned char *)ntlm->hostname;
+ hostname_len = ntlm->hostname ? strlen(ntlm->hostname) : 0;
+ }
+
+ /* Negotiate our requested authentication type with the server's */
+ if (!(ntlm->flags & NTLM_CLIENT_DISABLE_NTLM2) &&
+ (ntlm->challenge.flags & NTLM_NEGOTIATE_NTLM)) {
+ flags |= NTLM_NEGOTIATE_NTLM;
+
+ if (!generate_ntlm2_response(ntlm))
+ return -1;
+ } else if ((ntlm->flags & NTLM_CLIENT_ENABLE_NTLM) &&
+ (ntlm->challenge.flags & NTLM_NEGOTIATE_NTLM)) {
+ flags |= NTLM_NEGOTIATE_NTLM;
+
+ if (!generate_ntlm_response(ntlm) ||
+ !generate_lm_response(ntlm))
+ return -1;
+ } else if (ntlm->flags & NTLM_CLIENT_ENABLE_LM) {
+ if (!generate_lm_response(ntlm))
+ return -1;
+ } else {
+ ntlm_client_set_errmsg(ntlm,
+ "no encryption options could be negotiated");
+ return -1;
+ }
+
+ domain_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, domain_len);
+
+ username_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, username_len);
+
+ hostname_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, hostname_len);
+
+ lm_rep_len = ntlm->lm_response_len;
+ lm_rep_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, lm_rep_len);
+
+ ntlm_rep = ntlm->ntlm2_response_len ?
+ ntlm->ntlm2_response : ntlm->ntlm_response;
+ ntlm_rep_len = ntlm->ntlm2_response_len ?
+ ntlm->ntlm2_response_len : ntlm->ntlm_response_len;
+ ntlm_rep_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, ntlm_rep_len);
+
+ session = NULL;
+ session_len = 0;
+ session_offset = ntlm->response.len;
+ increment_size(&ntlm->response.len, session_len);
+
+ if (ntlm->response.len == (size_t)-1) {
+ ntlm_client_set_errmsg(ntlm, "message too large");
+ return -1;
+ }
+
+ if ((ntlm->response.buf = malloc(ntlm->response.len)) == NULL) {
+ ntlm_client_set_errmsg(ntlm, "out of memory");
+ return -1;
+ }
+
+ memset(ntlm->response.buf, 0, ntlm->response.len);
+
+ if (!write_buf(ntlm, &ntlm->response,
+ ntlm_client_signature, sizeof(ntlm_client_signature)) ||
+ !write_int32(ntlm, &ntlm->response, 3) ||
+ !write_bufinfo(ntlm, &ntlm->response, lm_rep_len, lm_rep_offset) ||
+ !write_bufinfo(ntlm, &ntlm->response, ntlm_rep_len, ntlm_rep_offset) ||
+ !write_bufinfo(ntlm, &ntlm->response, domain_len, domain_offset) ||
+ !write_bufinfo(ntlm, &ntlm->response, username_len, username_offset) ||
+ !write_bufinfo(ntlm, &ntlm->response, hostname_len, hostname_offset) ||
+ !write_bufinfo(ntlm, &ntlm->response, session_len, session_offset) ||
+ !write_int32(ntlm, &ntlm->response, flags) ||
+ !write_buf(ntlm, &ntlm->response, domain, domain_len) ||
+ !write_buf(ntlm, &ntlm->response, username, username_len) ||
+ !write_buf(ntlm, &ntlm->response, hostname, hostname_len) ||
+ !write_buf(ntlm, &ntlm->response, ntlm->lm_response, lm_rep_len) ||
+ !write_buf(ntlm, &ntlm->response, ntlm_rep, ntlm_rep_len) ||
+ !write_buf(ntlm, &ntlm->response, session, session_len))
+ return -1;
+
+ assert(ntlm->response.pos == ntlm->response.len);
+
+ ntlm->state = NTLM_STATE_COMPLETE;
+
+ *out = ntlm->response.buf;
+ *out_len = ntlm->response.len;
+
+ return 0;
+}
+
+void ntlm_client_reset(ntlm_client *ntlm)
+{
+ ntlm_client_flags flags;
+ ntlm_hmac_ctx *hmac_ctx;
+ ntlm_unicode_ctx *unicode_ctx;
+
+ assert(ntlm);
+
+ free(ntlm->negotiate.buf);
+ free(ntlm->challenge.target_info);
+ free(ntlm->challenge.target);
+ free(ntlm->challenge.target_domain);
+ free(ntlm->challenge.target_domain_dns);
+ free(ntlm->challenge.target_server);
+ free(ntlm->challenge.target_server_dns);
+ free(ntlm->response.buf);
+
+ free(ntlm->hostname);
+ free(ntlm->hostname_utf16);
+ free(ntlm->hostdomain);
+
+ free(ntlm->target);
+ free(ntlm->target_utf16);
+
+ free(ntlm->ntlm2_response);
+
+ free_credentials(ntlm);
+
+ flags = ntlm->flags;
+ hmac_ctx = ntlm->hmac_ctx;
+ unicode_ctx = ntlm->unicode_ctx;
+
+ memset(ntlm, 0, sizeof(struct ntlm_client));
+
+ ntlm->flags = flags;
+ ntlm->hmac_ctx = hmac_ctx;
+ ntlm->unicode_ctx = unicode_ctx;
+}
+
+void ntlm_client_free(ntlm_client *ntlm)
+{
+ if (!ntlm)
+ return;
+
+ ntlm_client_reset(ntlm);
+
+ ntlm_hmac_ctx_free(ntlm->hmac_ctx);
+ ntlm_unicode_ctx_free(ntlm->unicode_ctx);
+
+ free(ntlm);
+}
diff --git a/deps/ntlmclient/ntlm.h b/deps/ntlmclient/ntlm.h
new file mode 100644
index 000000000..0dad91ec0
--- /dev/null
+++ b/deps/ntlmclient/ntlm.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_NTLM_H__
+#define PRIVATE_NTLM_H__
+
+#include "ntlmclient.h"
+#include "unicode.h"
+#include "crypt.h"
+#include "compat.h"
+
+#define NTLM_LM_RESPONSE_LEN 24
+#define NTLM_NTLM_RESPONSE_LEN 24
+#define NTLM_NTLM_HASH_LEN 16
+#define NTLM_NTLM2_HASH_LEN 16
+
+#define NTLM_SIGNATURE { 'N', 'T', 'L', 'M', 'S', 'S', 'P', 0x00 }
+
+#define NTLM_LM_PLAINTEXT { 0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }
+
+typedef enum {
+ NTLM_STATE_NEGOTIATE = 0,
+ NTLM_STATE_CHALLENGE = 1,
+ NTLM_STATE_RESPONSE = 2,
+ NTLM_STATE_ERROR = 3,
+ NTLM_STATE_COMPLETE = 4,
+} ntlm_state;
+
+typedef struct {
+ unsigned char *buf;
+ size_t pos;
+ size_t len;
+} ntlm_buf;
+
+typedef struct {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+ uint32_t reserved;
+} ntlm_version;
+
+typedef struct {
+ uint32_t flags;
+ uint64_t nonce;
+ ntlm_version target_version;
+
+ /* The unparsed target information from the server */
+ unsigned char *target_info;
+ size_t target_info_len;
+
+ /* The target information parsed into usable strings */
+ char *target;
+ char *target_server;
+ char *target_domain;
+ char *target_server_dns;
+ char *target_domain_dns;
+} ntlm_challenge;
+
+struct ntlm_client {
+ ntlm_client_flags flags;
+
+ ntlm_state state;
+
+ /* crypto contexts */
+ ntlm_hmac_ctx *hmac_ctx;
+ ntlm_unicode_ctx *unicode_ctx;
+
+ /* error message as set by the library */
+ const char *errmsg;
+
+ char *hostname;
+ char *hostdomain;
+ ntlm_version host_version;
+
+ char *target;
+
+ char *username;
+ char *username_upper;
+ char *userdomain;
+ char *password;
+
+ /* strings as converted to utf16 */
+ char *target_utf16;
+ char *username_utf16;
+ char *username_upper_utf16;
+ char *userdomain_utf16;
+ char *hostname_utf16;
+ char *password_utf16;
+
+ /* timestamp and nonce; only for debugging */
+ uint64_t nonce;
+ uint64_t timestamp;
+
+ size_t username_utf16_len;
+ size_t username_upper_utf16_len;
+ size_t userdomain_utf16_len;
+ size_t hostname_utf16_len;
+ size_t password_utf16_len;
+ size_t target_utf16_len;
+
+ unsigned char lm_response[NTLM_LM_RESPONSE_LEN];
+ size_t lm_response_len;
+
+ unsigned char ntlm_response[NTLM_NTLM_RESPONSE_LEN];
+ size_t ntlm_response_len;
+
+ unsigned char *ntlm2_response;
+ size_t ntlm2_response_len;
+
+ ntlm_buf negotiate;
+ ntlm_challenge challenge;
+ ntlm_buf response;
+};
+
+typedef enum {
+ NTLM_ENABLE_HOSTVERSION = (1 << 31),
+} ntlm_client_internal_flags;
+
+typedef enum {
+ NTLM_TARGET_INFO_END = 0,
+ NTLM_TARGET_INFO_SERVER = 1,
+ NTLM_TARGET_INFO_DOMAIN = 2,
+ NTLM_TARGET_INFO_SERVER_DNS = 3,
+ NTLM_TARGET_INFO_DOMAIN_DNS = 4,
+} ntlm_target_info_type_t;
+
+typedef enum {
+ /* Unicode strings are supported in security buffers */
+ NTLM_NEGOTIATE_UNICODE = 0x00000001,
+
+ /* OEM (ANSI) strings are supported in security buffers */
+ NTLM_NEGOTIATE_OEM = 0x00000002,
+
+ /* Request the target realm from the server */
+ NTLM_NEGOTIATE_REQUEST_TARGET = 0x00000004,
+
+ /* NTLM authentication is supported */
+ NTLM_NEGOTIATE_NTLM = 0x00000200,
+
+ /* Negotiate domain name */
+ NTLM_NEGOTIATE_DOMAIN_SUPPLIED = 0x00001000,
+
+ /* Negotiate workstation (client) name */
+ NTLM_NEGOTIATE_WORKSTATION_SUPPLIED = 0x00002000,
+
+ /* Indicates that a local context is available */
+ NTLM_NEGOTIATE_LOCAL_CALL = 0x00004000,
+
+ /* Request a dummy signature */
+ NTLM_NEGOTIATE_ALWAYS_SIGN = 0x00008000,
+
+ /* Target (server) is a domain */
+ NTLM_NEGOTIATE_TYPE_DOMAIN = 0x00010000,
+
+ /* NTLM2 signing and sealing is supported */
+ NTLM_NEGOTIATE_NTLM2_SIGN_AND_SEAL = 0x00080000,
+
+ /* A target information block is included */
+ NTLM_NEGOTIATE_TARGET_INFO = 0x00800000,
+
+ /* Version information should be provided */
+ NTLM_NEGOTIATE_VERSION = 0x01000000,
+} ntlm_negotiate_t;
+
+extern int ntlm_client_set_nonce(ntlm_client *ntlm, uint64_t nonce);
+extern int ntlm_client_set_timestamp(ntlm_client *ntlm, uint64_t timestamp);
+extern void ntlm_client_set_errmsg(ntlm_client *ntlm, const char *errmsg);
+
+#endif /* PRIVATE_NTLM_H__ */
diff --git a/deps/ntlmclient/ntlmclient.h b/deps/ntlmclient/ntlmclient.h
new file mode 100644
index 000000000..d109a5c89
--- /dev/null
+++ b/deps/ntlmclient/ntlmclient.h
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+#ifndef INCLUDE_NTLMCLIENT_H__
+#define INCLUDE_NTLMCLIENT_H__
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NTLM_CLIENT_VERSION "0.0.1"
+#define NTLM_CLIENT_VERSION_MAJOR 0
+#define NTLM_CLIENT_VERSION_MINOR 0
+#define NTLM_CLIENT_VERSION_TEENY 1
+
+typedef struct ntlm_client ntlm_client;
+
+/*
+ * Flags for initializing the `ntlm_client` context. A combination of
+ * these flags can be provided to `ntlm_client_init`.
+ */
+typedef enum {
+ /** Default settings for the `ntlm_client`. */
+ NTLM_CLIENT_DEFAULTS = 0,
+
+ /**
+ * Disable Unicode negotiation. By default, strings are converted
+ * into UTF-16 when supplied to the remote host, but if this flag
+ * is specified, localizable strings (like username and password)
+ * will only be sent to the server as they were provided to the
+ * library. Since the NTLM protocol does not deliver the locale
+ * information, these will be interpreted by the remote host in
+ * whatever locale is configured, and likely be corrupted unless
+ * you limit yourself to ASCII.
+ *
+ * You are discouraged from setting this flag.
+ */
+ NTLM_CLIENT_DISABLE_UNICODE = (1 << 0),
+
+ /*
+ * Enable LM ("Lan Manager") authentication support. By default,
+ * LM authentication is disabled, since most remote servers have
+ * disabled support for it, and because it is both trivially
+ * brute-forced _and_ subject to rainbow table lookups. If this
+ * flag is enabled, LM is still not used unless NTLM2 support is
+ * also disabled.
+ *
+ * You are discouraged from setting this flag.
+ */
+ NTLM_CLIENT_ENABLE_LM = (1 << 1),
+
+ /*
+ * Enable NTLM ("Lan Manager") authentication support. By default,
+ * NTLM authentication is disabled, since most remote servers have
+ * disabled support for it, due to its weakness. If this flag is
+ * enabled, NTLM is still not used unless NTLM2 support is also
+ * disabled.
+ *
+ * You are discouraged from setting this flag.
+ */
+ NTLM_CLIENT_ENABLE_NTLM = (1 << 2),
+
+ /*
+ * Disable NTLM2 authentication support. By default, _only_ NTLM2
+ * support is enabled, since most remote servers will only support
+ * it due to its (relative) lack of weakness. If this flag is
+ * set, either NTLM or LM (or both) must be explicitly enabled or
+ * there will be no mechanisms available to use.
+ *
+ * You are discouraged from setting this flag.
+ */
+ NTLM_CLIENT_DISABLE_NTLM2 = (1 << 3),
+
+ /*
+ * Request the target's name. By default, you are expected to
+ * provide the name of the target you are authenticating to (eg,
+ * the remote hostname). If set, the remote host will provide
+ * its idea of its hostname in the challenge message. You may
+ * then set the authentication target based on it.
+ */
+ NTLM_CLIENT_DISABLE_REQUEST_TARGET = (1 << 4),
+} ntlm_client_flags;
+
+
+/** Declare a public function exported for application use. */
+#if __GNUC__ >= 4 && !defined(NTLM_STATIC)
+# define NTLM_EXTERN(type) extern \
+ __attribute__((visibility("default"))) \
+ type
+#elif defined(_MSC_VER) && !defined(NTLM_STATIC)
+# define NTLM_EXTERN(type) __declspec(dllexport) type
+#else
+# define NTLM_EXTERN(type) extern type
+#endif
+
+/**
+ * Initializes an `ntlm_client` context, which can begin sending
+ * and receiving NTLM authentication messages.
+ *
+ * @param flags the `ntlm_client_flag_t`s to use for negotiation.
+ * @return the `ntlm_client` context, or `NULL` if out-of-memory.
+ */
+NTLM_EXTERN(ntlm_client *) ntlm_client_init(ntlm_client_flags flags);
+
+/**
+ * Gets the error message for the most recent error that occurred. If
+ * a function returns an error, more details can be retrieved with this
+ * function. The string returned is a constant string; it should not
+ * be freed.
+ *
+ * @return a constant string containing the error message.
+ */
+NTLM_EXTERN(const char *) ntlm_client_errmsg(ntlm_client *ntlm);
+
+/**
+ * Sets the local hostname and domain. These strings should be in
+ * ASCII. They will be provided to the remote host during the
+ * negotiation phase.
+ *
+ * @param ntlm the `ntlm_client` context to configure
+ * @param hostname the hostname of the local machine
+ * @param domain the domain of the local machine
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_set_hostname(
+ ntlm_client *ntlm,
+ const char *hostname,
+ const char *domain);
+
+/**
+ * Sets the local operating system version. These numbers are expected
+ * to correspond to Windows operating system versions; for example
+ * major version 6, minor version 2, build 9200 would correspond to
+ * Windows 8 (aka "NT 6.2").
+ *
+ * It is not likely that you need to set the local version.
+ *
+ * @param ntlm the `ntlm_client` context to configure
+ * @param major the major version number of the local operating system
+ * @param minor the minor version number of the local operating system
+ * @param build the build number of the local operating system
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_set_version(
+ ntlm_client *ntlm,
+ uint8_t major,
+ uint8_t minor,
+ uint16_t build);
+
+/**
+ * Sets the username and password to authenticate with to the remote
+ * host. Username and password may be specified in UTF-8 but the
+ * domain should be in ASCII. These will not be sent to the remote host
+ * but will instead be used to compute the LM, NTLM or NTLM2 responses,
+ * which will be provided to the remote host during the response phase.
+ *
+ * @param ntlm the `ntlm_client` context to configure
+ * @param username the username to authenticate with
+ * @param domain the domain of the user authenticating
+ * @param password the password to authenticate with
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_set_credentials(
+ ntlm_client *ntlm,
+ const char *username,
+ const char *domain,
+ const char *password);
+
+/**
+ * Sets the authentication target, your idea of the remote host's
+ * name. The target should be provided as ASCII. It will be
+ * provided to the remote host during the response phase.
+ *
+ * @param ntlm the `ntlm_client` context to configure
+ * @param target the name of the authentication target
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_set_target(
+ ntlm_client *ntlm,
+ const char *target);
+
+/**
+ * Gets the remote host's nonce, as it was provided in the challenge
+ * message. This is an opaque 8 byte value that is used to compute
+ * the LM, NTLM and NTLM2 responses.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the challenge from the remote host
+ */
+NTLM_EXTERN(uint64_t) ntlm_client_challenge_nonce(
+ ntlm_client *ntlm);
+
+/**
+ * Gets the remote hosts's target name, which can be used as the
+ * authentication target. This will be given as it was provided
+ * in the challenge message.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the remote host's target name
+ */
+NTLM_EXTERN(const char *) ntlm_client_target(ntlm_client *ntlm);
+
+/**
+ * Gets the remote hosts's name, which is generally its short name.
+ * This will be given as it was provided in the challenge message.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the remote host's server name
+ */
+NTLM_EXTERN(const char *) ntlm_client_target_server(ntlm_client *ntlm);
+
+/**
+ * Gets the remote hosts's domain, which is generally the short or
+ * NT-style domain name. This will be given as it was provided in
+ * the challenge message.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the remote host's domain
+ */
+NTLM_EXTERN(const char *) ntlm_client_target_domain(ntlm_client *ntlm);
+
+/**
+ * Gets the remote hosts's DNS name, which is generally the long-style
+ * Active Directory or fully-qualified hostname. This will be given
+ * as it was provided in the challenge message.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the remote host's DNS name
+ */
+NTLM_EXTERN(const char *) ntlm_client_target_server_dns(ntlm_client *ntlm);
+
+/**
+ * Gets the remote hosts's DNS domain, which is generally the long-style
+ * Active Directory or fully-qualified domain name. This will be given
+ * as it was provided in the challenge message.
+ *
+ * @param ntlm the `ntlm_client` context to query
+ * @return the remote host's DNS domain
+ */
+NTLM_EXTERN(const char *) ntlm_client_target_domain_dns(ntlm_client *ntlm);
+
+/**
+ * Computes a negotiation message (aka a "Type 1" message) to begin
+ * NTLM authentication with the server. The local hostname should be
+ * set before calling this function (if necessary). This message
+ * should be delivered to the server to indicate a willingness to begin
+ * NTLM authentication. This buffer should not be freed by the caller.
+ *
+ * @param out a pointer to the negotiation message
+ * @param out_len a pointer to the length of the negotiation message
+ * @param ntlm the `ntlm_client` context
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_negotiate(
+ const unsigned char **out,
+ size_t *out_len,
+ ntlm_client *ntlm);
+
+/**
+ * Parses a challenge message (aka a "Type 2" message) from the server.
+ * This must be called in order to calculate the response to the
+ * authentication.
+ *
+ * @param ntlm the `ntlm_client` context
+ * @param message the challenge message from the server
+ * @param message_len the length of the challenge message
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_set_challenge(
+ ntlm_client *ntlm,
+ const unsigned char *message,
+ size_t message_len);
+
+/**
+ * Computes a response message (aka a "Type 3" message) to complete
+ * NTLM authentication with the server. The credentials should be
+ * set before calling this function. This message should be delivered
+ * to the server to complete authentication. This buffer should not
+ * be freed by the caller.
+ *
+ * @param out a pointer to the response message
+ * @param out_len a pointer to the length of the response message
+ * @param ntlm the `ntlm_client` context
+ * @return 0 on success, non-zero on failure
+ */
+NTLM_EXTERN(int) ntlm_client_response(
+ const unsigned char **out,
+ size_t *out_len,
+ ntlm_client *ntlm);
+
+/**
+ * Resets an `ntlm_client` context completely, so that authentication
+ * may be retried. You must set _all_ parameters again, including the
+ * target, username, password, etc. Once these values are configured
+ * again, the negotiation can begin.
+ *
+ * @param ntlm the `ntlm_client` context to reset
+ */
+NTLM_EXTERN(void) ntlm_client_reset(ntlm_client *ntlm);
+
+/**
+ * Frees an `ntlm_client` context. This should be done to free memory
+ * belonging to the context. The context cannot be reused.
+ *
+ * @param ntlm the `ntlm_client` context to free
+ */
+NTLM_EXTERN(void) ntlm_client_free(ntlm_client *ntlm);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INCLUDE_NTLMCLIENT_H__ */
diff --git a/deps/ntlmclient/unicode.h b/deps/ntlmclient/unicode.h
new file mode 100644
index 000000000..e3b17bcf7
--- /dev/null
+++ b/deps/ntlmclient/unicode.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_UNICODE_H__
+#define PRIVATE_UNICODE_H__
+
+#include "compat.h"
+
+#define NTLM_UNICODE_MAX_LEN 2048
+
+typedef struct ntlm_unicode_ctx ntlm_unicode_ctx;
+
+extern ntlm_unicode_ctx *ntlm_unicode_ctx_init(ntlm_client *ntlm);
+
+bool ntlm_unicode_utf8_to_16(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len);
+
+bool ntlm_unicode_utf16_to_8(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len);
+
+extern void ntlm_unicode_ctx_free(ntlm_unicode_ctx *ctx);
+
+#endif /* PRIVATE_UNICODE_H__ */
diff --git a/deps/ntlmclient/unicode_builtin.c b/deps/ntlmclient/unicode_builtin.c
new file mode 100644
index 000000000..e1856cca9
--- /dev/null
+++ b/deps/ntlmclient/unicode_builtin.c
@@ -0,0 +1,445 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "ntlm.h"
+#include "unicode.h"
+#include "compat.h"
+
+struct ntlm_unicode_ctx {
+ ntlm_client *ntlm;
+};
+
+typedef unsigned int UTF32; /* at least 32 bits */
+typedef unsigned short UTF16; /* at least 16 bits */
+typedef unsigned char UTF8; /* typically 8 bits */
+
+/* Some fundamental constants */
+#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
+#define UNI_MAX_BMP (UTF32)0x0000FFFF
+#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
+#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
+#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
+
+#define UNI_MAX_UTF8_BYTES_PER_CODE_POINT 4
+
+typedef enum {
+ conversionOK, /* conversion successful */
+ sourceExhausted, /* partial character in source, but hit end */
+ targetExhausted, /* insuff. room in target for conversion */
+ sourceIllegal /* source sequence is illegal/malformed */
+} ConversionResult;
+
+typedef enum {
+ strictConversion = 0,
+ lenientConversion
+} ConversionFlags;
+
+
+static const int halfShift = 10; /* used for shifting by 10 bits */
+
+static const UTF32 halfBase = 0x0010000UL;
+static const UTF32 halfMask = 0x3FFUL;
+
+#define UNI_SUR_HIGH_START (UTF32)0xD800
+#define UNI_SUR_HIGH_END (UTF32)0xDBFF
+#define UNI_SUR_LOW_START (UTF32)0xDC00
+#define UNI_SUR_LOW_END (UTF32)0xDFFF
+#define false 0
+#define true 1
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Index into the table below with the first byte of a UTF-8 sequence to
+ * get the number of trailing bytes that are supposed to follow it.
+ * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
+ * left as-is for anyone who may want to do such conversion, which was
+ * allowed in earlier algorithms.
+ */
+static const char trailingBytesForUTF8[256] = {
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
+};
+
+/*
+ * Magic values subtracted from a buffer value during UTF8 conversion.
+ * This table contains as many values as there might be trailing bytes
+ * in a UTF-8 sequence.
+ */
+static const UTF32 offsetsFromUTF8[6] = {
+ 0x00000000UL, 0x00003080UL, 0x000E2080UL,
+ 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
+
+/*
+ * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
+ * into the first byte, depending on how many bytes follow. There are
+ * as many entries in this table as there are UTF-8 sequence types.
+ * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
+ * for *legal* UTF-8 will be 4 or fewer bytes total.
+ */
+static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+/* --------------------------------------------------------------------- */
+
+/* The interface converts a whole buffer to avoid function-call overhead.
+ * Constants have been gathered. Loops & conditionals have been removed as
+ * much as possible for efficiency, in favor of drop-through switches.
+ * (See "Note A" at the bottom of the file for equivalent code.)
+ * If your compiler supports it, the "isLegalUTF8" call can be turned
+ * into an inline function.
+ */
+
+static ConversionResult ConvertUTF16toUTF8 (
+ const UTF16** sourceStart, const UTF16* sourceEnd,
+ UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
+ ConversionResult result = conversionOK;
+ const UTF16* source = *sourceStart;
+ UTF8* target = *targetStart;
+ while (source < sourceEnd) {
+ UTF32 ch;
+ unsigned short bytesToWrite = 0;
+ const UTF32 byteMask = 0xBF;
+ const UTF32 byteMark = 0x80;
+ const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */
+ ch = *source++;
+ /* If we have a surrogate pair, convert to UTF32 first. */
+ if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
+ /* If the 16 bits following the high surrogate are in the source buffer... */
+ if (source < sourceEnd) {
+ UTF32 ch2 = *source;
+ /* If it's a low surrogate, convert to UTF32. */
+ if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
+ ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
+ + (ch2 - UNI_SUR_LOW_START) + halfBase;
+ ++source;
+ } else if (flags == strictConversion) { /* it's an unpaired high surrogate */
+ --source; /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ }
+ } else { /* We don't have the 16 bits following the high surrogate. */
+ --source; /* return to the high surrogate */
+ result = sourceExhausted;
+ break;
+ }
+ } else if (flags == strictConversion) {
+ /* UTF-16 surrogate values are illegal in UTF-32 */
+ if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
+ --source; /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ }
+ }
+ /* Figure out how many bytes the result will require */
+ if (ch < (UTF32)0x80) { bytesToWrite = 1;
+ } else if (ch < (UTF32)0x800) { bytesToWrite = 2;
+ } else if (ch < (UTF32)0x10000) { bytesToWrite = 3;
+ } else if (ch < (UTF32)0x110000) { bytesToWrite = 4;
+ } else { bytesToWrite = 3;
+ ch = UNI_REPLACEMENT_CHAR;
+ }
+
+ target += bytesToWrite;
+ if (target > targetEnd) {
+ source = oldSource; /* Back up source pointer! */
+ target -= bytesToWrite; result = targetExhausted; break;
+ }
+ switch (bytesToWrite) { /* note: everything falls through. */
+ case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+ case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+ case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+ case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]);
+ }
+ target += bytesToWrite;
+ }
+ *sourceStart = source;
+ *targetStart = target;
+ return result;
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Utility routine to tell whether a sequence of bytes is legal UTF-8.
+ * This must be called with the length pre-determined by the first byte.
+ * If not calling this from ConvertUTF8to*, then the length can be set by:
+ * length = trailingBytesForUTF8[*source]+1;
+ * and the sequence is illegal right away if there aren't that many bytes
+ * available.
+ * If presented with a length > 4, this returns false. The Unicode
+ * definition of UTF-8 goes up to 4-byte sequences.
+ */
+
+static inline bool isLegalUTF8(const UTF8 *source, int length) {
+ UTF8 a;
+ const UTF8 *srcptr = source+length;
+ switch (length) {
+ default: return false;
+ /* Everything else falls through when "true"... */
+ case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
+ case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
+ case 2: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
+
+ switch (*source) {
+ /* no fall-through in this inner switch */
+ case 0xE0: if (a < 0xA0) return false; break;
+ case 0xED: if (a > 0x9F) return false; break;
+ case 0xF0: if (a < 0x90) return false; break;
+ case 0xF4: if (a > 0x8F) return false; break;
+ default: if (a < 0x80) return false;
+ }
+
+ case 1: if (*source >= 0x80 && *source < 0xC2) return false;
+ }
+ if (*source > 0xF4) return false;
+ return true;
+}
+
+static ConversionResult ConvertUTF8toUTF16 (
+ const UTF8** sourceStart, const UTF8* sourceEnd,
+ UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) {
+ ConversionResult result = conversionOK;
+ const UTF8* source = *sourceStart;
+ UTF16* target = *targetStart;
+ while (source < sourceEnd) {
+ UTF32 ch = 0;
+ unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
+ if (extraBytesToRead >= sourceEnd - source) {
+ result = sourceExhausted; break;
+ }
+ /* Do this check whether lenient or strict */
+ if (!isLegalUTF8(source, extraBytesToRead+1)) {
+ result = sourceIllegal;
+ break;
+ }
+ /*
+ * The cases all fall through. See "Note A" below.
+ */
+ switch (extraBytesToRead) {
+ case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
+ case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
+ case 3: ch += *source++; ch <<= 6;
+ case 2: ch += *source++; ch <<= 6;
+ case 1: ch += *source++; ch <<= 6;
+ case 0: ch += *source++;
+ }
+ ch -= offsetsFromUTF8[extraBytesToRead];
+
+ if (target >= targetEnd) {
+ source -= (extraBytesToRead+1); /* Back up source pointer! */
+ result = targetExhausted; break;
+ }
+ if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
+ /* UTF-16 surrogate values are illegal in UTF-32 */
+ if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
+ if (flags == strictConversion) {
+ source -= (extraBytesToRead+1); /* return to the illegal value itself */
+ result = sourceIllegal;
+ break;
+ } else {
+ *target++ = UNI_REPLACEMENT_CHAR;
+ }
+ } else {
+ *target++ = (UTF16)ch; /* normal case */
+ }
+ } else if (ch > UNI_MAX_UTF16) {
+ if (flags == strictConversion) {
+ result = sourceIllegal;
+ source -= (extraBytesToRead+1); /* return to the start */
+ break; /* Bail out; shouldn't continue */
+ } else {
+ *target++ = UNI_REPLACEMENT_CHAR;
+ }
+ } else {
+ /* target is a character in range 0xFFFF - 0x10FFFF. */
+ if (target + 1 >= targetEnd) {
+ source -= (extraBytesToRead+1); /* Back up source pointer! */
+ result = targetExhausted; break;
+ }
+ ch -= halfBase;
+ *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
+ *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
+ }
+ }
+ *sourceStart = source;
+ *targetStart = target;
+ return result;
+}
+
+
+ntlm_unicode_ctx *ntlm_unicode_ctx_init(ntlm_client *ntlm)
+{
+ ntlm_unicode_ctx *ctx;
+
+ if ((ctx = malloc(sizeof(ntlm_unicode_ctx))) == NULL)
+ return NULL;
+
+ ctx->ntlm = ntlm;
+ return ctx;
+}
+
+typedef enum {
+ unicode_builtin_utf8_to_16,
+ unicode_builtin_utf16_to_8
+} unicode_builtin_encoding_direction;
+
+static inline bool unicode_builtin_encoding_convert(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len,
+ unicode_builtin_encoding_direction direction)
+{
+ const char *in_start, *in_end;
+ char *out, *out_start, *out_end, *new_out;
+ size_t out_size, out_len;
+ bool success = false;
+ ConversionResult result;
+
+ *converted = NULL;
+ *converted_len = 0;
+
+ in_start = string;
+ in_end = in_start + string_len;
+
+ /*
+ * When translating UTF8 to UTF16, these strings are only used
+ * internally, and we obey the given length, so we can simply
+ * use a buffer that is 2x the size. Add an extra byte to NUL
+ * terminate the results (two bytes for UTF16).
+ */
+ if (direction == unicode_builtin_utf8_to_16)
+ out_size = (string_len * 2 + 2);
+ else
+ out_size = (string_len / 2 + 1);
+
+ /* Round to the nearest multiple of 8 */
+ out_size = (out_size + 7) & ~7;
+
+ if ((out = malloc(out_size)) == NULL) {
+ ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
+ return false;
+ }
+
+ out_start = out;
+ out_end = out_start + out_size;
+
+ /* Make room for NUL termination */
+ if (direction == unicode_builtin_utf16_to_8)
+ out_end--;
+
+ while (true) {
+ if (direction == unicode_builtin_utf8_to_16)
+ result = ConvertUTF8toUTF16(
+ (const UTF8 **)&in_start, (UTF8 *)in_end,
+ (UTF16 **)&out_start, (UTF16 *)out_end, strictConversion);
+ else
+ result = ConvertUTF16toUTF8(
+ (const UTF16 **)&in_start, (UTF16 *)in_end,
+ (UTF8 **)&out_start, (UTF8 *)out_end, lenientConversion);
+
+ switch (result) {
+ case conversionOK:
+ success = true;
+ goto done;
+ case sourceExhausted:
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "invalid unicode string; trailing data remains");
+ goto done;
+ case targetExhausted:
+ break;
+ case sourceIllegal:
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "invalid unicode string; trailing data remains");
+ goto done;
+ default:
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "unknown unicode conversion failure");
+ goto done;
+ }
+
+ /* Grow buffer size by 1.5 (rounded up to a multiple of 8) */
+ out_size = ((((out_size << 1) - (out_size >> 1)) + 7) & ~7);
+
+ if (out_size > NTLM_UNICODE_MAX_LEN) {
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "unicode conversion too large");
+ goto done;
+ }
+
+ if ((new_out = realloc(out, out_size)) == NULL) {
+ ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
+ goto done;
+ }
+
+ out_len = out_start - out;
+
+ out = new_out;
+ out_start = new_out + out_len;
+ out_end = out + out_size;
+
+ /* Make room for NUL termination */
+ out_end -= (direction == unicode_builtin_utf8_to_16) ? 2 : 1;
+ }
+
+done:
+ if (!success) {
+ free(out);
+ return false;
+ }
+
+ out_len = (out_start - out);
+
+ /* NUL terminate */
+ out[out_len] = '\0';
+
+ if (direction == unicode_builtin_utf8_to_16)
+ out[out_len+1] = '\0';
+
+ *converted = out;
+ *converted_len = out_len;
+ return true;
+}
+
+bool ntlm_unicode_utf8_to_16(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len)
+{
+ return unicode_builtin_encoding_convert(converted, converted_len,
+ ctx, string, string_len, unicode_builtin_utf8_to_16);
+}
+
+bool ntlm_unicode_utf16_to_8(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len)
+{
+ return unicode_builtin_encoding_convert(converted, converted_len,
+ ctx, string, string_len, unicode_builtin_utf16_to_8);
+}
+
+void ntlm_unicode_ctx_free(ntlm_unicode_ctx *ctx)
+{
+ if (ctx)
+ free(ctx);
+}
diff --git a/deps/ntlmclient/unicode_iconv.c b/deps/ntlmclient/unicode_iconv.c
new file mode 100644
index 000000000..d1fe07e26
--- /dev/null
+++ b/deps/ntlmclient/unicode_iconv.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <locale.h>
+#include <iconv.h>
+#include <string.h>
+#include <errno.h>
+
+#include "ntlmclient.h"
+#include "unicode.h"
+#include "ntlm.h"
+#include "compat.h"
+
+struct ntlm_unicode_ctx {
+ ntlm_client *ntlm;
+ iconv_t utf8_to_16;
+ iconv_t utf16_to_8;
+};
+
+ntlm_unicode_ctx *ntlm_unicode_ctx_init(ntlm_client *ntlm)
+{
+ ntlm_unicode_ctx *ctx;
+
+ if ((ctx = calloc(1, sizeof(ntlm_unicode_ctx))) == NULL)
+ return NULL;
+
+ ctx->ntlm = ntlm;
+ ctx->utf8_to_16 = (iconv_t)-1;
+ ctx->utf16_to_8 = (iconv_t)-1;
+
+ return ctx;
+}
+
+typedef enum {
+ unicode_iconv_utf8_to_16,
+ unicode_iconv_utf16_to_8
+} unicode_iconv_encoding_direction;
+
+static inline bool unicode_iconv_init(ntlm_unicode_ctx *ctx)
+{
+ if (ctx->utf8_to_16 != (iconv_t)-1 || ctx->utf16_to_8 != (iconv_t)-1)
+ return true;
+
+ if ((ctx->utf8_to_16 = iconv_open("UTF-16LE", "UTF-8")) == (iconv_t)-1 ||
+ (ctx->utf16_to_8 = iconv_open("UTF-8", "UTF-16LE")) == (iconv_t)-1) {
+ if (errno == EINVAL)
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "iconv does not support UTF8 <-> UTF16 conversion");
+ else
+ ntlm_client_set_errmsg(ctx->ntlm, strerror(errno));
+
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool unicode_iconv_encoding_convert(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len,
+ unicode_iconv_encoding_direction direction)
+{
+ char *in_start, *out_start, *out, *new_out;
+ size_t in_start_len, out_start_len, out_size, nul_size, ret, written = 0;
+ iconv_t converter;
+
+ *converted = NULL;
+ *converted_len = 0;
+
+ if (!unicode_iconv_init(ctx))
+ return false;
+
+ /*
+ * When translating UTF8 to UTF16, these strings are only used
+ * internally, and we obey the given length, so we can simply
+ * use a buffer that is 2x the size. When translating from UTF16
+ * to UTF8, we may need to return to callers, so we need to NUL
+ * terminate and expect an extra byte for UTF8, two for UTF16.
+ */
+ if (direction == unicode_iconv_utf8_to_16) {
+ converter = ctx->utf8_to_16;
+ out_size = (string_len * 2) + 2;
+ nul_size = 2;
+ } else {
+ converter = ctx->utf16_to_8;
+ out_size = (string_len / 2) + 1;
+ nul_size = 1;
+ }
+
+ /* Round to the nearest multiple of 8 */
+ out_size = (out_size + 7) & ~7;
+
+ if ((out = malloc(out_size)) == NULL) {
+ ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
+ return false;
+ }
+
+ in_start = (char *)string;
+ in_start_len = string_len;
+
+ while (true) {
+ out_start = out + written;
+ out_start_len = (out_size - nul_size) - written;
+
+ ret = iconv(converter, &in_start, &in_start_len, &out_start, &out_start_len);
+ written = (out_size - nul_size) - out_start_len;
+
+ if (ret == 0)
+ break;
+
+ if (ret == (size_t)-1 && errno != E2BIG) {
+ ntlm_client_set_errmsg(ctx->ntlm, strerror(errno));
+ goto on_error;
+ }
+
+ /* Grow buffer size by 1.5 (rounded up to a multiple of 8) */
+ out_size = ((((out_size << 1) - (out_size >> 1)) + 7) & ~7);
+
+ if (out_size > NTLM_UNICODE_MAX_LEN) {
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "unicode conversion too large");
+ goto on_error;
+ }
+
+ if ((new_out = realloc(out, out_size)) == NULL) {
+ ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
+ goto on_error;
+ }
+
+ out = new_out;
+ }
+
+ if (in_start_len != 0) {
+ ntlm_client_set_errmsg(ctx->ntlm,
+ "invalid unicode string; trailing data remains");
+ goto on_error;
+ }
+
+ /* NUL terminate */
+ out[written] = '\0';
+
+ if (direction == unicode_iconv_utf8_to_16)
+ out[written + 1] = '\0';
+
+ *converted = out;
+
+ if (converted_len)
+ *converted_len = written;
+
+ return true;
+
+on_error:
+ free(out);
+ return false;
+}
+
+bool ntlm_unicode_utf8_to_16(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len)
+{
+ return unicode_iconv_encoding_convert(
+ converted, converted_len, ctx, string, string_len,
+ unicode_iconv_utf8_to_16);
+}
+
+bool ntlm_unicode_utf16_to_8(
+ char **converted,
+ size_t *converted_len,
+ ntlm_unicode_ctx *ctx,
+ const char *string,
+ size_t string_len)
+{
+ return unicode_iconv_encoding_convert(
+ converted, converted_len, ctx, string, string_len,
+ unicode_iconv_utf16_to_8);
+}
+
+void ntlm_unicode_ctx_free(ntlm_unicode_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ if (ctx->utf16_to_8 != (iconv_t)-1)
+ iconv_close(ctx->utf16_to_8);
+
+ if (ctx->utf8_to_16 != (iconv_t)-1)
+ iconv_close(ctx->utf8_to_16);
+
+ free(ctx);
+}
diff --git a/deps/ntlmclient/utf8.h b/deps/ntlmclient/utf8.h
new file mode 100644
index 000000000..a26ae85c3
--- /dev/null
+++ b/deps/ntlmclient/utf8.h
@@ -0,0 +1,1257 @@
+// The latest version of this library is available on GitHub;
+// https://github.com/sheredom/utf8.h
+
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+//
+// In jurisdictions that recognize copyright laws, the author or authors
+// of this software dedicate any and all copyright interest in the
+// software to the public domain. We make this dedication for the benefit
+// of the public at large and to the detriment of our heirs and
+// successors. We intend this dedication to be an overt act of
+// relinquishment in perpetuity of all present and future rights to this
+// software under copyright law.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// For more information, please refer to <http://unlicense.org/>
+
+#ifndef SHEREDOM_UTF8_H_INCLUDED
+#define SHEREDOM_UTF8_H_INCLUDED
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+
+// disable 'bytes padding added after construct' warning
+#pragma warning(disable : 4820)
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if defined(_MSC_VER)
+typedef __int32 utf8_int32_t;
+#else
+#include <stdint.h>
+typedef int32_t utf8_int32_t;
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#define utf8_nonnull __attribute__((nonnull))
+#define utf8_pure __attribute__((pure))
+#define utf8_restrict __restrict__
+#define utf8_weak __attribute__((weak))
+#elif defined(_MSC_VER)
+#define utf8_nonnull
+#define utf8_pure
+#define utf8_restrict __restrict
+#define utf8_weak __inline
+#else
+#error Non clang, non gcc, non MSVC compiler found!
+#endif
+
+#ifdef __cplusplus
+#define utf8_null NULL
+#else
+#define utf8_null 0
+#endif
+
+// Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 >
+// src2 respectively, case insensitive.
+utf8_nonnull utf8_pure utf8_weak int utf8casecmp(const void *src1,
+ const void *src2);
+
+// Append the utf8 string src onto the utf8 string dst.
+utf8_nonnull utf8_weak void *utf8cat(void *utf8_restrict dst,
+ const void *utf8_restrict src);
+
+// Find the first match of the utf8 codepoint chr in the utf8 string src.
+utf8_nonnull utf8_pure utf8_weak void *utf8chr(const void *src,
+ utf8_int32_t chr);
+
+// Return less than 0, 0, greater than 0 if src1 < src2,
+// src1 == src2, src1 > src2 respectively.
+utf8_nonnull utf8_pure utf8_weak int utf8cmp(const void *src1,
+ const void *src2);
+
+// Copy the utf8 string src onto the memory allocated in dst.
+utf8_nonnull utf8_weak void *utf8cpy(void *utf8_restrict dst,
+ const void *utf8_restrict src);
+
+// Number of utf8 codepoints in the utf8 string src that consists entirely
+// of utf8 codepoints not from the utf8 string reject.
+utf8_nonnull utf8_pure utf8_weak size_t utf8cspn(const void *src,
+ const void *reject);
+
+// Duplicate the utf8 string src by getting its size, malloc'ing a new buffer
+// copying over the data, and returning that. Or 0 if malloc failed.
+utf8_nonnull utf8_weak void *utf8dup(const void *src);
+
+// Number of utf8 codepoints in the utf8 string str,
+// excluding the null terminating byte.
+utf8_nonnull utf8_pure utf8_weak size_t utf8len(const void *str);
+
+// Return less than 0, 0, greater than 0 if src1 < src2, src1 == src2, src1 >
+// src2 respectively, case insensitive. Checking at most n bytes of each utf8
+// string.
+utf8_nonnull utf8_pure utf8_weak int utf8ncasecmp(const void *src1,
+ const void *src2, size_t n);
+
+// Append the utf8 string src onto the utf8 string dst,
+// writing at most n+1 bytes. Can produce an invalid utf8
+// string if n falls partway through a utf8 codepoint.
+utf8_nonnull utf8_weak void *utf8ncat(void *utf8_restrict dst,
+ const void *utf8_restrict src, size_t n);
+
+// Return less than 0, 0, greater than 0 if src1 < src2,
+// src1 == src2, src1 > src2 respectively. Checking at most n
+// bytes of each utf8 string.
+utf8_nonnull utf8_pure utf8_weak int utf8ncmp(const void *src1,
+ const void *src2, size_t n);
+
+// Copy the utf8 string src onto the memory allocated in dst.
+// Copies at most n bytes. If there is no terminating null byte in
+// the first n bytes of src, the string placed into dst will not be
+// null-terminated. If the size (in bytes) of src is less than n,
+// extra null terminating bytes are appended to dst such that at
+// total of n bytes are written. Can produce an invalid utf8
+// string if n falls partway through a utf8 codepoint.
+utf8_nonnull utf8_weak void *utf8ncpy(void *utf8_restrict dst,
+ const void *utf8_restrict src, size_t n);
+
+// Similar to utf8dup, except that at most n bytes of src are copied. If src is
+// longer than n, only n bytes are copied and a null byte is added.
+//
+// Returns a new string if successful, 0 otherwise
+utf8_nonnull utf8_weak void *utf8ndup(const void *src, size_t n);
+
+// Locates the first occurence in the utf8 string str of any byte in the
+// utf8 string accept, or 0 if no match was found.
+utf8_nonnull utf8_pure utf8_weak void *utf8pbrk(const void *str,
+ const void *accept);
+
+// Find the last match of the utf8 codepoint chr in the utf8 string src.
+utf8_nonnull utf8_pure utf8_weak void *utf8rchr(const void *src, int chr);
+
+// Number of bytes in the utf8 string str,
+// including the null terminating byte.
+utf8_nonnull utf8_pure utf8_weak size_t utf8size(const void *str);
+
+// Number of utf8 codepoints in the utf8 string src that consists entirely
+// of utf8 codepoints from the utf8 string accept.
+utf8_nonnull utf8_pure utf8_weak size_t utf8spn(const void *src,
+ const void *accept);
+
+// The position of the utf8 string needle in the utf8 string haystack.
+utf8_nonnull utf8_pure utf8_weak void *utf8str(const void *haystack,
+ const void *needle);
+
+// The position of the utf8 string needle in the utf8 string haystack, case
+// insensitive.
+utf8_nonnull utf8_pure utf8_weak void *utf8casestr(const void *haystack,
+ const void *needle);
+
+// Return 0 on success, or the position of the invalid
+// utf8 codepoint on failure.
+utf8_nonnull utf8_pure utf8_weak void *utf8valid(const void *str);
+
+// Sets out_codepoint to the next utf8 codepoint in str, and returns the address
+// of the utf8 codepoint after the current one in str.
+utf8_nonnull utf8_weak void *
+utf8codepoint(const void *utf8_restrict str,
+ utf8_int32_t *utf8_restrict out_codepoint);
+
+// Returns the size of the given codepoint in bytes.
+utf8_weak size_t utf8codepointsize(utf8_int32_t chr);
+
+// Write a codepoint to the given string, and return the address to the next
+// place after the written codepoint. Pass how many bytes left in the buffer to
+// n. If there is not enough space for the codepoint, this function returns
+// null.
+utf8_nonnull utf8_weak void *utf8catcodepoint(void *utf8_restrict str,
+ utf8_int32_t chr, size_t n);
+
+// Returns 1 if the given character is lowercase, or 0 if it is not.
+utf8_weak int utf8islower(utf8_int32_t chr);
+
+// Returns 1 if the given character is uppercase, or 0 if it is not.
+utf8_weak int utf8isupper(utf8_int32_t chr);
+
+// Transform the given string into all lowercase codepoints.
+utf8_nonnull utf8_weak void utf8lwr(void *utf8_restrict str);
+
+// Transform the given string into all uppercase codepoints.
+utf8_nonnull utf8_weak void utf8upr(void *utf8_restrict str);
+
+// Make a codepoint lower case if possible.
+utf8_weak utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp);
+
+// Make a codepoint upper case if possible.
+utf8_weak utf8_int32_t utf8uprcodepoint(utf8_int32_t cp);
+
+#undef utf8_weak
+#undef utf8_pure
+#undef utf8_nonnull
+
+int utf8casecmp(const void *src1, const void *src2) {
+ utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp;
+
+ for (;;) {
+ src1 = utf8codepoint(src1, &src1_cp);
+ src2 = utf8codepoint(src2, &src2_cp);
+
+ // Take a copy of src1 & src2
+ src1_orig_cp = src1_cp;
+ src2_orig_cp = src2_cp;
+
+ // Lower the srcs if required
+ src1_cp = utf8lwrcodepoint(src1_cp);
+ src2_cp = utf8lwrcodepoint(src2_cp);
+
+ // Check if the lowered codepoints match
+ if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) {
+ return 0;
+ } else if (src1_cp == src2_cp) {
+ continue;
+ }
+
+ // If they don't match, then we return which of the original's are less
+ if (src1_orig_cp < src2_orig_cp) {
+ return -1;
+ } else if (src1_orig_cp > src2_orig_cp) {
+ return 1;
+ }
+ }
+}
+
+void *utf8cat(void *utf8_restrict dst, const void *utf8_restrict src) {
+ char *d = (char *)dst;
+ const char *s = (const char *)src;
+
+ // find the null terminating byte in dst
+ while ('\0' != *d) {
+ d++;
+ }
+
+ // overwriting the null terminating byte in dst, append src byte-by-byte
+ while ('\0' != *s) {
+ *d++ = *s++;
+ }
+
+ // write out a new null terminating byte into dst
+ *d = '\0';
+
+ return dst;
+}
+
+void *utf8chr(const void *src, utf8_int32_t chr) {
+ char c[5] = {'\0', '\0', '\0', '\0', '\0'};
+
+ if (0 == chr) {
+ // being asked to return position of null terminating byte, so
+ // just run s to the end, and return!
+ const char *s = (const char *)src;
+ while ('\0' != *s) {
+ s++;
+ }
+ return (void *)s;
+ } else if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
+ // 1-byte/7-bit ascii
+ // (0b0xxxxxxx)
+ c[0] = (char)chr;
+ } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
+ // 2-byte/11-bit utf8 code point
+ // (0b110xxxxx 0b10xxxxxx)
+ c[0] = 0xc0 | (char)(chr >> 6);
+ c[1] = 0x80 | (char)(chr & 0x3f);
+ } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
+ // 3-byte/16-bit utf8 code point
+ // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
+ c[0] = 0xe0 | (char)(chr >> 12);
+ c[1] = 0x80 | (char)((chr >> 6) & 0x3f);
+ c[2] = 0x80 | (char)(chr & 0x3f);
+ } else { // if (0 == ((int)0xffe00000 & chr)) {
+ // 4-byte/21-bit utf8 code point
+ // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
+ c[0] = 0xf0 | (char)(chr >> 18);
+ c[1] = 0x80 | (char)((chr >> 12) & 0x3f);
+ c[2] = 0x80 | (char)((chr >> 6) & 0x3f);
+ c[3] = 0x80 | (char)(chr & 0x3f);
+ }
+
+ // we've made c into a 2 utf8 codepoint string, one for the chr we are
+ // seeking, another for the null terminating byte. Now use utf8str to
+ // search
+ return utf8str(src, c);
+}
+
+int utf8cmp(const void *src1, const void *src2) {
+ const unsigned char *s1 = (const unsigned char *)src1;
+ const unsigned char *s2 = (const unsigned char *)src2;
+
+ while (('\0' != *s1) || ('\0' != *s2)) {
+ if (*s1 < *s2) {
+ return -1;
+ } else if (*s1 > *s2) {
+ return 1;
+ }
+
+ s1++;
+ s2++;
+ }
+
+ // both utf8 strings matched
+ return 0;
+}
+
+int utf8coll(const void *src1, const void *src2);
+
+void *utf8cpy(void *utf8_restrict dst, const void *utf8_restrict src) {
+ char *d = (char *)dst;
+ const char *s = (const char *)src;
+
+ // overwriting anything previously in dst, write byte-by-byte
+ // from src
+ while ('\0' != *s) {
+ *d++ = *s++;
+ }
+
+ // append null terminating byte
+ *d = '\0';
+
+ return dst;
+}
+
+size_t utf8cspn(const void *src, const void *reject) {
+ const char *s = (const char *)src;
+ size_t chars = 0;
+
+ while ('\0' != *s) {
+ const char *r = (const char *)reject;
+ size_t offset = 0;
+
+ while ('\0' != *r) {
+ // checking that if *r is the start of a utf8 codepoint
+ // (it is not 0b10xxxxxx) and we have successfully matched
+ // a previous character (0 < offset) - we found a match
+ if ((0x80 != (0xc0 & *r)) && (0 < offset)) {
+ return chars;
+ } else {
+ if (*r == s[offset]) {
+ // part of a utf8 codepoint matched, so move our checking
+ // onwards to the next byte
+ offset++;
+ r++;
+ } else {
+ // r could be in the middle of an unmatching utf8 code point,
+ // so we need to march it on to the next character beginning,
+
+ do {
+ r++;
+ } while (0x80 == (0xc0 & *r));
+
+ // reset offset too as we found a mismatch
+ offset = 0;
+ }
+ }
+ }
+
+ // the current utf8 codepoint in src did not match reject, but src
+ // could have been partway through a utf8 codepoint, so we need to
+ // march it onto the next utf8 codepoint starting byte
+ do {
+ s++;
+ } while ((0x80 == (0xc0 & *s)));
+ chars++;
+ }
+
+ return chars;
+}
+
+size_t utf8size(const void *str);
+
+void *utf8dup(const void *src) {
+ const char *s = (const char *)src;
+ char *n = utf8_null;
+
+ // figure out how many bytes (including the terminator) we need to copy first
+ size_t bytes = utf8size(src);
+
+ n = (char *)malloc(bytes);
+
+ if (utf8_null == n) {
+ // out of memory so we bail
+ return utf8_null;
+ } else {
+ bytes = 0;
+
+ // copy src byte-by-byte into our new utf8 string
+ while ('\0' != s[bytes]) {
+ n[bytes] = s[bytes];
+ bytes++;
+ }
+
+ // append null terminating byte
+ n[bytes] = '\0';
+ return n;
+ }
+}
+
+void *utf8fry(const void *str);
+
+size_t utf8len(const void *str) {
+ const unsigned char *s = (const unsigned char *)str;
+ size_t length = 0;
+
+ while ('\0' != *s) {
+ if (0xf0 == (0xf8 & *s)) {
+ // 4-byte utf8 code point (began with 0b11110xxx)
+ s += 4;
+ } else if (0xe0 == (0xf0 & *s)) {
+ // 3-byte utf8 code point (began with 0b1110xxxx)
+ s += 3;
+ } else if (0xc0 == (0xe0 & *s)) {
+ // 2-byte utf8 code point (began with 0b110xxxxx)
+ s += 2;
+ } else { // if (0x00 == (0x80 & *s)) {
+ // 1-byte ascii (began with 0b0xxxxxxx)
+ s += 1;
+ }
+
+ // no matter the bytes we marched s forward by, it was
+ // only 1 utf8 codepoint
+ length++;
+ }
+
+ return length;
+}
+
+int utf8ncasecmp(const void *src1, const void *src2, size_t n) {
+ utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp;
+
+ do {
+ const unsigned char *const s1 = (const unsigned char *)src1;
+ const unsigned char *const s2 = (const unsigned char *)src2;
+
+ // first check that we have enough bytes left in n to contain an entire
+ // codepoint
+ if (0 == n) {
+ return 0;
+ }
+
+ if ((1 == n) && ((0xc0 == (0xe0 & *s1)) || (0xc0 == (0xe0 & *s2)))) {
+ const utf8_int32_t c1 = (0xe0 & *s1);
+ const utf8_int32_t c2 = (0xe0 & *s2);
+
+ if (c1 < c2) {
+ return -1;
+ } else if (c1 > c2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ if ((2 >= n) && ((0xe0 == (0xf0 & *s1)) || (0xe0 == (0xf0 & *s2)))) {
+ const utf8_int32_t c1 = (0xf0 & *s1);
+ const utf8_int32_t c2 = (0xf0 & *s2);
+
+ if (c1 < c2) {
+ return -1;
+ } else if (c1 > c2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ if ((3 >= n) && ((0xf0 == (0xf8 & *s1)) || (0xf0 == (0xf8 & *s2)))) {
+ const utf8_int32_t c1 = (0xf8 & *s1);
+ const utf8_int32_t c2 = (0xf8 & *s2);
+
+ if (c1 < c2) {
+ return -1;
+ } else if (c1 > c2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ src1 = utf8codepoint(src1, &src1_cp);
+ src2 = utf8codepoint(src2, &src2_cp);
+ n -= utf8codepointsize(src1_cp);
+
+ // Take a copy of src1 & src2
+ src1_orig_cp = src1_cp;
+ src2_orig_cp = src2_cp;
+
+ // Lower srcs if required
+ src1_cp = utf8lwrcodepoint(src1_cp);
+ src2_cp = utf8lwrcodepoint(src2_cp);
+
+ // Check if the lowered codepoints match
+ if ((0 == src1_orig_cp) && (0 == src2_orig_cp)) {
+ return 0;
+ } else if (src1_cp == src2_cp) {
+ continue;
+ }
+
+ // If they don't match, then we return which of the original's are less
+ if (src1_orig_cp < src2_orig_cp) {
+ return -1;
+ } else if (src1_orig_cp > src2_orig_cp) {
+ return 1;
+ }
+ } while (0 < n);
+
+ // both utf8 strings matched
+ return 0;
+}
+
+void *utf8ncat(void *utf8_restrict dst, const void *utf8_restrict src,
+ size_t n) {
+ char *d = (char *)dst;
+ const char *s = (const char *)src;
+
+ // find the null terminating byte in dst
+ while ('\0' != *d) {
+ d++;
+ }
+
+ // overwriting the null terminating byte in dst, append src byte-by-byte
+ // stopping if we run out of space
+ do {
+ *d++ = *s++;
+ } while (('\0' != *s) && (0 != --n));
+
+ // write out a new null terminating byte into dst
+ *d = '\0';
+
+ return dst;
+}
+
+int utf8ncmp(const void *src1, const void *src2, size_t n) {
+ const unsigned char *s1 = (const unsigned char *)src1;
+ const unsigned char *s2 = (const unsigned char *)src2;
+
+ while ((('\0' != *s1) || ('\0' != *s2)) && (0 != n--)) {
+ if (*s1 < *s2) {
+ return -1;
+ } else if (*s1 > *s2) {
+ return 1;
+ }
+
+ s1++;
+ s2++;
+ }
+
+ // both utf8 strings matched
+ return 0;
+}
+
+void *utf8ncpy(void *utf8_restrict dst, const void *utf8_restrict src,
+ size_t n) {
+ char *d = (char *)dst;
+ const char *s = (const char *)src;
+
+ // overwriting anything previously in dst, write byte-by-byte
+ // from src
+ do {
+ *d++ = *s++;
+ } while (('\0' != *s) && (0 != --n));
+
+ // append null terminating byte
+ while (0 != n) {
+ *d++ = '\0';
+ n--;
+ }
+
+ return dst;
+}
+
+void *utf8ndup(const void *src, size_t n) {
+ const char *s = (const char *)src;
+ char *c = utf8_null;
+ size_t bytes = 0;
+
+ // Find the end of the string or stop when n is reached
+ while ('\0' != s[bytes] && bytes < n) {
+ bytes++;
+ }
+
+ // In case bytes is actually less than n, we need to set it
+ // to be used later in the copy byte by byte.
+ n = bytes;
+
+ c = (char *)malloc(bytes + 1);
+ if (utf8_null == c) {
+ // out of memory so we bail
+ return utf8_null;
+ }
+
+ bytes = 0;
+
+ // copy src byte-by-byte into our new utf8 string
+ while ('\0' != s[bytes] && bytes < n) {
+ c[bytes] = s[bytes];
+ bytes++;
+ }
+
+ // append null terminating byte
+ c[bytes] = '\0';
+ return c;
+}
+
+void *utf8rchr(const void *src, int chr) {
+ const char *s = (const char *)src;
+ const char *match = utf8_null;
+ char c[5] = {'\0', '\0', '\0', '\0', '\0'};
+
+ if (0 == chr) {
+ // being asked to return position of null terminating byte, so
+ // just run s to the end, and return!
+ while ('\0' != *s) {
+ s++;
+ }
+ return (void *)s;
+ } else if (0 == ((int)0xffffff80 & chr)) {
+ // 1-byte/7-bit ascii
+ // (0b0xxxxxxx)
+ c[0] = (char)chr;
+ } else if (0 == ((int)0xfffff800 & chr)) {
+ // 2-byte/11-bit utf8 code point
+ // (0b110xxxxx 0b10xxxxxx)
+ c[0] = 0xc0 | (char)(chr >> 6);
+ c[1] = 0x80 | (char)(chr & 0x3f);
+ } else if (0 == ((int)0xffff0000 & chr)) {
+ // 3-byte/16-bit utf8 code point
+ // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
+ c[0] = 0xe0 | (char)(chr >> 12);
+ c[1] = 0x80 | (char)((chr >> 6) & 0x3f);
+ c[2] = 0x80 | (char)(chr & 0x3f);
+ } else { // if (0 == ((int)0xffe00000 & chr)) {
+ // 4-byte/21-bit utf8 code point
+ // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
+ c[0] = 0xf0 | (char)(chr >> 18);
+ c[1] = 0x80 | (char)((chr >> 12) & 0x3f);
+ c[2] = 0x80 | (char)((chr >> 6) & 0x3f);
+ c[3] = 0x80 | (char)(chr & 0x3f);
+ }
+
+ // we've created a 2 utf8 codepoint string in c that is
+ // the utf8 character asked for by chr, and a null
+ // terminating byte
+
+ while ('\0' != *s) {
+ size_t offset = 0;
+
+ while (s[offset] == c[offset]) {
+ offset++;
+ }
+
+ if ('\0' == c[offset]) {
+ // we found a matching utf8 code point
+ match = s;
+ s += offset;
+ } else {
+ s += offset;
+
+ // need to march s along to next utf8 codepoint start
+ // (the next byte that doesn't match 0b10xxxxxx)
+ if ('\0' != *s) {
+ do {
+ s++;
+ } while (0x80 == (0xc0 & *s));
+ }
+ }
+ }
+
+ // return the last match we found (or 0 if no match was found)
+ return (void *)match;
+}
+
+void *utf8pbrk(const void *str, const void *accept) {
+ const char *s = (const char *)str;
+
+ while ('\0' != *s) {
+ const char *a = (const char *)accept;
+ size_t offset = 0;
+
+ while ('\0' != *a) {
+ // checking that if *a is the start of a utf8 codepoint
+ // (it is not 0b10xxxxxx) and we have successfully matched
+ // a previous character (0 < offset) - we found a match
+ if ((0x80 != (0xc0 & *a)) && (0 < offset)) {
+ return (void *)s;
+ } else {
+ if (*a == s[offset]) {
+ // part of a utf8 codepoint matched, so move our checking
+ // onwards to the next byte
+ offset++;
+ a++;
+ } else {
+ // r could be in the middle of an unmatching utf8 code point,
+ // so we need to march it on to the next character beginning,
+
+ do {
+ a++;
+ } while (0x80 == (0xc0 & *a));
+
+ // reset offset too as we found a mismatch
+ offset = 0;
+ }
+ }
+ }
+
+ // we found a match on the last utf8 codepoint
+ if (0 < offset) {
+ return (void *)s;
+ }
+
+ // the current utf8 codepoint in src did not match accept, but src
+ // could have been partway through a utf8 codepoint, so we need to
+ // march it onto the next utf8 codepoint starting byte
+ do {
+ s++;
+ } while ((0x80 == (0xc0 & *s)));
+ }
+
+ return utf8_null;
+}
+
+size_t utf8size(const void *str) {
+ const char *s = (const char *)str;
+ size_t size = 0;
+ while ('\0' != s[size]) {
+ size++;
+ }
+
+ // we are including the null terminating byte in the size calculation
+ size++;
+ return size;
+}
+
+size_t utf8spn(const void *src, const void *accept) {
+ const char *s = (const char *)src;
+ size_t chars = 0;
+
+ while ('\0' != *s) {
+ const char *a = (const char *)accept;
+ size_t offset = 0;
+
+ while ('\0' != *a) {
+ // checking that if *r is the start of a utf8 codepoint
+ // (it is not 0b10xxxxxx) and we have successfully matched
+ // a previous character (0 < offset) - we found a match
+ if ((0x80 != (0xc0 & *a)) && (0 < offset)) {
+ // found a match, so increment the number of utf8 codepoints
+ // that have matched and stop checking whether any other utf8
+ // codepoints in a match
+ chars++;
+ s += offset;
+ break;
+ } else {
+ if (*a == s[offset]) {
+ offset++;
+ a++;
+ } else {
+ // a could be in the middle of an unmatching utf8 codepoint,
+ // so we need to march it on to the next character beginning,
+ do {
+ a++;
+ } while (0x80 == (0xc0 & *a));
+
+ // reset offset too as we found a mismatch
+ offset = 0;
+ }
+ }
+ }
+
+ // if a got to its terminating null byte, then we didn't find a match.
+ // Return the current number of matched utf8 codepoints
+ if ('\0' == *a) {
+ return chars;
+ }
+ }
+
+ return chars;
+}
+
+void *utf8str(const void *haystack, const void *needle) {
+ const char *h = (const char *)haystack;
+
+ // if needle has no utf8 codepoints before the null terminating
+ // byte then return haystack
+ if ('\0' == *((const char *)needle)) {
+ return (void *)haystack;
+ }
+
+ while ('\0' != *h) {
+ const char *maybeMatch = h;
+ const char *n = (const char *)needle;
+
+ while (*h == *n && (*h != '\0' && *n != '\0')) {
+ n++;
+ h++;
+ }
+
+ if ('\0' == *n) {
+ // we found the whole utf8 string for needle in haystack at
+ // maybeMatch, so return it
+ return (void *)maybeMatch;
+ } else {
+ // h could be in the middle of an unmatching utf8 codepoint,
+ // so we need to march it on to the next character beginning,
+ if ('\0' != *h) {
+ do {
+ h++;
+ } while (0x80 == (0xc0 & *h));
+ }
+ }
+ }
+
+ // no match
+ return utf8_null;
+}
+
+void *utf8casestr(const void *haystack, const void *needle) {
+ const void *h = haystack;
+
+ // if needle has no utf8 codepoints before the null terminating
+ // byte then return haystack
+ if ('\0' == *((const char *)needle)) {
+ return (void *)haystack;
+ }
+
+ for (;;) {
+ const void *maybeMatch = h;
+ const void *n = needle;
+ utf8_int32_t h_cp, n_cp;
+
+ h = utf8codepoint(h, &h_cp);
+ n = utf8codepoint(n, &n_cp);
+
+ while ((0 != h_cp) && (0 != n_cp)) {
+ h_cp = utf8lwrcodepoint(h_cp);
+ n_cp = utf8lwrcodepoint(n_cp);
+
+ // if we find a mismatch, bail out!
+ if (h_cp != n_cp) {
+ break;
+ }
+
+ h = utf8codepoint(h, &h_cp);
+ n = utf8codepoint(n, &n_cp);
+ }
+
+ if (0 == n_cp) {
+ // we found the whole utf8 string for needle in haystack at
+ // maybeMatch, so return it
+ return (void *)maybeMatch;
+ }
+
+ if (0 == h_cp) {
+ // no match
+ return utf8_null;
+ }
+ }
+}
+
+void *utf8valid(const void *str) {
+ const char *s = (const char *)str;
+
+ while ('\0' != *s) {
+ if (0xf0 == (0xf8 & *s)) {
+ // ensure each of the 3 following bytes in this 4-byte
+ // utf8 codepoint began with 0b10xxxxxx
+ if ((0x80 != (0xc0 & s[1])) || (0x80 != (0xc0 & s[2])) ||
+ (0x80 != (0xc0 & s[3]))) {
+ return (void *)s;
+ }
+
+ // ensure that our utf8 codepoint ended after 4 bytes
+ if (0x80 == (0xc0 & s[4])) {
+ return (void *)s;
+ }
+
+ // ensure that the top 5 bits of this 4-byte utf8
+ // codepoint were not 0, as then we could have used
+ // one of the smaller encodings
+ if ((0 == (0x07 & s[0])) && (0 == (0x30 & s[1]))) {
+ return (void *)s;
+ }
+
+ // 4-byte utf8 code point (began with 0b11110xxx)
+ s += 4;
+ } else if (0xe0 == (0xf0 & *s)) {
+ // ensure each of the 2 following bytes in this 3-byte
+ // utf8 codepoint began with 0b10xxxxxx
+ if ((0x80 != (0xc0 & s[1])) || (0x80 != (0xc0 & s[2]))) {
+ return (void *)s;
+ }
+
+ // ensure that our utf8 codepoint ended after 3 bytes
+ if (0x80 == (0xc0 & s[3])) {
+ return (void *)s;
+ }
+
+ // ensure that the top 5 bits of this 3-byte utf8
+ // codepoint were not 0, as then we could have used
+ // one of the smaller encodings
+ if ((0 == (0x0f & s[0])) && (0 == (0x20 & s[1]))) {
+ return (void *)s;
+ }
+
+ // 3-byte utf8 code point (began with 0b1110xxxx)
+ s += 3;
+ } else if (0xc0 == (0xe0 & *s)) {
+ // ensure the 1 following byte in this 2-byte
+ // utf8 codepoint began with 0b10xxxxxx
+ if (0x80 != (0xc0 & s[1])) {
+ return (void *)s;
+ }
+
+ // ensure that our utf8 codepoint ended after 2 bytes
+ if (0x80 == (0xc0 & s[2])) {
+ return (void *)s;
+ }
+
+ // ensure that the top 4 bits of this 2-byte utf8
+ // codepoint were not 0, as then we could have used
+ // one of the smaller encodings
+ if (0 == (0x1e & s[0])) {
+ return (void *)s;
+ }
+
+ // 2-byte utf8 code point (began with 0b110xxxxx)
+ s += 2;
+ } else if (0x00 == (0x80 & *s)) {
+ // 1-byte ascii (began with 0b0xxxxxxx)
+ s += 1;
+ } else {
+ // we have an invalid 0b1xxxxxxx utf8 code point entry
+ return (void *)s;
+ }
+ }
+
+ return utf8_null;
+}
+
+void *utf8codepoint(const void *utf8_restrict str,
+ utf8_int32_t *utf8_restrict out_codepoint) {
+ const char *s = (const char *)str;
+
+ if (0xf0 == (0xf8 & s[0])) {
+ // 4 byte utf8 codepoint
+ *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) |
+ ((0x3f & s[2]) << 6) | (0x3f & s[3]);
+ s += 4;
+ } else if (0xe0 == (0xf0 & s[0])) {
+ // 3 byte utf8 codepoint
+ *out_codepoint =
+ ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]);
+ s += 3;
+ } else if (0xc0 == (0xe0 & s[0])) {
+ // 2 byte utf8 codepoint
+ *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]);
+ s += 2;
+ } else {
+ // 1 byte utf8 codepoint otherwise
+ *out_codepoint = s[0];
+ s += 1;
+ }
+
+ return (void *)s;
+}
+
+size_t utf8codepointsize(utf8_int32_t chr) {
+ if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
+ return 1;
+ } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
+ return 2;
+ } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
+ return 3;
+ } else { // if (0 == ((int)0xffe00000 & chr)) {
+ return 4;
+ }
+}
+
+void *utf8catcodepoint(void *utf8_restrict str, utf8_int32_t chr, size_t n) {
+ char *s = (char *)str;
+
+ if (0 == ((utf8_int32_t)0xffffff80 & chr)) {
+ // 1-byte/7-bit ascii
+ // (0b0xxxxxxx)
+ if (n < 1) {
+ return utf8_null;
+ }
+ s[0] = (char)chr;
+ s += 1;
+ } else if (0 == ((utf8_int32_t)0xfffff800 & chr)) {
+ // 2-byte/11-bit utf8 code point
+ // (0b110xxxxx 0b10xxxxxx)
+ if (n < 2) {
+ return utf8_null;
+ }
+ s[0] = 0xc0 | (char)(chr >> 6);
+ s[1] = 0x80 | (char)(chr & 0x3f);
+ s += 2;
+ } else if (0 == ((utf8_int32_t)0xffff0000 & chr)) {
+ // 3-byte/16-bit utf8 code point
+ // (0b1110xxxx 0b10xxxxxx 0b10xxxxxx)
+ if (n < 3) {
+ return utf8_null;
+ }
+ s[0] = 0xe0 | (char)(chr >> 12);
+ s[1] = 0x80 | (char)((chr >> 6) & 0x3f);
+ s[2] = 0x80 | (char)(chr & 0x3f);
+ s += 3;
+ } else { // if (0 == ((int)0xffe00000 & chr)) {
+ // 4-byte/21-bit utf8 code point
+ // (0b11110xxx 0b10xxxxxx 0b10xxxxxx 0b10xxxxxx)
+ if (n < 4) {
+ return utf8_null;
+ }
+ s[0] = 0xf0 | (char)(chr >> 18);
+ s[1] = 0x80 | (char)((chr >> 12) & 0x3f);
+ s[2] = 0x80 | (char)((chr >> 6) & 0x3f);
+ s[3] = 0x80 | (char)(chr & 0x3f);
+ s += 4;
+ }
+
+ return s;
+}
+
+int utf8islower(utf8_int32_t chr) { return chr != utf8uprcodepoint(chr); }
+
+int utf8isupper(utf8_int32_t chr) { return chr != utf8lwrcodepoint(chr); }
+
+void utf8lwr(void *utf8_restrict str) {
+ void *p, *pn;
+ utf8_int32_t cp;
+
+ p = (char *)str;
+ pn = utf8codepoint(p, &cp);
+
+ while (cp != 0) {
+ const utf8_int32_t lwr_cp = utf8lwrcodepoint(cp);
+ const size_t size = utf8codepointsize(lwr_cp);
+
+ if (lwr_cp != cp) {
+ utf8catcodepoint(p, lwr_cp, size);
+ }
+
+ p = pn;
+ pn = utf8codepoint(p, &cp);
+ }
+}
+
+void utf8upr(void *utf8_restrict str) {
+ void *p, *pn;
+ utf8_int32_t cp;
+
+ p = (char *)str;
+ pn = utf8codepoint(p, &cp);
+
+ while (cp != 0) {
+ const utf8_int32_t lwr_cp = utf8uprcodepoint(cp);
+ const size_t size = utf8codepointsize(lwr_cp);
+
+ if (lwr_cp != cp) {
+ utf8catcodepoint(p, lwr_cp, size);
+ }
+
+ p = pn;
+ pn = utf8codepoint(p, &cp);
+ }
+}
+
+utf8_int32_t utf8lwrcodepoint(utf8_int32_t cp) {
+ if (((0x0041 <= cp) && (0x005a >= cp)) ||
+ ((0x00c0 <= cp) && (0x00d6 >= cp)) ||
+ ((0x00d8 <= cp) && (0x00de >= cp)) ||
+ ((0x0391 <= cp) && (0x03a1 >= cp)) ||
+ ((0x03a3 <= cp) && (0x03ab >= cp))) {
+ cp += 32;
+ } else if (((0x0100 <= cp) && (0x012f >= cp)) ||
+ ((0x0132 <= cp) && (0x0137 >= cp)) ||
+ ((0x014a <= cp) && (0x0177 >= cp)) ||
+ ((0x0182 <= cp) && (0x0185 >= cp)) ||
+ ((0x01a0 <= cp) && (0x01a5 >= cp)) ||
+ ((0x01de <= cp) && (0x01ef >= cp)) ||
+ ((0x01f8 <= cp) && (0x021f >= cp)) ||
+ ((0x0222 <= cp) && (0x0233 >= cp)) ||
+ ((0x0246 <= cp) && (0x024f >= cp)) ||
+ ((0x03d8 <= cp) && (0x03ef >= cp))) {
+ cp |= 0x1;
+ } else if (((0x0139 <= cp) && (0x0148 >= cp)) ||
+ ((0x0179 <= cp) && (0x017e >= cp)) ||
+ ((0x01af <= cp) && (0x01b0 >= cp)) ||
+ ((0x01b3 <= cp) && (0x01b6 >= cp)) ||
+ ((0x01cd <= cp) && (0x01dc >= cp))) {
+ cp += 1;
+ cp &= ~0x1;
+ } else {
+ switch (cp) {
+ default: break;
+ case 0x0178: cp = 0x00ff; break;
+ case 0x0243: cp = 0x0180; break;
+ case 0x018e: cp = 0x01dd; break;
+ case 0x023d: cp = 0x019a; break;
+ case 0x0220: cp = 0x019e; break;
+ case 0x01b7: cp = 0x0292; break;
+ case 0x01c4: cp = 0x01c6; break;
+ case 0x01c7: cp = 0x01c9; break;
+ case 0x01ca: cp = 0x01cc; break;
+ case 0x01f1: cp = 0x01f3; break;
+ case 0x01f7: cp = 0x01bf; break;
+ case 0x0187: cp = 0x0188; break;
+ case 0x018b: cp = 0x018c; break;
+ case 0x0191: cp = 0x0192; break;
+ case 0x0198: cp = 0x0199; break;
+ case 0x01a7: cp = 0x01a8; break;
+ case 0x01ac: cp = 0x01ad; break;
+ case 0x01af: cp = 0x01b0; break;
+ case 0x01b8: cp = 0x01b9; break;
+ case 0x01bc: cp = 0x01bd; break;
+ case 0x01f4: cp = 0x01f5; break;
+ case 0x023b: cp = 0x023c; break;
+ case 0x0241: cp = 0x0242; break;
+ case 0x03fd: cp = 0x037b; break;
+ case 0x03fe: cp = 0x037c; break;
+ case 0x03ff: cp = 0x037d; break;
+ case 0x037f: cp = 0x03f3; break;
+ case 0x0386: cp = 0x03ac; break;
+ case 0x0388: cp = 0x03ad; break;
+ case 0x0389: cp = 0x03ae; break;
+ case 0x038a: cp = 0x03af; break;
+ case 0x038c: cp = 0x03cc; break;
+ case 0x038e: cp = 0x03cd; break;
+ case 0x038f: cp = 0x03ce; break;
+ case 0x0370: cp = 0x0371; break;
+ case 0x0372: cp = 0x0373; break;
+ case 0x0376: cp = 0x0377; break;
+ case 0x03f4: cp = 0x03d1; break;
+ case 0x03cf: cp = 0x03d7; break;
+ case 0x03f9: cp = 0x03f2; break;
+ case 0x03f7: cp = 0x03f8; break;
+ case 0x03fa: cp = 0x03fb; break;
+ };
+ }
+
+ return cp;
+}
+
+utf8_int32_t utf8uprcodepoint(utf8_int32_t cp) {
+ if (((0x0061 <= cp) && (0x007a >= cp)) ||
+ ((0x00e0 <= cp) && (0x00f6 >= cp)) ||
+ ((0x00f8 <= cp) && (0x00fe >= cp)) ||
+ ((0x03b1 <= cp) && (0x03c1 >= cp)) ||
+ ((0x03c3 <= cp) && (0x03cb >= cp))) {
+ cp -= 32;
+ } else if (((0x0100 <= cp) && (0x012f >= cp)) ||
+ ((0x0132 <= cp) && (0x0137 >= cp)) ||
+ ((0x014a <= cp) && (0x0177 >= cp)) ||
+ ((0x0182 <= cp) && (0x0185 >= cp)) ||
+ ((0x01a0 <= cp) && (0x01a5 >= cp)) ||
+ ((0x01de <= cp) && (0x01ef >= cp)) ||
+ ((0x01f8 <= cp) && (0x021f >= cp)) ||
+ ((0x0222 <= cp) && (0x0233 >= cp)) ||
+ ((0x0246 <= cp) && (0x024f >= cp)) ||
+ ((0x03d8 <= cp) && (0x03ef >= cp))) {
+ cp &= ~0x1;
+ } else if (((0x0139 <= cp) && (0x0148 >= cp)) ||
+ ((0x0179 <= cp) && (0x017e >= cp)) ||
+ ((0x01af <= cp) && (0x01b0 >= cp)) ||
+ ((0x01b3 <= cp) && (0x01b6 >= cp)) ||
+ ((0x01cd <= cp) && (0x01dc >= cp))) {
+ cp -= 1;
+ cp |= 0x1;
+ } else {
+ switch (cp) {
+ default: break;
+ case 0x00ff: cp = 0x0178; break;
+ case 0x0180: cp = 0x0243; break;
+ case 0x01dd: cp = 0x018e; break;
+ case 0x019a: cp = 0x023d; break;
+ case 0x019e: cp = 0x0220; break;
+ case 0x0292: cp = 0x01b7; break;
+ case 0x01c6: cp = 0x01c4; break;
+ case 0x01c9: cp = 0x01c7; break;
+ case 0x01cc: cp = 0x01ca; break;
+ case 0x01f3: cp = 0x01f1; break;
+ case 0x01bf: cp = 0x01f7; break;
+ case 0x0188: cp = 0x0187; break;
+ case 0x018c: cp = 0x018b; break;
+ case 0x0192: cp = 0x0191; break;
+ case 0x0199: cp = 0x0198; break;
+ case 0x01a8: cp = 0x01a7; break;
+ case 0x01ad: cp = 0x01ac; break;
+ case 0x01b0: cp = 0x01af; break;
+ case 0x01b9: cp = 0x01b8; break;
+ case 0x01bd: cp = 0x01bc; break;
+ case 0x01f5: cp = 0x01f4; break;
+ case 0x023c: cp = 0x023b; break;
+ case 0x0242: cp = 0x0241; break;
+ case 0x037b: cp = 0x03fd; break;
+ case 0x037c: cp = 0x03fe; break;
+ case 0x037d: cp = 0x03ff; break;
+ case 0x03f3: cp = 0x037f; break;
+ case 0x03ac: cp = 0x0386; break;
+ case 0x03ad: cp = 0x0388; break;
+ case 0x03ae: cp = 0x0389; break;
+ case 0x03af: cp = 0x038a; break;
+ case 0x03cc: cp = 0x038c; break;
+ case 0x03cd: cp = 0x038e; break;
+ case 0x03ce: cp = 0x038f; break;
+ case 0x0371: cp = 0x0370; break;
+ case 0x0373: cp = 0x0372; break;
+ case 0x0377: cp = 0x0376; break;
+ case 0x03d1: cp = 0x03f4; break;
+ case 0x03d7: cp = 0x03cf; break;
+ case 0x03f2: cp = 0x03f9; break;
+ case 0x03f8: cp = 0x03f7; break;
+ case 0x03fb: cp = 0x03fa; break;
+ };
+ }
+
+ return cp;
+}
+
+#undef utf8_restrict
+#undef utf8_null
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif // SHEREDOM_UTF8_H_INCLUDED
diff --git a/deps/ntlmclient/util.c b/deps/ntlmclient/util.c
new file mode 100644
index 000000000..d0e3e53be
--- /dev/null
+++ b/deps/ntlmclient/util.c
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "compat.h"
+#include "util.h"
+
+void memzero(void *data, size_t size)
+{
+ volatile uint8_t *scan = (volatile uint8_t *)data;
+
+ while (size--)
+ *scan++ = 0x0;
+}
diff --git a/deps/ntlmclient/util.h b/deps/ntlmclient/util.h
new file mode 100644
index 000000000..1c1806ba3
--- /dev/null
+++ b/deps/ntlmclient/util.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) Edward Thomson. All rights reserved.
+ *
+ * This file is part of ntlmclient, distributed under the MIT license.
+ * For full terms and copyright information, and for third-party
+ * copyright information, see the included LICENSE.txt file.
+ */
+
+#ifndef PRIVATE_UTIL_H__
+#define PRIVATE_UTIL_H__
+
+extern void memzero(void *data, size_t size);
+
+#endif /* PRIVATE_UTIL_H__ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bf51c45bd..647bb097a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -396,6 +396,15 @@ ELSE()
ENDIF()
ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support")
+# Optional external dependency: ntlmclient
+IF (USE_NTLMCLIENT)
+ SET(GIT_NTLM 1)
+ ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient")
+ LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient")
+ LIST(APPEND LIBGIT2_OBJECTS "$<TARGET_OBJECTS:ntlmclient>")
+ENDIF()
+ADD_FEATURE_INFO(ntlmclient GIT_NTLM "NTLM authentication support for Unix")
+
# Optional external dependency: libgssapi
IF (USE_GSSAPI)
FIND_PACKAGE(GSSAPI)
diff --git a/src/features.h.in b/src/features.h.in
index 4090fadb5..f2931cb11 100644
--- a/src/features.h.in
+++ b/src/features.h.in
@@ -25,8 +25,10 @@
#cmakedefine GIT_SSH 1
#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
+#cmakedefine GIT_NTLM 1
#cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_WINHTTP 1
+#cmakedefine GIT_NTLM 1
#cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 1
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 000000000..e35abbb94
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,184 @@
+/*
+ * 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 "net.h"
+#include "netops.h"
+
+#include <ctype.h>
+#include "git2/errors.h"
+
+#include "posix.h"
+#include "buffer.h"
+#include "http_parser.h"
+#include "global.h"
+
+#define DEFAULT_PORT_HTTP "80"
+#define DEFAULT_PORT_HTTPS "443"
+#define DEFAULT_PORT_GIT "9418"
+#define DEFAULT_PORT_SSH "22"
+
+static const char *default_port_for_scheme(const char *scheme)
+{
+ if (strcmp(scheme, "http") == 0)
+ return DEFAULT_PORT_HTTP;
+ else if (strcmp(scheme, "https") == 0)
+ return DEFAULT_PORT_HTTPS;
+ else if (strcmp(scheme, "git") == 0)
+ return DEFAULT_PORT_GIT;
+ else if (strcmp(scheme, "ssh") == 0)
+ return DEFAULT_PORT_SSH;
+
+ return NULL;
+}
+
+int git_net_url_parse(git_net_url *url, const char *given)
+{
+ struct http_parser_url u = {0};
+ bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo;
+ git_buf scheme = GIT_BUF_INIT,
+ host = GIT_BUF_INIT,
+ port = GIT_BUF_INIT,
+ path = GIT_BUF_INIT,
+ username = GIT_BUF_INIT,
+ password = GIT_BUF_INIT,
+ query = GIT_BUF_INIT;
+ int error = GIT_EINVALIDSPEC;
+
+ if (http_parser_parse_url(given, strlen(given), false, &u)) {
+ git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
+ goto done;
+ }
+
+ has_scheme = !!(u.field_set & (1 << UF_SCHEMA));
+ has_host = !!(u.field_set & (1 << UF_HOST));
+ has_port = !!(u.field_set & (1 << UF_PORT));
+ has_path = !!(u.field_set & (1 << UF_PATH));
+ has_query = !!(u.field_set & (1 << UF_QUERY));
+ has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
+
+ if (has_scheme) {
+ const char *url_scheme = given + u.field_data[UF_SCHEMA].off;
+ size_t url_scheme_len = u.field_data[UF_SCHEMA].len;
+ git_buf_put(&scheme, url_scheme, url_scheme_len);
+ git__strntolower(scheme.ptr, scheme.size);
+ } else {
+ git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
+ goto done;
+ }
+
+ if (has_host) {
+ const char *url_host = given + u.field_data[UF_HOST].off;
+ size_t url_host_len = u.field_data[UF_HOST].len;
+ git_buf_decode_percent(&host, url_host, url_host_len);
+ }
+
+ if (has_port) {
+ const char *url_port = given + u.field_data[UF_PORT].off;
+ size_t url_port_len = u.field_data[UF_PORT].len;
+ git_buf_put(&port, url_port, url_port_len);
+ } else {
+ const char *default_port = default_port_for_scheme(scheme.ptr);
+
+ if (default_port == NULL) {
+ git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given);
+ goto done;
+ }
+
+ git_buf_puts(&port, default_port);
+ }
+
+ if (has_path) {
+ const char *url_path = given + u.field_data[UF_PATH].off;
+ size_t url_path_len = u.field_data[UF_PATH].len;
+ git_buf_decode_percent(&path, url_path, url_path_len);
+ } else {
+ git_buf_puts(&path, "/");
+ }
+
+ if (has_query) {
+ const char *url_query = given + u.field_data[UF_QUERY].off;
+ size_t url_query_len = u.field_data[UF_QUERY].len;
+ git_buf_decode_percent(&query, url_query, url_query_len);
+ }
+
+ if (has_userinfo) {
+ const char *url_userinfo = given + u.field_data[UF_USERINFO].off;
+ size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
+ const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
+
+ if (colon) {
+ const char *url_username = url_userinfo;
+ size_t url_username_len = colon - url_userinfo;
+ const char *url_password = colon + 1;
+ size_t url_password_len = url_userinfo_len - (url_username_len + 1);
+
+ git_buf_decode_percent(&username, url_username, url_username_len);
+ git_buf_decode_percent(&password, url_password, url_password_len);
+ } else {
+ git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
+ }
+ }
+
+ if (git_buf_oom(&scheme) ||
+ git_buf_oom(&host) ||
+ git_buf_oom(&port) ||
+ git_buf_oom(&path) ||
+ git_buf_oom(&query) ||
+ git_buf_oom(&username) ||
+ git_buf_oom(&password))
+ return -1;
+
+ url->scheme = git_buf_detach(&scheme);
+ url->host = git_buf_detach(&host);
+ url->port = git_buf_detach(&port);
+ url->path = git_buf_detach(&path);
+ url->query = git_buf_detach(&query);
+ url->username = git_buf_detach(&username);
+ url->password = git_buf_detach(&password);
+
+ error = 0;
+
+done:
+ git_buf_dispose(&scheme);
+ git_buf_dispose(&host);
+ git_buf_dispose(&port);
+ git_buf_dispose(&path);
+ git_buf_dispose(&query);
+ git_buf_dispose(&username);
+ git_buf_dispose(&password);
+ return error;
+}
+
+int git_net_url_is_default_port(git_net_url *url)
+{
+ return (strcmp(url->port, default_port_for_scheme(url->scheme)) == 0);
+}
+
+void git_net_url_swap(git_net_url *a, git_net_url *b)
+{
+ git_net_url tmp = GIT_NET_URL_INIT;
+
+ memcpy(&tmp, a, sizeof(git_net_url));
+ memcpy(a, b, sizeof(git_net_url));
+ memcpy(b, &tmp, sizeof(git_net_url));
+}
+
+void git_net_url_dispose(git_net_url *url)
+{
+ if (url->username)
+ git__memzero(url->username, strlen(url->username));
+
+ if (url->password)
+ git__memzero(url->password, strlen(url->password));
+
+ git__free(url->scheme); url->scheme = NULL;
+ git__free(url->host); url->host = NULL;
+ git__free(url->port); url->port = NULL;
+ git__free(url->path); url->path = NULL;
+ git__free(url->username); url->username = NULL;
+ git__free(url->password); url->password = NULL;
+}
diff --git a/src/net.h b/src/net.h
new file mode 100644
index 000000000..6df129089
--- /dev/null
+++ b/src/net.h
@@ -0,0 +1,36 @@
+/*
+ * 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_net_h__
+#define INCLUDE_net_h__
+
+#include "common.h"
+
+typedef struct git_net_url {
+ char *scheme;
+ char *host;
+ char *port;
+ char *path;
+ char *query;
+ char *username;
+ char *password;
+} git_net_url;
+
+#define GIT_NET_URL_INIT { NULL }
+
+/** Parses a string containing a URL into a structure. */
+int git_net_url_parse(git_net_url *url, const char *str);
+
+/** Returns nonzero if the URL is on the default port. */
+int git_net_url_is_default_port(git_net_url *url);
+
+/** Swaps the contents of one URL for another. */
+void git_net_url_swap(git_net_url *a, git_net_url *b);
+
+/** Disposes the contents of the structure. */
+void git_net_url_dispose(git_net_url *url);
+
+#endif
diff --git a/src/netops.c b/src/netops.c
index ecbc2aebe..708f694e3 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -119,192 +119,79 @@ int gitno__match_host(const char *pattern, const char *host)
return -1;
}
-static const char *default_port_http = "80";
-static const char *default_port_https = "443";
-
-const char *gitno__default_port(
- gitno_connection_data *data)
-{
- return data->use_ssl ? default_port_https : default_port_http;
-}
-
-static const char *prefix_http = "http://";
-static const char *prefix_https = "https://";
-
-int gitno_connection_data_from_url(
- gitno_connection_data *data,
- const char *url,
+int gitno_connection_data_handle_redirect(
+ git_net_url *url,
+ const char *redirect_str,
const char *service_suffix)
{
- int error = -1;
- const char *default_port = NULL, *path_search_start = NULL;
- char *original_host = NULL;
-
- /* service_suffix is optional */
- assert(data && url);
+ git_net_url tmp = GIT_NET_URL_INIT;
+ int error = 0;
- /* Save these for comparison later */
- original_host = data->host;
- data->host = NULL;
- gitno_connection_data_free_ptrs(data);
+ assert(url && redirect_str);
- if (!git__prefixcmp(url, prefix_http)) {
- path_search_start = url + strlen(prefix_http);
- default_port = default_port_http;
+ if (redirect_str[0] == '/') {
+ git__free(url->path);
- if (data->use_ssl) {
- git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed");
- goto cleanup;
+ if ((url->path = git__strdup(redirect_str)) == NULL) {
+ error = -1;
+ goto done;
}
- } else if (!git__prefixcmp(url, prefix_https)) {
- path_search_start = url + strlen(prefix_https);
- default_port = default_port_https;
- data->use_ssl = true;
- } else if (url[0] == '/')
- default_port = gitno__default_port(data);
-
- if (!default_port) {
- git_error_set(GIT_ERROR_NET, "unrecognized URL prefix");
- goto cleanup;
- }
+ } else {
+ git_net_url *original = url;
- error = gitno_extract_url_parts(
- &data->host, &data->port, &data->path, &data->user, &data->pass,
- url, default_port);
+ if ((error = git_net_url_parse(&tmp, redirect_str)) < 0)
+ goto done;
- if (url[0] == '/') {
- /* Relative redirect; reuse original host name and port */
- path_search_start = url;
- git__free(data->host);
- data->host = original_host;
- original_host = NULL;
- }
+ /* Validate that this is a legal redirection */
- if (!error) {
- const char *path = strchr(path_search_start, '/');
- size_t pathlen = strlen(path);
- size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
-
- if (suffixlen &&
- !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
- git__free(data->path);
- data->path = git__strndup(path, pathlen - suffixlen);
- } else {
- git__free(data->path);
- data->path = git__strdup(path);
- }
+ if (original->scheme &&
+ strcmp(original->scheme, tmp.scheme) != 0 &&
+ strcmp(tmp.scheme, "https") != 0) {
+ git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
+ original->scheme, tmp.scheme);
- /* Check for errors in the resulting data */
- if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
- git_error_set(GIT_ERROR_NET, "cross host redirect not allowed");
error = -1;
+ goto done;
}
- }
-cleanup:
- if (original_host) git__free(original_host);
- return error;
-}
+ if (original->host &&
+ git__strcasecmp(original->host, tmp.host) != 0) {
+ git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
+ original->host, tmp.host);
-void gitno_connection_data_free_ptrs(gitno_connection_data *d)
-{
- git__free(d->host); d->host = NULL;
- git__free(d->port); d->port = NULL;
- git__free(d->path); d->path = NULL;
- git__free(d->user); d->user = NULL;
- git__free(d->pass); d->pass = NULL;
-}
-
-int gitno_extract_url_parts(
- char **host_out,
- char **port_out,
- char **path_out,
- char **username_out,
- char **password_out,
- const char *url,
- const char *default_port)
-{
- struct http_parser_url u = {0};
- bool has_host, has_port, has_path, has_userinfo;
- git_buf host = GIT_BUF_INIT,
- port = GIT_BUF_INIT,
- path = GIT_BUF_INIT,
- username = GIT_BUF_INIT,
- password = GIT_BUF_INIT;
- int error = 0;
+ error = -1;
+ goto done;
+ }
- if (http_parser_parse_url(url, strlen(url), false, &u)) {
- git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url);
- error = GIT_EINVALIDSPEC;
- goto done;
+ git_net_url_swap(url, &tmp);
}
- has_host = !!(u.field_set & (1 << UF_HOST));
- has_port = !!(u.field_set & (1 << UF_PORT));
- has_path = !!(u.field_set & (1 << UF_PATH));
- has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
+ /* Remove the service suffix if it was given to us */
+ if (service_suffix) {
+ const char *service_query = strchr(service_suffix, '?');
+ size_t suffix_len = service_query ?
+ (size_t)(service_query - service_suffix) : strlen(service_suffix);
+ size_t path_len = strlen(url->path);
- if (has_host) {
- const char *url_host = url + u.field_data[UF_HOST].off;
- size_t url_host_len = u.field_data[UF_HOST].len;
- git_buf_decode_percent(&host, url_host, url_host_len);
- }
+ if (suffix_len && path_len >= suffix_len) {
+ size_t suffix_offset = path_len - suffix_len;
- if (has_port) {
- const char *url_port = url + u.field_data[UF_PORT].off;
- size_t url_port_len = u.field_data[UF_PORT].len;
- git_buf_put(&port, url_port, url_port_len);
- } else {
- git_buf_puts(&port, default_port);
- }
+ if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
+ (!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
+ /* Ensure we leave a minimum of '/' as the path */
+ if (suffix_offset == 0)
+ suffix_offset++;
- if (has_path && path_out) {
- const char *url_path = url + u.field_data[UF_PATH].off;
- size_t url_path_len = u.field_data[UF_PATH].len;
- git_buf_decode_percent(&path, url_path, url_path_len);
- } else if (path_out) {
- git_error_set(GIT_ERROR_NET, "invalid url, missing path");
- error = GIT_EINVALIDSPEC;
- goto done;
- }
+ url->path[suffix_offset] = '\0';
- if (has_userinfo) {
- const char *url_userinfo = url + u.field_data[UF_USERINFO].off;
- size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
- const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
-
- if (colon) {
- const char *url_username = url_userinfo;
- size_t url_username_len = colon - url_userinfo;
- const char *url_password = colon + 1;
- size_t url_password_len = url_userinfo_len - (url_username_len + 1);
-
- git_buf_decode_percent(&username, url_username, url_username_len);
- git_buf_decode_percent(&password, url_password, url_password_len);
- } else {
- git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
+ git__free(url->query);
+ url->query = NULL;
+ }
}
}
- if (git_buf_oom(&host) ||
- git_buf_oom(&port) ||
- git_buf_oom(&path) ||
- git_buf_oom(&username) ||
- git_buf_oom(&password))
- return -1;
-
- *host_out = git_buf_detach(&host);
- *port_out = git_buf_detach(&port);
- if (path_out)
- *path_out = git_buf_detach(&path);
- *username_out = git_buf_detach(&username);
- *password_out = git_buf_detach(&password);
-
done:
- git_buf_dispose(&host);
- git_buf_dispose(&port);
- git_buf_dispose(&path);
- git_buf_dispose(&username);
- git_buf_dispose(&password);
+ git_net_url_dispose(&tmp);
return error;
}
+
diff --git a/src/netops.h b/src/netops.h
index f376bd911..4c4bf78b0 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -11,6 +11,7 @@
#include "posix.h"
#include "stream.h"
+#include "net.h"
#ifdef GIT_OPENSSL
# include <openssl/ssl.h>
@@ -64,38 +65,15 @@ int gitno_recv(gitno_buffer *buf);
void gitno_consume(gitno_buffer *buf, const char *ptr);
void gitno_consume_n(gitno_buffer *buf, size_t cons);
-typedef struct gitno_connection_data {
- char *host;
- char *port;
- char *path;
- char *user;
- char *pass;
- bool use_ssl;
-} gitno_connection_data;
-
/*
* This replaces all the pointers in `data` with freshly-allocated strings,
* that the caller is responsible for freeing.
* `gitno_connection_data_free_ptrs` is good for this.
*/
-int gitno_connection_data_from_url(
- gitno_connection_data *data,
+int gitno_connection_data_handle_redirect(
+ git_net_url *data,
const char *url,
const char *service_suffix);
-/* This frees all the pointers IN the struct, but not the struct itself. */
-void gitno_connection_data_free_ptrs(gitno_connection_data *data);
-
-int gitno_extract_url_parts(
- char **host,
- char **port,
- char **path,
- char **username,
- char **password,
- const char *url,
- const char *default_port);
-
-const char *gitno__default_port(gitno_connection_data *data);
-
#endif
diff --git a/src/transports/auth.c b/src/transports/auth.c
index 6c69282b4..773e3020a 100644
--- a/src/transports/auth.c
+++ b/src/transports/auth.c
@@ -13,7 +13,6 @@
static int basic_next_token(
git_buf *out,
git_http_auth_context *ctx,
- const char *header_name,
git_cred *c)
{
git_cred_userpass_plaintext *cred;
@@ -32,9 +31,8 @@ static int basic_next_token(
git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
if (git_buf_oom(&raw) ||
- git_buf_printf(out, "%s: Basic ", header_name) < 0 ||
- git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 ||
- git_buf_puts(out, "\r\n") < 0)
+ git_buf_puts(out, "Basic ") < 0 ||
+ git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0)
goto on_error;
error = 0;
@@ -50,24 +48,26 @@ on_error:
static git_http_auth_context basic_context = {
GIT_AUTHTYPE_BASIC,
GIT_CREDTYPE_USERPASS_PLAINTEXT,
+ 0,
NULL,
basic_next_token,
+ NULL,
NULL
};
int git_http_auth_basic(
- git_http_auth_context **out, const gitno_connection_data *connection_data)
+ git_http_auth_context **out, const git_net_url *url)
{
- GIT_UNUSED(connection_data);
+ GIT_UNUSED(url);
*out = &basic_context;
return 0;
}
int git_http_auth_dummy(
- git_http_auth_context **out, const gitno_connection_data *connection_data)
+ git_http_auth_context **out, const git_net_url *url)
{
- GIT_UNUSED(connection_data);
+ GIT_UNUSED(url);
*out = NULL;
return 0;
diff --git a/src/transports/auth.h b/src/transports/auth.h
index e5cf7eff0..aeea6ce4c 100644
--- a/src/transports/auth.h
+++ b/src/transports/auth.h
@@ -16,6 +16,7 @@
typedef enum {
GIT_AUTHTYPE_BASIC = 1,
GIT_AUTHTYPE_NEGOTIATE = 2,
+ GIT_AUTHTYPE_NTLM = 4,
} git_http_authtype_t;
typedef struct git_http_auth_context git_http_auth_context;
@@ -27,11 +28,17 @@ struct git_http_auth_context {
/** Supported credentials */
git_credtype_t credtypes;
+ /** Connection affinity or request affinity */
+ unsigned connection_affinity : 1;
+
/** Sets the challenge on the authentication context */
int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
/** Gets the next authentication token from the context */
- int (*next_token)(git_buf *out, git_http_auth_context *ctx, const char *header_name, git_cred *cred);
+ int (*next_token)(git_buf *out, git_http_auth_context *ctx, git_cred *cred);
+
+ /** Examines if all tokens have been presented. */
+ int (*is_complete)(git_http_auth_context *ctx);
/** Frees the authentication context */
void (*free)(git_http_auth_context *ctx);
@@ -50,15 +57,15 @@ typedef struct {
/** Function to initialize an authentication context */
int (*init_context)(
git_http_auth_context **out,
- const gitno_connection_data *connection_data);
+ const git_net_url *url);
} git_http_auth_scheme;
int git_http_auth_dummy(
git_http_auth_context **out,
- const gitno_connection_data *connection_data);
+ const git_net_url *url);
int git_http_auth_basic(
git_http_auth_context **out,
- const gitno_connection_data *connection_data);
+ const git_net_url *url);
#endif
diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c
index 25c865c15..f0f2b08a4 100644
--- a/src/transports/auth_negotiate.c
+++ b/src/transports/auth_negotiate.c
@@ -73,7 +73,6 @@ static int negotiate_set_challenge(
static int negotiate_next_token(
git_buf *buf,
git_http_auth_context *c,
- const char *header_name,
git_cred *cred)
{
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
@@ -156,9 +155,8 @@ static int negotiate_next_token(
goto done;
}
- git_buf_printf(buf, "%s: Negotiate ", header_name);
+ git_buf_puts(buf, "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;
@@ -170,6 +168,15 @@ done:
return error;
}
+static int negotiate_is_complete(git_http_auth_context *c)
+{
+ http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
+
+ assert(ctx);
+
+ return (ctx->complete == 1);
+}
+
static void negotiate_context_free(git_http_auth_context *c)
{
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
@@ -194,7 +201,7 @@ static void negotiate_context_free(git_http_auth_context *c)
static int negotiate_init_context(
http_auth_negotiate_context *ctx,
- const gitno_connection_data *connection_data)
+ const git_net_url *url)
{
OM_uint32 status_major, status_minor;
gss_OID item, *oid;
@@ -235,7 +242,7 @@ static int negotiate_init_context(
}
git_buf_puts(&ctx->target, "HTTP@");
- git_buf_puts(&ctx->target, connection_data->host);
+ git_buf_puts(&ctx->target, url->host);
if (git_buf_oom(&ctx->target))
return -1;
@@ -248,7 +255,7 @@ static int negotiate_init_context(
int git_http_auth_negotiate(
git_http_auth_context **out,
- const gitno_connection_data *connection_data)
+ const git_net_url *url)
{
http_auth_negotiate_context *ctx;
@@ -257,15 +264,17 @@ int git_http_auth_negotiate(
ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
GIT_ERROR_CHECK_ALLOC(ctx);
- if (negotiate_init_context(ctx, connection_data) < 0) {
+ if (negotiate_init_context(ctx, url) < 0) {
git__free(ctx);
return -1;
}
ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE;
ctx->parent.credtypes = GIT_CREDTYPE_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;
*out = (git_http_auth_context *)ctx;
diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h
index 15a528aaf..d3f2ba2a1 100644
--- a/src/transports/auth_negotiate.h
+++ b/src/transports/auth_negotiate.h
@@ -16,7 +16,7 @@
extern int git_http_auth_negotiate(
git_http_auth_context **out,
- const gitno_connection_data *connection_data);
+ const git_net_url *url);
#else
diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c
new file mode 100644
index 000000000..eff09bd8a
--- /dev/null
+++ b/src/transports/auth_ntlm.c
@@ -0,0 +1,222 @@
+/*
+ * 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 "git2.h"
+#include "common.h"
+#include "buffer.h"
+#include "auth.h"
+#include "auth_ntlm.h"
+
+#ifdef GIT_NTLM
+
+#include "ntlm.h"
+
+typedef struct {
+ git_http_auth_context parent;
+ ntlm_client *ntlm;
+ char *challenge;
+ bool complete;
+} http_auth_ntlm_context;
+
+static int ntlm_set_challenge(
+ git_http_auth_context *c,
+ const char *challenge)
+{
+ http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+ assert(ctx && challenge);
+
+ git__free(ctx->challenge);
+
+ ctx->challenge = git__strdup(challenge);
+ GIT_ERROR_CHECK_ALLOC(ctx->challenge);
+
+ return 0;
+}
+
+static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_cred *_cred)
+{
+ git_cred_userpass_plaintext *cred;
+ const char *sep, *username;
+ char *domain = NULL, *domainuser = NULL;
+ int error = 0;
+
+ assert(_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT);
+ cred = (git_cred_userpass_plaintext *)_cred;
+
+ if ((sep = strchr(cred->username, '\\')) != NULL) {
+ domain = strndup(cred->username, (sep - cred->username));
+ GIT_ERROR_CHECK_ALLOC(domain);
+
+ domainuser = strdup(sep + 1);
+ GIT_ERROR_CHECK_ALLOC(domainuser);
+
+ username = domainuser;
+ } else {
+ username = cred->username;
+ }
+
+ if (ntlm_client_set_credentials(ctx->ntlm,
+ username, domain, cred->password) < 0) {
+ git_error_set(GIT_ERROR_NET, "could not set credentials: %s",
+ ntlm_client_errmsg(ctx->ntlm));
+ error = -1;
+ goto done;
+ }
+
+done:
+ git__free(domain);
+ git__free(domainuser);
+ return error;
+}
+
+static int ntlm_next_token(
+ git_buf *buf,
+ git_http_auth_context *c,
+ git_cred *cred)
+{
+ http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+ git_buf input_buf = GIT_BUF_INIT;
+ const unsigned char *msg;
+ size_t challenge_len, msg_len;
+ int error = -1;
+
+ assert(buf && ctx && ctx->ntlm);
+
+ challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
+
+ if (ctx->complete)
+ ntlm_client_reset(ctx->ntlm);
+
+ /*
+ * Set us complete now since it's the default case; the one
+ * incomplete case (successfully created a client request)
+ * will explicitly set that it requires a second step.
+ */
+ ctx->complete = true;
+
+ if (cred && ntlm_set_credentials(ctx, cred) != 0)
+ goto done;
+
+ if (challenge_len < 4) {
+ git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server");
+ goto done;
+ } else if (challenge_len == 4) {
+ if (memcmp(ctx->challenge, "NTLM", 4) != 0) {
+ git_error_set(GIT_ERROR_NET, "server did not request NTLM");
+ goto done;
+ }
+
+ if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) {
+ git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
+ ntlm_client_errmsg(ctx->ntlm));
+ goto done;
+ }
+
+ ctx->complete = false;
+ } else {
+ if (memcmp(ctx->challenge, "NTLM ", 5) != 0) {
+ git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM");
+ goto done;
+ }
+
+ if (git_buf_decode_base64(&input_buf,
+ ctx->challenge + 5, challenge_len - 5) < 0) {
+ git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server");
+ goto done;
+ }
+
+ if (ntlm_client_set_challenge(ctx->ntlm,
+ (const unsigned char *)input_buf.ptr, input_buf.size) != 0) {
+ git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s",
+ ntlm_client_errmsg(ctx->ntlm));
+ goto done;
+ }
+
+ if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) {
+ git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
+ ntlm_client_errmsg(ctx->ntlm));
+ goto done;
+ }
+ }
+
+ git_buf_puts(buf, "NTLM ");
+ git_buf_encode_base64(buf, (const char *)msg, msg_len);
+
+ if (git_buf_oom(buf))
+ goto done;
+
+ error = 0;
+
+done:
+ git_buf_dispose(&input_buf);
+ return error;
+}
+
+static int ntlm_is_complete(git_http_auth_context *c)
+{
+ http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+ assert(ctx);
+ return (ctx->complete == true);
+}
+
+static void ntlm_context_free(git_http_auth_context *c)
+{
+ http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+ ntlm_client_free(ctx->ntlm);
+ git__free(ctx->challenge);
+ git__free(ctx);
+}
+
+static int ntlm_init_context(
+ http_auth_ntlm_context *ctx,
+ const git_net_url *url)
+{
+ GIT_UNUSED(url);
+
+ if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) {
+ git_error_set_oom();
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_http_auth_ntlm(
+ git_http_auth_context **out,
+ const git_net_url *url)
+{
+ http_auth_ntlm_context *ctx;
+
+ GIT_UNUSED(url);
+
+ *out = NULL;
+
+ ctx = git__calloc(1, sizeof(http_auth_ntlm_context));
+ GIT_ERROR_CHECK_ALLOC(ctx);
+
+ if (ntlm_init_context(ctx, url) < 0) {
+ git__free(ctx);
+ return -1;
+ }
+
+ ctx->parent.type = GIT_AUTHTYPE_NTLM;
+ ctx->parent.credtypes = GIT_CREDTYPE_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;
+
+ *out = (git_http_auth_context *)ctx;
+
+ return 0;
+}
+
+#endif /* GIT_NTLM */
diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h
new file mode 100644
index 000000000..5b42b2b8e
--- /dev/null
+++ b/src/transports/auth_ntlm.h
@@ -0,0 +1,35 @@
+/*
+ * 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_transports_auth_ntlm_h__
+#define INCLUDE_transports_auth_ntlm_h__
+
+#include "git2.h"
+#include "auth.h"
+
+#ifdef GIT_NTLM
+
+#if defined(GIT_OPENSSL)
+# define CRYPT_OPENSSL
+#elif defined(GIT_MBEDTLS)
+# define CRYPT_MBEDTLS
+#elif defined(GIT_SECURE_TRANSPORT)
+# define CRYPT_COMMONCRYPTO
+#endif
+
+extern int git_http_auth_ntlm(
+ git_http_auth_context **out,
+ const git_net_url *url);
+
+#else
+
+#define git_http_auth_ntlm git_http_auth_dummy
+
+#endif /* GIT_NTLM */
+
+#endif
+
diff --git a/src/transports/git.c b/src/transports/git.c
index 9fd3b47fc..e48b7f961 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -192,8 +192,9 @@ static int _git_uploadpack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ git_net_url urldata = GIT_NET_URL_INIT;
const char *stream_url = url;
+ const char *host, *port;
git_proto_stream *s;
int error;
@@ -202,17 +203,15 @@ static int _git_uploadpack_ls(
if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(prefix_git);
- if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
+ if ((error = git_net_url_parse(&urldata, url)) < 0)
return error;
- error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
+ host = urldata.host;
+ port = urldata.port ? urldata.port : GIT_DEFAULT_PORT;
- git__free(host);
- git__free(port);
- git__free(path);
- git__free(user);
- git__free(pass);
+ error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
+ git_net_url_dispose(&urldata);
if (error < 0) {
git_proto_stream_free(*stream);
@@ -251,7 +250,7 @@ static int _git_receivepack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ git_net_url urldata = GIT_NET_URL_INIT;
const char *stream_url = url;
git_proto_stream *s;
int error;
@@ -260,16 +259,12 @@ static int _git_receivepack_ls(
if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(prefix_git);
- if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0)
+ if ((error = git_net_url_parse(&urldata, url)) < 0)
return error;
- error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream);
+ error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream);
- git__free(host);
- git__free(port);
- git__free(path);
- git__free(user);
- git__free(pass);
+ git_net_url_dispose(&urldata);
if (error < 0) {
git_proto_stream_free(*stream);
diff --git a/src/transports/http.c b/src/transports/http.c
index 9d77e627f..f1e048ee6 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -12,6 +12,7 @@
#include "git2.h"
#include "http_parser.h"
#include "buffer.h"
+#include "net.h"
#include "netops.h"
#include "global.h"
#include "remote.h"
@@ -19,11 +20,13 @@
#include "auth.h"
#include "http.h"
#include "auth_negotiate.h"
+#include "auth_ntlm.h"
#include "streams/tls.h"
#include "streams/socket.h"
git_http_auth_scheme auth_schemes[] = {
{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
+ { GIT_AUTHTYPE_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
{ GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
};
@@ -71,14 +74,18 @@ typedef struct {
} http_stream;
typedef struct {
- gitno_connection_data url;
+ git_net_url url;
git_stream *stream;
+ git_http_authtype_t authtypes;
+ git_credtype_t credtypes;
+
git_cred *cred;
- git_cred *url_cred;
+ unsigned url_cred_presented : 1,
+ authenticated : 1;
git_vector auth_challenges;
- git_vector auth_contexts;
+ git_http_auth_context *auth_context;
} http_server;
typedef struct {
@@ -106,8 +113,10 @@ typedef struct {
enum last_cb last_cb;
int parse_error;
int error;
+ unsigned request_count;
unsigned parse_finished : 1,
- replay_count : 3;
+ keepalive : 1,
+ replay_count : 4;
} http_subtransport;
typedef struct {
@@ -120,66 +129,24 @@ typedef struct {
size_t *bytes_read;
} parser_context;
-static bool credtype_match(git_http_auth_scheme *scheme, void *data)
-{
- unsigned int credtype = *(unsigned int *)data;
-
- return !!(scheme->credtypes & credtype);
-}
-
-static bool challenge_match(git_http_auth_scheme *scheme, void *data)
-{
- const char *scheme_name = scheme->name;
- const char *challenge = (const char *)data;
- size_t scheme_len;
-
- scheme_len = strlen(scheme_name);
- return (strncasecmp(challenge, scheme_name, scheme_len) == 0 &&
- (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
-}
-
-static int auth_context_match(
- git_http_auth_context **out,
- http_server *server,
- bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
- void *data)
+static git_http_auth_scheme *scheme_for_challenge(const char *challenge)
{
git_http_auth_scheme *scheme = NULL;
- git_http_auth_context *context = NULL, *c;
size_t i;
- *out = NULL;
-
for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
- if (scheme_match(&auth_schemes[i], data)) {
- scheme = &auth_schemes[i];
- break;
- }
- }
-
- if (!scheme)
- return 0;
+ const char *scheme_name = auth_schemes[i].name;
+ size_t scheme_len;
- /* See if authentication has already started for this scheme */
- git_vector_foreach(&server->auth_contexts, i, c) {
- if (c->type == scheme->type) {
- context = c;
+ scheme_len = strlen(scheme_name);
+ if (strncasecmp(challenge, scheme_name, scheme_len) == 0 &&
+ (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' ')) {
+ scheme = &auth_schemes[i];
break;
}
}
- if (!context) {
- if (scheme->init_context(&context, &server->url) < 0)
- return -1;
- else if (!context)
- return 0;
- else if (git_vector_insert(&server->auth_contexts, context) < 0)
- return -1;
- }
-
- *out = context;
-
- return 0;
+ return scheme;
}
static int apply_credentials(
@@ -187,31 +154,20 @@ static int apply_credentials(
http_server *server,
const char *header_name)
{
- git_cred *cred = server->cred;
- git_http_auth_context *context;
-
- /* Apply the credentials given to us in the URL */
- if (!cred && server->url.user && server->url.pass) {
- if (!server->url_cred &&
- git_cred_userpass_plaintext_new(&server->url_cred,
- server->url.user, server->url.pass) < 0)
- return -1;
+ git_buf token = GIT_BUF_INIT;
+ int error = 0;
- cred = server->url_cred;
- }
-
- if (!cred)
- return 0;
+ if (!server->auth_context)
+ goto done;
- /* Get or create a context for the best scheme for this cred type */
- if (auth_context_match(&context, server,
- credtype_match, &cred->credtype) < 0)
- return -1;
+ if ((error = server->auth_context->next_token(&token, server->auth_context, server->cred)) < 0)
+ goto done;
- if (!context)
- return 0;
+ error = git_buf_printf(buf, "%s: %s\r\n", header_name, token.ptr);
- return context->next_token(buf, context, header_name, cred);
+done:
+ git_buf_dispose(&token);
+ return error;
}
static int gen_request(
@@ -226,7 +182,7 @@ static int gen_request(
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED)
git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n",
s->verb,
- t->server.url.use_ssl ? "https" : "http",
+ t->server.url.scheme,
t->server.url.host,
t->server.url.port,
path, s->service_url);
@@ -238,9 +194,10 @@ static int gen_request(
git_http__user_agent(buf);
git_buf_puts(buf, "\r\n");
git_buf_printf(buf, "Host: %s", t->server.url.host);
- if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) {
+
+ if (!git_net_url_is_default_port(&t->server.url))
git_buf_printf(buf, ":%s", t->server.url.port);
- }
+
git_buf_puts(buf, "\r\n");
if (s->chunked || content_length > 0) {
@@ -275,29 +232,86 @@ static int gen_request(
return 0;
}
-static int parse_authenticate_response(
- http_server *server,
- int *allowed_types)
+static int set_authentication_challenge(http_server *server)
{
- git_http_auth_context *context;
+ const char *challenge;
+
+ if (git_vector_length(&server->auth_challenges) > 1) {
+ git_error_set(GIT_ERROR_NET, "received multiple authentication challenges");
+ return -1;
+ }
+
+ challenge = git_vector_get(&server->auth_challenges, 0);
+
+ if (server->auth_context->set_challenge)
+ return server->auth_context->set_challenge(server->auth_context, challenge);
+ else
+ return 0;
+}
+
+static int set_authentication_types(http_server *server)
+{
+ git_http_auth_scheme *scheme;
char *challenge;
size_t i;
git_vector_foreach(&server->auth_challenges, i, challenge) {
- if (auth_context_match(&context, server,
- challenge_match, challenge) < 0)
- return -1;
- else if (!context)
- continue;
+ if ((scheme = scheme_for_challenge(challenge)) != NULL) {
+ server->authtypes |= scheme->type;
+ server->credtypes |= scheme->credtypes;
+ }
+ }
- if (context->set_challenge &&
- context->set_challenge(context, challenge) < 0)
- return -1;
+ return 0;
+}
+
+static bool auth_context_complete(http_server *server)
+{
+ /* If there's no is_complete function, we're always complete */
+ if (!server->auth_context->is_complete)
+ return true;
+
+ if (server->auth_context->is_complete(server->auth_context))
+ return true;
+
+ return false;
+}
+
+static void free_auth_context(http_server *server)
+{
+ if (!server->auth_context)
+ return;
+
+ if (server->auth_context->free)
+ server->auth_context->free(server->auth_context);
+
+ server->auth_context = NULL;
+}
+
+static int parse_authenticate_response(http_server *server)
+{
+ /*
+ * If we think that we've completed authentication (ie, we've either
+ * sent a basic credential or we've sent the NTLM/Negotiate response)
+ * but we've got an authentication request from the server then our
+ * last authentication did not succeed. Start over.
+ */
+ if (server->auth_context && auth_context_complete(server)) {
+ free_auth_context(server);
- *allowed_types |= context->credtypes;
+ server->authenticated = 0;
}
- return 0;
+ /*
+ * If we've begun authentication, give the challenge to the context.
+ * Otherwise, set up the types to prepare credentials.
+ */
+ if (git_vector_length(&server->auth_challenges) == 0)
+ return 0;
+ else if (server->auth_context)
+ return set_authentication_challenge(server);
+ else
+ return set_authentication_types(server);
}
static int on_header_ready(http_subtransport *t)
@@ -396,55 +410,145 @@ GIT_INLINE(void) free_cred(git_cred **cred)
}
}
+static int apply_url_credentials(
+ git_cred **cred,
+ unsigned int allowed_types,
+ const char *username,
+ const char *password)
+{
+ if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)
+ return git_cred_userpass_plaintext_new(cred, username, password);
+
+ if ((allowed_types & GIT_CREDTYPE_DEFAULT) && *username == '\0' && *password == '\0')
+ return git_cred_default_new(cred);
+
+ return GIT_PASSTHROUGH;
+}
+
+static int init_auth(http_server *server)
+{
+ git_http_auth_scheme *s, *scheme = NULL;
+ char *c, *challenge = NULL;
+ size_t i;
+
+ git_vector_foreach(&server->auth_challenges, i, c) {
+ s = scheme_for_challenge(c);
+
+ if (s && !!(s->credtypes & server->credtypes)) {
+ scheme = s;
+ challenge = c;
+ break;
+ }
+ }
+
+ if (!scheme) {
+ git_error_set(GIT_ERROR_NET, "no authentication mechanism could be negotiated");
+ return -1;
+ }
+
+ if (scheme->init_context(&server->auth_context, &server->url) < 0)
+ return -1;
+
+ if (server->auth_context->set_challenge &&
+ server->auth_context->set_challenge(server->auth_context, challenge) < 0)
+ return -1;
+
+ return 0;
+}
+
static int on_auth_required(
- git_cred **creds,
http_parser *parser,
+ http_server *server,
const char *url,
const char *type,
git_cred_acquire_cb callback,
- void *callback_payload,
- const char *username,
- int allowed_types)
+ void *callback_payload)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
- int ret;
+ int error = 1;
+
+ if (parse_authenticate_response(server) < 0) {
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ }
- if (!allowed_types) {
+ /* If we're in the middle of challenge/response auth, continue */
+ if (parser->status_code == 407 || parser->status_code == 401) {
+ if (server->auth_context && !auth_context_complete(server)) {
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+ }
+ }
+
+ /* Enforce a reasonable cap on the number of replays */
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (!server->credtypes) {
git_error_set(GIT_ERROR_NET, "%s requested authentication but did not negotiate mechanisms", type);
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
}
- if (callback) {
- free_cred(creds);
- ret = callback(creds, url, username, allowed_types, callback_payload);
+ free_auth_context(server);
+ free_cred(&server->cred);
+
+ /* Start with URL-specified credentials, if there were any. */
+ if (!server->url_cred_presented && server->url.username && server->url.password) {
+ error = apply_url_credentials(&server->cred, server->credtypes, server->url.username, server->url.password);
+ server->url_cred_presented = 1;
- if (ret == GIT_PASSTHROUGH) {
+ if (error == GIT_PASSTHROUGH) {
/* treat GIT_PASSTHROUGH as if callback isn't set */
- } else if (ret < 0) {
- t->error = ret;
- t->parse_error = PARSE_ERROR_EXT;
- return t->parse_error;
- } else {
- assert(*creds);
-
- if (!((*creds)->credtype & allowed_types)) {
- git_error_set(GIT_ERROR_NET, "%s credential provider returned an invalid cred type", type);
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
- }
+ error = 1;
+ }
+ }
- /* Successfully acquired a credential. */
- t->parse_error = PARSE_ERROR_REPLAY;
- return 0;
+ if (error > 0 && callback) {
+ error = callback(&server->cred, url, server->url.username, server->credtypes, callback_payload);
+
+ if (error == GIT_PASSTHROUGH) {
+ /* treat GIT_PASSTHROUGH as if callback isn't set */
+ error = 1;
}
}
- git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set",
- type);
- t->parse_error = PARSE_ERROR_GENERIC;
- return t->parse_error;
+ if (error > 0) {
+ git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set",
+ type);
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ } else if (error < 0) {
+ t->error = error;
+ t->parse_error = PARSE_ERROR_EXT;
+ return t->parse_error;
+ }
+
+ assert(server->cred);
+
+ if (!(server->cred->credtype & server->credtypes)) {
+ git_error_set(GIT_ERROR_NET, "%s credential provider returned an invalid cred type", type);
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ }
+
+ /* Successfully acquired a credential. Start an auth context. */
+ if (init_auth(server) < 0) {
+ t->parse_error = PARSE_ERROR_GENERIC;
+ return t->parse_error;
+ }
+
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+}
+
+static void on_auth_success(http_server *server)
+{
+ server->url_cred_presented = 0;
+ server->authenticated = 1;
}
static int on_headers_complete(http_parser *parser)
@@ -453,50 +557,35 @@ 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 proxy_auth_types = 0, server_auth_types = 0;
-
- /* Enforce a reasonable cap on the number of replays */
- if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
- git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
- if (VALUE == t->last_cb)
- if (on_header_ready(t) < 0)
- return t->parse_error = PARSE_ERROR_GENERIC;
-
- /*
- * Capture authentication headers for the proxy or final endpoint,
- * these may be 407/401 (authentication is not complete) or a 200
- * (informing us that auth has completed).
- */
- if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0 ||
- parse_authenticate_response(&t->server, &server_auth_types) < 0)
+ if (t->last_cb == VALUE && on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Check for a proxy authentication failure. */
if (parser->status_code == 407 && get_verb == s->verb)
- return on_auth_required(&t->proxy.cred,
+ return on_auth_required(
parser,
+ &t->proxy,
t->proxy_opts.url,
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
- t->proxy_opts.payload,
- t->proxy.url.user,
- proxy_auth_types);
+ t->proxy_opts.payload);
+ else
+ on_auth_success(&t->proxy);
/* Check for an authentication failure. */
if (parser->status_code == 401 && get_verb == s->verb)
- return on_auth_required(&t->server.cred,
+ return on_auth_required(
parser,
+ &t->server,
t->owner->url,
SERVER_TYPE_REMOTE,
t->owner->cred_acquire_cb,
- t->owner->cred_acquire_payload,
- t->server.url.user,
- server_auth_types);
+ t->owner->cred_acquire_payload);
+ else
+ on_auth_success(&t->server);
/* Check for a redirect.
* Right now we only permit a redirect to the same hostname. */
@@ -507,7 +596,7 @@ static int on_headers_complete(http_parser *parser)
parser->status_code == 308) &&
t->location) {
- if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0)
+ if (gitno_connection_data_handle_redirect(&t->server.url, t->location, s->service_url) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/* Set the redirect URL on the stream. This is a transfer of
@@ -569,6 +658,7 @@ static int on_message_complete(http_parser *parser)
http_subtransport *t = ctx->t;
t->parse_finished = 1;
+ t->keepalive = http_should_keep_alive(parser);
return 0;
}
@@ -578,13 +668,6 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
- /* If our goal is to replay the request (either an auth failure or
- * a redirect) then don't bother buffering since we're ignoring the
- * content anyway.
- */
- if (t->parse_error == PARSE_ERROR_REPLAY)
- return 0;
-
/* If there's no buffer set, we're explicitly ignoring the body. */
if (ctx->buffer) {
if (ctx->buf_size < len) {
@@ -613,6 +696,7 @@ static void clear_parser_state(http_subtransport *t)
t->last_cb = NONE;
t->parse_error = 0;
t->parse_finished = 0;
+ t->keepalive = 0;
git_buf_dispose(&t->parse_header_name);
git_buf_init(&t->parse_header_name, 0);
@@ -676,7 +760,7 @@ static int load_proxy_config(http_subtransport *t)
git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION);
if ((error = git_remote__get_http_proxy(t->owner->owner,
- !!t->server.url.use_ssl, &t->proxy_url)) < 0)
+ !strcmp(t->server.url.scheme, "https"), &t->proxy_url)) < 0)
return error;
if (!t->proxy_url)
@@ -698,20 +782,14 @@ static int load_proxy_config(http_subtransport *t)
return -1;
}
- if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0)
- return error;
-
- if (t->proxy.url.use_ssl) {
- git_error_set(GIT_ERROR_NET, "SSL connections to proxy are not supported");
- return -1;
- }
+ git_net_url_dispose(&t->proxy.url);
- return error;
+ return git_net_url_parse(&t->proxy.url, t->proxy_opts.url);
}
static int check_certificate(
git_stream *stream,
- gitno_connection_data *url,
+ git_net_url *url,
int is_valid,
git_transport_certificate_check_cb cert_cb,
void *cert_cb_payload)
@@ -740,7 +818,7 @@ static int check_certificate(
static int stream_connect(
git_stream *stream,
- gitno_connection_data *url,
+ git_net_url *url,
git_transport_certificate_check_cb cert_cb,
void *cb_payload)
{
@@ -782,18 +860,10 @@ static int proxy_headers_complete(http_parser *parser)
{
parser_context *ctx = (parser_context *) parser->data;
http_subtransport *t = ctx->t;
- int proxy_auth_types = 0;
-
- /* Enforce a reasonable cap on the number of replays */
- if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
- git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
- return t->parse_error = PARSE_ERROR_GENERIC;
- }
/* Both parse_header_name and parse_header_value are populated
* and ready for consumption. */
- if (VALUE == t->last_cb)
- if (on_header_ready(t) < 0)
+ if (t->last_cb == VALUE && on_header_ready(t) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
/*
@@ -801,19 +871,32 @@ static int proxy_headers_complete(http_parser *parser)
* these may be 407/401 (authentication is not complete) or a 200
* (informing us that auth has completed).
*/
- if (parse_authenticate_response(&t->proxy, &proxy_auth_types) < 0)
+ if (parse_authenticate_response(&t->proxy) < 0)
return t->parse_error = PARSE_ERROR_GENERIC;
+ /* If we're in the middle of challenge/response auth, continue */
+ if (parser->status_code == 407) {
+ if (t->proxy.auth_context && !auth_context_complete(&t->proxy)) {
+ t->parse_error = PARSE_ERROR_REPLAY;
+ return 0;
+ }
+ }
+
+ /* Enforce a reasonable cap on the number of replays */
+ if (t->replay_count++ >= GIT_HTTP_REPLAY_MAX) {
+ git_error_set(GIT_ERROR_NET, "too many redirects or authentication replays");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
/* Check for a proxy authentication failure. */
if (parser->status_code == 407)
- return on_auth_required(&t->proxy.cred,
+ return on_auth_required(
parser,
+ &t->proxy,
t->proxy_opts.url,
SERVER_TYPE_PROXY,
t->proxy_opts.credentials,
- t->proxy_opts.payload,
- t->proxy.url.user,
- proxy_auth_types);
+ t->proxy_opts.payload);
if (parser->status_code != 200) {
git_error_set(GIT_ERROR_NET, "unexpected status code from proxy: %d",
@@ -834,6 +917,7 @@ static int proxy_connect(
static http_parser_settings proxy_parser_settings = {0};
size_t bytes_read = 0, bytes_parsed;
parser_context ctx;
+ bool auth_replay;
int error;
/* Use the parser settings only to parser headers. */
@@ -845,6 +929,8 @@ static int proxy_connect(
replay:
clear_parser_state(t);
+ auth_replay = false;
+
gitno_buffer_setup_fromstream(proxy_stream,
&t->parse_buffer,
t->parse_buffer_data,
@@ -862,8 +948,17 @@ replay:
while (!bytes_read && !t->parse_finished) {
t->parse_buffer.offset = 0;
- if ((error = gitno_recv(&t->parse_buffer)) < 0)
+ if ((error = gitno_recv(&t->parse_buffer)) < 0) {
goto done;
+ } else if (error == 0 && t->request_count > 0) {
+ /* Server closed a keep-alive socket; reconnect. */
+ auth_replay = true;
+ goto done;
+ } else if (error == 0) {
+ git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
+ error = -1;
+ goto done;
+ }
/*
* This call to http_parser_execute will invoke the on_*
@@ -892,10 +987,9 @@ replay:
}
/* Replay the request with authentication headers. */
- if (PARSE_ERROR_REPLAY == t->parse_error)
- goto replay;
-
- if (t->parse_error < 0) {
+ if (PARSE_ERROR_REPLAY == t->parse_error) {
+ auth_replay = true;
+ } else if (t->parse_error < 0) {
error = t->parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1;
goto done;
}
@@ -909,6 +1003,15 @@ replay:
}
}
+ t->request_count++;
+
+ if (auth_replay) {
+ if (t->keepalive && t->parse_finished)
+ goto replay;
+
+ return PARSE_ERROR_REPLAY;
+ }
+
if ((error = git_tls_stream_wrap(out, proxy_stream, t->server.url.host)) == 0)
error = stream_connect(*out, &t->server.url,
t->owner->certificate_check_cb,
@@ -920,22 +1023,45 @@ replay:
*/
t->proxy_opts.type = GIT_PROXY_NONE;
t->replay_count = 0;
+ t->request_count = 0;
done:
return error;
}
+static void reset_auth_connection(http_server *server)
+{
+ /*
+ * If we've authenticated and we're doing "normal"
+ * authentication with a request affinity (Basic, Digest)
+ * then we want to _keep_ our context, since authentication
+ * survives even through non-keep-alive connections. If
+ * we've authenticated and we're doing connection-based
+ * authentication (NTLM, Negotiate) - indicated by the presence
+ * of an `is_complete` callback - then we need to restart
+ * authentication on a new connection.
+ */
+
+ if (server->authenticated &&
+ server->auth_context &&
+ server->auth_context->connection_affinity) {
+ free_auth_context(server);
+
+ server->url_cred_presented = 0;
+ server->authenticated = 0;
+ }
+}
+
static int http_connect(http_subtransport *t)
{
- gitno_connection_data *url;
+ git_net_url *url;
git_stream *proxy_stream = NULL, *stream = NULL;
git_transport_certificate_check_cb cert_cb;
void *cb_payload;
int error;
- if (t->connected &&
- http_should_keep_alive(&t->parser) &&
- t->parse_finished)
+auth_replay:
+ if (t->connected && t->keepalive && t->parse_finished)
return 0;
if ((error = load_proxy_config(t)) < 0)
@@ -953,7 +1079,12 @@ static int http_connect(http_subtransport *t)
t->proxy.stream = NULL;
}
+ reset_auth_connection(&t->server);
+ reset_auth_connection(&t->proxy);
+
t->connected = 0;
+ t->keepalive = 0;
+ t->request_count = 0;
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) {
url = &t->proxy.url;
@@ -965,7 +1096,7 @@ static int http_connect(http_subtransport *t)
cb_payload = t->owner->message_cb_payload;
}
- if (url->use_ssl)
+ if (strcmp(url->scheme, "https") == 0)
error = git_tls_stream_new(&stream, url->host, url->port);
else
error = git_socket_stream_new(&stream, url->host, url->port);
@@ -982,18 +1113,24 @@ static int http_connect(http_subtransport *t)
* an HTTPS connection, then we need to build a CONNECT tunnel.
*/
if (t->proxy_opts.type == GIT_PROXY_SPECIFIED &&
- t->server.url.use_ssl) {
+ strcmp(t->server.url.scheme, "https") == 0) {
proxy_stream = stream;
stream = NULL;
- if ((error = proxy_connect(&stream, proxy_stream, t)) < 0)
+ error = proxy_connect(&stream, proxy_stream, t);
+
+ if (error == PARSE_ERROR_REPLAY) {
+ git_stream_close(proxy_stream);
+ git_stream_free(proxy_stream);
+ goto auth_replay;
+ } else if (error < 0) {
goto on_error;
+ }
}
t->proxy.stream = proxy_stream;
t->server.stream = stream;
t->connected = 1;
- t->replay_count = 0;
return 0;
on_error:
@@ -1020,27 +1157,23 @@ static int http_stream_read(
http_subtransport *t = OWNING_SUBTRANSPORT(s);
parser_context ctx;
size_t bytes_parsed;
+ git_buf request = GIT_BUF_INIT;
+ bool auth_replay;
+ int error = 0;
replay:
*bytes_read = 0;
+ auth_replay = false;
assert(t->connected);
if (!s->sent_request) {
- git_buf request = GIT_BUF_INIT;
-
+ git_buf_clear(&request);
clear_parser_state(t);
- if (gen_request(&request, s, 0) < 0)
- return -1;
-
- if (git_stream__write_full(t->server.stream, request.ptr,
- request.size, 0) < 0) {
- git_buf_dispose(&request);
- return -1;
- }
-
- git_buf_dispose(&request);
+ if ((error = gen_request(&request, s, 0)) < 0 ||
+ (error = git_stream__write_full(t->server.stream, request.ptr, request.size, 0)) < 0)
+ goto done;
s->sent_request = 1;
}
@@ -1050,17 +1183,17 @@ replay:
assert(s->verb == post_verb);
/* Flush, if necessary */
- if (s->chunk_buffer_len > 0 &&
- write_chunk(t->server.stream,
- s->chunk_buffer, s->chunk_buffer_len) < 0)
- return -1;
+ if (s->chunk_buffer_len > 0) {
+ if ((error = write_chunk(t->server.stream, s->chunk_buffer, s->chunk_buffer_len)) < 0)
+ goto done;
- s->chunk_buffer_len = 0;
+ s->chunk_buffer_len = 0;
+ }
/* Write the final chunk. */
- if (git_stream__write_full(t->server.stream,
- "0\r\n\r\n", 5, 0) < 0)
- return -1;
+ if ((error = git_stream__write_full(t->server.stream,
+ "0\r\n\r\n", 5, 0)) < 0)
+ goto done;
}
s->received_response = 1;
@@ -1068,7 +1201,6 @@ replay:
while (!*bytes_read && !t->parse_finished) {
size_t data_offset;
- int error;
/*
* Make the parse_buffer think it's as full of data as
@@ -1078,26 +1210,37 @@ replay:
* data_offset is the actual data offset from which we
* should tell the parser to start reading.
*/
- if (buf_size >= t->parse_buffer.len) {
+ if (buf_size >= t->parse_buffer.len)
t->parse_buffer.offset = 0;
- } else {
+ else
t->parse_buffer.offset = t->parse_buffer.len - buf_size;
- }
data_offset = t->parse_buffer.offset;
- if (gitno_recv(&t->parse_buffer) < 0)
- return -1;
+ if ((error = gitno_recv(&t->parse_buffer)) < 0) {
+ goto done;
+ } else if (error == 0 && t->request_count > 0) {
+ /* Server closed a keep-alive socket; reconnect. */
+ auth_replay = true;
+ goto done;
+ } else if (error == 0) {
+ git_error_set(GIT_ERROR_NET, "unexpected disconnection from server");
+ error = -1;
+ goto done;
+ }
- /* This call to http_parser_execute will result in invocations of the
- * on_* family of callbacks. The most interesting of these is
- * on_body_fill_buffer, which is called when data is ready to be copied
- * into the target buffer. We need to marshal the buffer, buf_size, and
- * bytes_read parameters to this callback. */
+ /*
+ * This call to http_parser_execute will result in invocations
+ * of the on_* family of callbacks, including on_body_fill_buffer
+ * which will write into the target buffer. Set up the buffer
+ * for it to write into _unless_ we got an auth failure; in
+ * that case we only care about the headers and don't need to
+ * bother copying the body.
+ */
ctx.t = t;
ctx.s = s;
- ctx.buffer = buffer;
- ctx.buf_size = buf_size;
+ ctx.buffer = auth_replay ? NULL : buffer;
+ ctx.buf_size = auth_replay ? 0 : buf_size;
ctx.bytes_read = bytes_read;
/* Set the context, call the parser, then unset the context. */
@@ -1110,33 +1253,40 @@ replay:
t->parser.data = NULL;
- /* If there was a handled authentication failure, then parse_error
- * will have signaled us that we should replay the request. */
- if (PARSE_ERROR_REPLAY == t->parse_error) {
- s->sent_request = 0;
-
- if ((error = http_connect(t)) < 0)
- return error;
-
- goto replay;
- }
-
- if (t->parse_error == PARSE_ERROR_EXT) {
- return t->error;
+ /* On a 401, read the rest of the response then retry. */
+ if (t->parse_error == PARSE_ERROR_REPLAY) {
+ auth_replay = true;
+ } else if (t->parse_error == PARSE_ERROR_EXT) {
+ error = t->error;
+ goto done;
+ } else if (t->parse_error < 0) {
+ error = -1;
+ goto done;
}
- if (t->parse_error < 0)
- return -1;
-
if (bytes_parsed != t->parse_buffer.offset - data_offset) {
git_error_set(GIT_ERROR_NET,
"HTTP parser error: %s",
http_errno_description((enum http_errno)t->parser.http_errno));
- return -1;
+ error = -1;
+ goto done;
}
}
- return 0;
+ t->request_count++;
+
+ if (auth_replay) {
+ s->sent_request = 0;
+
+ if ((error = http_connect(t)) < 0)
+ return error;
+
+ goto replay;
+ }
+
+done:
+ git_buf_dispose(&request);
+ return error;
}
static int http_stream_write_chunked(
@@ -1378,7 +1528,7 @@ static int http_action(
* that would be insecure in plaintext (eg, HTTP Basic).
*/
if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) &&
- (ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0)
+ (ret = git_net_url_parse(&t->server.url, url)) < 0)
return ret;
assert(t->server.url.host && t->server.url.port && t->server.url.path);
@@ -1404,19 +1554,6 @@ static int http_action(
return -1;
}
-static void free_auth_contexts(git_vector *contexts)
-{
- git_http_auth_context *context;
- size_t i;
-
- git_vector_foreach(contexts, i, context) {
- if (context->free)
- context->free(context);
- }
-
- git_vector_clear(contexts);
-}
-
static int http_close(git_smart_subtransport *subtransport)
{
http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
@@ -1438,18 +1575,16 @@ static int http_close(git_smart_subtransport *subtransport)
}
free_cred(&t->server.cred);
- free_cred(&t->server.url_cred);
free_cred(&t->proxy.cred);
- free_cred(&t->proxy.url_cred);
- free_auth_contexts(&t->server.auth_contexts);
- free_auth_contexts(&t->proxy.auth_contexts);
+ free_auth_context(&t->server);
+ free_auth_context(&t->proxy);
- gitno_connection_data_free_ptrs(&t->server.url);
- memset(&t->server.url, 0x0, sizeof(gitno_connection_data));
+ t->server.url_cred_presented = false;
+ t->proxy.url_cred_presented = false;
- gitno_connection_data_free_ptrs(&t->proxy.url);
- memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data));
+ git_net_url_dispose(&t->server.url);
+ git_net_url_dispose(&t->proxy.url);
git__free(t->proxy_url);
t->proxy_url = NULL;
@@ -1462,9 +1597,6 @@ static void http_free(git_smart_subtransport *subtransport)
http_subtransport *t = GIT_CONTAINER_OF(subtransport, http_subtransport, parent);
http_close(subtransport);
-
- git_vector_free(&t->server.auth_contexts);
- git_vector_free(&t->proxy.auth_contexts);
git__free(t);
}
diff --git a/src/transports/http.h b/src/transports/http.h
index b09475755..ddaab0b45 100644
--- a/src/transports/http.h
+++ b/src/transports/http.h
@@ -10,7 +10,7 @@
#include "buffer.h"
-#define GIT_HTTP_REPLAY_MAX 7
+#define GIT_HTTP_REPLAY_MAX 15
GIT_INLINE(int) git_http__user_agent(git_buf *buf)
{
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index 9b5d8a528..caa3a17f5 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -14,6 +14,7 @@
#include "global.h"
#include "git2.h"
#include "buffer.h"
+#include "net.h"
#include "netops.h"
#include "smart.h"
#include "cred.h"
@@ -258,8 +259,7 @@ static int ssh_stream_alloc(
}
static int git_ssh_extract_url_parts(
- char **host,
- char **username,
+ git_net_url *urldata,
const char *url)
{
char *colon, *at;
@@ -271,11 +271,11 @@ static int git_ssh_extract_url_parts(
at = strchr(url, '@');
if (at) {
start = at + 1;
- *username = git__substrdup(url, at - url);
- GIT_ERROR_CHECK_ALLOC(*username);
+ urldata->username = git__substrdup(url, at - url);
+ GIT_ERROR_CHECK_ALLOC(urldata->username);
} else {
start = url;
- *username = NULL;
+ urldata->username = NULL;
}
if (colon == NULL || (colon < start)) {
@@ -283,8 +283,8 @@ static int git_ssh_extract_url_parts(
return -1;
}
- *host = git__substrdup(start, colon - start);
- GIT_ERROR_CHECK_ALLOC(*host);
+ urldata->host = git__substrdup(start, colon - start);
+ GIT_ERROR_CHECK_ALLOC(urldata->host);
return 0;
}
@@ -506,14 +506,15 @@ static int _git_ssh_session_create(
return 0;
}
+#define SSH_DEFAULT_PORT "22"
+
static int _git_ssh_setup_conn(
ssh_subtransport *t,
const char *url,
const char *cmd,
git_smart_subtransport_stream **stream)
{
- char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
- const char *default_port="22";
+ git_net_url urldata = GIT_NET_URL_INIT;
int auth_methods, error = 0;
size_t i;
ssh_stream *s;
@@ -535,19 +536,22 @@ static int _git_ssh_setup_conn(
const char *p = ssh_prefixes[i];
if (!git__prefixcmp(url, p)) {
- if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0)
+ if ((error = git_net_url_parse(&urldata, url)) < 0)
goto done;
goto post_extract;
}
}
- if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0)
+ if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0)
goto done;
- port = git__strdup(default_port);
- GIT_ERROR_CHECK_ALLOC(port);
+
+ if (urldata.port == NULL)
+ urldata.port = git__strdup(SSH_DEFAULT_PORT);
+
+ GIT_ERROR_CHECK_ALLOC(urldata.port);
post_extract:
- if ((error = git_socket_stream_new(&s->io, host, port)) < 0 ||
+ if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 ||
(error = git_stream_connect(s->io)) < 0)
goto done;
@@ -583,7 +587,7 @@ post_extract:
cert_ptr = &cert;
- error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload);
+ error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload);
if (error < 0 && error != GIT_PASSTHROUGH) {
if (!git_error_last())
@@ -594,21 +598,21 @@ post_extract:
}
/* we need the username to ask for auth methods */
- if (!user) {
+ if (!urldata.username) {
if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
goto done;
- user = git__strdup(((git_cred_username *) cred)->username);
+ urldata.username = git__strdup(((git_cred_username *) cred)->username);
cred->free(cred);
cred = NULL;
- if (!user)
+ if (!urldata.username)
goto done;
- } else if (user && pass) {
- if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0)
+ } else if (urldata.username && urldata.password) {
+ if ((error = git_cred_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0)
goto done;
}
- if ((error = list_auth_methods(&auth_methods, session, user)) < 0)
+ if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0)
goto done;
error = GIT_EAUTH;
@@ -622,10 +626,10 @@ post_extract:
cred = NULL;
}
- if ((error = request_creds(&cred, t, user, auth_methods)) < 0)
+ if ((error = request_creds(&cred, t, urldata.username, auth_methods)) < 0)
goto done;
- if (strcmp(user, git_cred__username(cred))) {
+ if (strcmp(urldata.username, git_cred__username(cred))) {
git_error_set(GIT_ERROR_SSH, "username does not match previous request");
error = -1;
goto done;
@@ -662,11 +666,7 @@ done:
if (cred)
cred->free(cred);
- git__free(host);
- git__free(port);
- git__free(path);
- git__free(user);
- git__free(pass);
+ git_net_url_dispose(&urldata);
return error;
}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index f922cb480..6392a7d1c 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -101,23 +101,43 @@ typedef struct {
} winhttp_stream;
typedef struct {
- git_smart_subtransport parent;
- transport_smart *owner;
- gitno_connection_data connection_data;
- gitno_connection_data proxy_connection_data;
+ git_net_url url;
git_cred *cred;
- git_cred *url_cred;
- git_cred *proxy_cred;
int auth_mechanisms;
+ bool url_cred_presented;
+} winhttp_server;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+
+ winhttp_server server;
+ winhttp_server proxy;
+
HINTERNET session;
HINTERNET connection;
} winhttp_subtransport;
-static int _apply_userpass_credential(HINTERNET request, DWORD target, DWORD scheme, git_cred *cred)
+static int apply_userpass_credentials(HINTERNET request, DWORD target, int mechanisms, git_cred *cred)
{
git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
- wchar_t *user, *pass;
+ wchar_t *user = NULL, *pass = NULL;
int user_len = 0, pass_len = 0, error = 0;
+ DWORD native_scheme;
+
+ if (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) {
+ native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
+ } else if (mechanisms & GIT_WINHTTP_AUTH_NTLM) {
+ native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
+ } else if (mechanisms & GIT_WINHTTP_AUTH_DIGEST) {
+ native_scheme = WINHTTP_AUTH_SCHEME_DIGEST;
+ } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) {
+ native_scheme = WINHTTP_AUTH_SCHEME_BASIC;
+ } else {
+ git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
+ error = -1;
+ goto done;
+ }
if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0)
goto done;
@@ -125,7 +145,7 @@ static int _apply_userpass_credential(HINTERNET request, DWORD target, DWORD sch
if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0)
goto done;
- if (!WinHttpSetCredentials(request, target, scheme, user, pass, NULL)) {
+ if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) {
git_error_set(GIT_ERROR_OS, "failed to set credentials");
error = -1;
}
@@ -143,77 +163,58 @@ done:
return error;
}
-static int apply_userpass_credential_proxy(HINTERNET request, git_cred *cred, int mechanisms)
+static int apply_default_credentials(HINTERNET request, DWORD target, int mechanisms)
{
- if (GIT_WINHTTP_AUTH_DIGEST & mechanisms) {
- return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY,
- WINHTTP_AUTH_SCHEME_DIGEST, cred);
- }
-
- return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_PROXY,
- WINHTTP_AUTH_SCHEME_BASIC, cred);
-}
-
-static int apply_userpass_credential(HINTERNET request, int mechanisms, git_cred *cred)
-{
- DWORD native_scheme;
+ DWORD autologon_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
+ DWORD native_scheme = 0;
- if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) ||
- (mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE)) {
+ if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0) {
+ native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
+ } else if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0) {
native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
- } else if (mechanisms & GIT_WINHTTP_AUTH_BASIC) {
- native_scheme = WINHTTP_AUTH_SCHEME_BASIC;
} else {
git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
return -1;
}
- return _apply_userpass_credential(request, WINHTTP_AUTH_TARGET_SERVER,
- native_scheme, cred);
-}
-
-static int apply_default_credentials(HINTERNET request, int mechanisms)
-{
- /* Either the caller explicitly requested that default credentials be passed,
- * or our fallback credential callback was invoked and checked that the target
- * URI was in the appropriate Internet Explorer security zone. By setting this
- * flag, we guarantee that the credentials are delivered by WinHTTP. The default
- * is "medium" which applies to the intranet and sounds like it would correspond
- * to Internet Explorer security zones, but in fact does not. */
- DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
- DWORD native_scheme = 0;
-
- if ((mechanisms & GIT_WINHTTP_AUTH_NTLM) != 0)
- native_scheme = WINHTTP_AUTH_SCHEME_NTLM;
-
- if ((mechanisms & GIT_WINHTTP_AUTH_NEGOTIATE) != 0)
- native_scheme = WINHTTP_AUTH_SCHEME_NEGOTIATE;
-
- if (!native_scheme) {
- git_error_set(GIT_ERROR_NET, "invalid authentication scheme");
+ /*
+ * Autologon policy must be "low" to use default creds.
+ * This is safe as the user has explicitly requested it.
+ */
+ if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_level, sizeof(DWORD))) {
+ git_error_set(GIT_ERROR_OS, "could not configure logon policy");
return -1;
}
- if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD)))
- return -1;
-
- if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER, native_scheme, NULL, NULL, NULL))
+ if (!WinHttpSetCredentials(request, target, native_scheme, NULL, NULL, NULL)) {
+ git_error_set(GIT_ERROR_OS, "could not configure credentials");
return -1;
+ }
return 0;
}
-static int fallback_cred_acquire_cb(
+static int acquire_url_cred(
git_cred **cred,
- const char *url,
- const char *username_from_url,
unsigned int allowed_types,
- void *payload)
+ const char *username,
+ const char *password)
{
- int error = 1;
+ if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)
+ return git_cred_userpass_plaintext_new(cred, username, password);
+
+ if ((allowed_types & GIT_CREDTYPE_DEFAULT) && *username == '\0' && *password == '\0')
+ return git_cred_default_new(cred);
+
+ return 1;
+}
- GIT_UNUSED(username_from_url);
- GIT_UNUSED(payload);
+static int acquire_fallback_cred(
+ git_cred **cred,
+ const char *url,
+ unsigned int allowed_types)
+{
+ int error = 1;
/* If the target URI supports integrated Windows authentication
* as an authentication mechanism */
@@ -253,9 +254,9 @@ static int fallback_cred_acquire_cb(
pISM->lpVtbl->Release(pISM);
}
- if (SUCCEEDED(hCoInitResult))
- /* Only unitialize if the call to CoInitializeEx was successful. */
- CoUninitialize();
+ /* Only unitialize if the call to CoInitializeEx was successful. */
+ if (SUCCEEDED(hCoInitResult))
+ CoUninitialize();
}
git__free(wide_url);
@@ -280,7 +281,7 @@ static int certificate_check(winhttp_stream *s, int valid)
return GIT_ECERTIFICATE;
}
- if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl)
+ if (t->owner->certificate_check_cb == NULL || git__strcmp(t->server.url.scheme, "https") != 0)
return 0;
if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
@@ -292,7 +293,7 @@ static int certificate_check(winhttp_stream *s, int valid)
cert.parent.cert_type = GIT_CERT_X509;
cert.data = cert_ctx->pbCertEncoded;
cert.len = cert_ctx->cbCertEncoded;
- error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload);
+ error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->server.url.host, t->owner->message_cb_payload);
CertFreeCertificateContext(cert_ctx);
if (error == GIT_PASSTHROUGH)
@@ -329,8 +330,23 @@ static void winhttp_stream_close(winhttp_stream *s)
s->sent_request = 0;
}
-#define SCHEME_HTTP "http://"
-#define SCHEME_HTTPS "https://"
+static int apply_credentials(
+ HINTERNET request,
+ git_net_url *url,
+ int target,
+ git_cred *creds,
+ int mechanisms)
+{
+ int error = 0;
+
+ /* If we have creds, just apply them */
+ if (creds && creds->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT)
+ error = apply_userpass_credentials(request, target, mechanisms, creds);
+ else if (creds && creds->credtype == GIT_CREDTYPE_DEFAULT)
+ error = apply_default_credentials(request, target, mechanisms);
+
+ return error;
+}
static int winhttp_stream_connect(winhttp_stream *s)
{
@@ -344,11 +360,13 @@ static int winhttp_stream_connect(winhttp_stream *s)
unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
int default_timeout = TIMEOUT_INFINITE;
int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
+ DWORD autologon_policy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
+
size_t i;
const git_proxy_options *proxy_opts;
/* Prepare URL */
- git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
+ git_buf_printf(&buf, "%s%s", t->server.url.path, s->service_url);
if (git_buf_oom(&buf))
return -1;
@@ -367,13 +385,17 @@ static int winhttp_stream_connect(winhttp_stream *s)
NULL,
WINHTTP_NO_REFERER,
types,
- t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0);
+ git__strcmp(t->server.url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0);
if (!s->request) {
git_error_set(GIT_ERROR_OS, "failed to open request");
goto on_error;
}
+ /* Never attempt default credentials; we'll provide them explicitly. */
+ if (!WinHttpSetOption(s->request, WINHTTP_OPTION_AUTOLOGON_POLICY, &autologon_policy, sizeof(DWORD)))
+ return -1;
+
if (!WinHttpSetTimeouts(s->request, default_timeout, default_connect_timeout, default_timeout, default_timeout)) {
git_error_set(GIT_ERROR_OS, "failed to set timeouts for WinHTTP");
goto on_error;
@@ -382,7 +404,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
proxy_opts = &t->owner->proxy;
if (proxy_opts->type == GIT_PROXY_AUTO) {
/* Set proxy if necessary */
- if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
+ if (git_remote__get_http_proxy(t->owner->owner, (strcmp(t->server.url.scheme, "https") == 0), &proxy_url) < 0)
goto on_error;
}
else if (proxy_opts->type == GIT_PROXY_SPECIFIED) {
@@ -395,38 +417,24 @@ static int winhttp_stream_connect(winhttp_stream *s)
WINHTTP_PROXY_INFO proxy_info;
wchar_t *proxy_wide;
- if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) {
- t->proxy_connection_data.use_ssl = false;
- } else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) {
- t->proxy_connection_data.use_ssl = true;
- } else {
- git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
- return -1;
- }
-
- gitno_connection_data_free_ptrs(&t->proxy_connection_data);
+ git_net_url_dispose(&t->proxy.url);
- if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL,
- &t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0)
+ if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0)
goto on_error;
- if (t->proxy_connection_data.user && t->proxy_connection_data.pass) {
- if (t->proxy_cred) {
- t->proxy_cred->free(t->proxy_cred);
- }
-
- if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0)
- goto on_error;
+ if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) {
+ git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url);
+ error = -1;
+ goto on_error;
}
- if (t->proxy_connection_data.use_ssl)
- git_buf_PUTS(&processed_url, SCHEME_HTTPS);
- else
- git_buf_PUTS(&processed_url, SCHEME_HTTP);
+ git_buf_puts(&processed_url, t->proxy.url.scheme);
+ git_buf_PUTS(&processed_url, "://");
- git_buf_puts(&processed_url, t->proxy_connection_data.host);
- if (t->proxy_connection_data.port)
- git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port);
+ git_buf_puts(&processed_url, t->proxy.url.host);
+
+ if (!git_net_url_is_default_port(&t->proxy.url))
+ git_buf_printf(&processed_url, ":%s", t->proxy.url.port);
if (git_buf_oom(&processed_url)) {
error = -1;
@@ -454,13 +462,8 @@ static int winhttp_stream_connect(winhttp_stream *s)
git__free(proxy_wide);
- if (t->proxy_cred) {
- if (t->proxy_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT) {
- if ((error = apply_userpass_credential_proxy(s->request, t->proxy_cred, t->auth_mechanisms)) < 0)
- goto on_error;
- }
- }
-
+ if ((error = apply_credentials(s->request, &t->proxy.url, WINHTTP_AUTH_TARGET_PROXY, t->proxy.cred, t->proxy.auth_mechanisms)) < 0)
+ goto on_error;
}
/* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
@@ -471,6 +474,7 @@ static int winhttp_stream_connect(winhttp_stream *s)
&disable_redirects,
sizeof(disable_redirects))) {
git_error_set(GIT_ERROR_OS, "failed to disable redirects");
+ error = -1;
goto on_error;
}
@@ -543,32 +547,15 @@ static int winhttp_stream_connect(winhttp_stream *s)
}
/* If requested, disable certificate validation */
- if (t->connection_data.use_ssl) {
+ if (strcmp(t->server.url.scheme, "https") == 0) {
int flags;
if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
goto on_error;
}
- /* If we have a credential on the subtransport, apply it to the request */
- if (t->cred &&
- t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- apply_userpass_credential(s->request, t->auth_mechanisms, t->cred) < 0)
+ if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 0)
goto on_error;
- else if (t->cred &&
- t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
- apply_default_credentials(s->request, t->auth_mechanisms) < 0)
- goto on_error;
-
- /* If no other credentials have been applied and the URL has username and
- * password, use those */
- 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)
- goto on_error;
- if (apply_userpass_credential(s->request, GIT_WINHTTP_AUTH_BASIC, t->url_cred) < 0)
- goto on_error;
- }
/* We've done everything up to calling WinHttpSendRequest. */
@@ -584,9 +571,9 @@ on_error:
}
static int parse_unauthorized_response(
- HINTERNET request,
int *allowed_types,
- int *allowed_mechanisms)
+ int *allowed_mechanisms,
+ HINTERNET request)
{
DWORD supported, first, target;
@@ -741,12 +728,12 @@ static int winhttp_connect(
t->connection = NULL;
/* Prepare port */
- if (git__strntol32(&port, t->connection_data.port,
- strlen(t->connection_data.port), NULL, 10) < 0)
+ if (git__strntol32(&port, t->server.url.port,
+ strlen(t->server.url.port), NULL, 10) < 0)
return -1;
/* Prepare host */
- if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
+ if (git__utf8_to_16_alloc(&wide_host, t->server.url.host) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
return -1;
}
@@ -890,6 +877,59 @@ static int send_request(winhttp_stream *s, size_t len, int ignore_length)
return error;
}
+static int acquire_credentials(
+ HINTERNET request,
+ winhttp_server *server,
+ const char *url_str,
+ git_cred_acquire_cb cred_cb,
+ void *cred_cb_payload)
+{
+ int allowed_types;
+ int error = 1;
+
+ if (parse_unauthorized_response(&allowed_types, &server->auth_mechanisms, request) < 0)
+ return -1;
+
+ if (allowed_types) {
+ git_cred_free(server->cred);
+ server->cred = NULL;
+
+ /* Start with URL-specified credentials, if there were any. */
+ if (!server->url_cred_presented && server->url.username && server->url.password) {
+ error = acquire_url_cred(&server->cred, allowed_types, server->url.username, server->url.password);
+ server->url_cred_presented = 1;
+
+ if (error < 0)
+ return error;
+ }
+
+ /* Next use the user-defined callback, if there is one. */
+ if (error > 0 && cred_cb) {
+ error = cred_cb(&server->cred, url_str, server->url.username, allowed_types, cred_cb_payload);
+
+ /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
+ if (error == GIT_PASSTHROUGH)
+ error = 1;
+ else if (error < 0)
+ return error;
+ }
+
+ /* Finally, invoke the fallback default credential lookup. */
+ if (error > 0) {
+ error = acquire_fallback_cred(&server->cred, url_str, allowed_types);
+
+ if (error < 0)
+ return error;
+ }
+ }
+
+ /*
+ * No error occurred but we could not find appropriate credentials.
+ * This behaves like a pass-through.
+ */
+ return error;
+}
+
static int winhttp_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
@@ -1062,7 +1102,7 @@ replay:
if (!git__prefixcmp_icase(location8, prefix_https)) {
/* Upgrade to secure connection; disconnect and start over */
- if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
+ if (gitno_connection_data_handle_redirect(&t->server.url, location8, s->service_url) < 0) {
git__free(location8);
return -1;
}
@@ -1077,67 +1117,34 @@ replay:
goto replay;
}
- /* Handle proxy authentication failures */
- if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
- int allowed_types;
-
- if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0)
- return -1;
-
- /* TODO: extract the username from the url, no payload? */
- if (t->owner->proxy.credentials) {
- int cred_error = 1;
- cred_error = t->owner->proxy.credentials(&t->proxy_cred, t->owner->proxy.url, NULL, allowed_types, t->owner->proxy.payload);
-
- if (cred_error < 0)
- return cred_error;
- }
-
- winhttp_stream_close(s);
- goto replay;
- }
-
/* Handle authentication failures */
- if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) {
- int allowed_types;
-
- if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanisms) < 0)
- return -1;
-
- if (allowed_types) {
- int cred_error = 1;
-
- git_cred_free(t->cred);
- t->cred = NULL;
- /* Start with the user-supplied credential callback, if present */
- if (t->owner->cred_acquire_cb) {
- cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
- t->connection_data.user, allowed_types, t->owner->cred_acquire_payload);
-
- /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */
- if (cred_error == GIT_PASSTHROUGH)
- cred_error = 1;
- else if (cred_error < 0)
- return cred_error;
- }
-
- /* Invoke the fallback credentials acquisition callback if necessary */
- if (cred_error > 0) {
- cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
- t->connection_data.user, allowed_types, NULL);
-
- if (cred_error < 0)
- return cred_error;
- }
-
- if (!cred_error) {
- assert(t->cred);
-
- winhttp_stream_close(s);
-
- /* Successfully acquired a credential */
- goto replay;
- }
+ if (status_code == HTTP_STATUS_DENIED) {
+ int error = acquire_credentials(s->request,
+ &t->server,
+ t->owner->url,
+ t->owner->cred_acquire_cb,
+ t->owner->cred_acquire_payload);
+
+ if (error < 0) {
+ return error;
+ } else if (!error) {
+ assert(t->server.cred);
+ winhttp_stream_close(s);
+ goto replay;
+ }
+ } else if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) {
+ int error = acquire_credentials(s->request,
+ &t->proxy,
+ t->owner->proxy.url,
+ t->owner->proxy.credentials,
+ t->owner->proxy.payload);
+
+ if (error < 0) {
+ return error;
+ } else if (!error) {
+ assert(t->proxy.cred);
+ winhttp_stream_close(s);
+ goto replay;
}
}
@@ -1496,7 +1503,7 @@ static int winhttp_action(
int ret = -1;
if (!t->connection)
- if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 ||
+ if ((ret = git_net_url_parse(&t->server.url, url)) < 0 ||
(ret = winhttp_connect(t)) < 0)
return ret;
@@ -1538,24 +1545,17 @@ static int winhttp_close(git_smart_subtransport *subtransport)
{
winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
- gitno_connection_data_free_ptrs(&t->connection_data);
- memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
- gitno_connection_data_free_ptrs(&t->proxy_connection_data);
- memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data));
-
- if (t->cred) {
- t->cred->free(t->cred);
- t->cred = NULL;
- }
+ git_net_url_dispose(&t->server.url);
+ git_net_url_dispose(&t->proxy.url);
- if (t->proxy_cred) {
- t->proxy_cred->free(t->proxy_cred);
- t->proxy_cred = NULL;
+ if (t->server.cred) {
+ t->server.cred->free(t->server.cred);
+ t->server.cred = NULL;
}
- if (t->url_cred) {
- t->url_cred->free(t->url_cred);
- t->url_cred = NULL;
+ if (t->proxy.cred) {
+ t->proxy.cred->free(t->proxy.cred);
+ t->proxy.cred = NULL;
}
return winhttp_close_connection(t);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index aaa8ed109..e39fd6f7b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -62,4 +62,4 @@ ADD_TEST(invasive "${libgit2_BINARY_DIR}/libgit2_clar" -v -score::ftruncate -sf
ADD_TEST(online "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline)
ADD_TEST(gitdaemon "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push)
ADD_TEST(ssh "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths)
-ADD_TEST(proxy "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy_credentials_in_url -sonline::clone::proxy_credentials_request)
+ADD_TEST(proxy "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::clone::proxy)
diff --git a/tests/network/redirect.c b/tests/network/redirect.c
new file mode 100644
index 000000000..3fc0b1826
--- /dev/null
+++ b/tests/network/redirect.c
@@ -0,0 +1,113 @@
+#include "clar_libgit2.h"
+#include "net.h"
+#include "netops.h"
+
+static git_net_url conndata;
+
+void test_network_redirect__initialize(void)
+{
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_network_redirect__cleanup(void)
+{
+ git_net_url_dispose(&conndata);
+}
+
+void test_network_redirect__redirect_http(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "http://example.com/foo/bar/baz"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "http://example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_network_redirect__redirect_ssl(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com/foo/bar/baz"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "https://example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_network_redirect__redirect_leaves_root_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com/foo/bar/baz"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "https://example.com/foo/bar/baz", "/foo/bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_network_redirect__redirect_encoded_username_password(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_s(conndata.username, "user/name");
+ cl_assert_equal_s(conndata.password, "pass@word%zyx%v");
+}
+
+void test_network_redirect__redirect_cross_host_denied(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz"));
+ cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
+ "https://foo.com/bar/baz", NULL),
+ -1);
+}
+
+void test_network_redirect__redirect_http_downgrade_denied(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz"));
+ cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata,
+ "http://foo.com/bar/baz", NULL),
+ -1);
+}
+
+void test_network_redirect__redirect_relative(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "/zap/baz/biff?bam", NULL));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
+
+void test_network_redirect__redirect_relative_ssl(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff"));
+ cl_git_pass(gitno_connection_data_handle_redirect(&conndata,
+ "/zap/baz/biff?bam", NULL));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+}
diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c
index 4a3096baa..c2362e628 100644
--- a/tests/network/urlparse.c
+++ b/tests/network/urlparse.c
@@ -1,220 +1,144 @@
#include "clar_libgit2.h"
-#include "netops.h"
+#include "net.h"
-static char *host, *port, *path, *user, *pass;
-static gitno_connection_data conndata;
+static git_net_url conndata;
void test_network_urlparse__initialize(void)
{
- host = port = path = user = pass = NULL;
memset(&conndata, 0, sizeof(conndata));
}
void test_network_urlparse__cleanup(void)
{
-#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; }
- FREE_AND_NULL(host);
- FREE_AND_NULL(port);
- FREE_AND_NULL(path);
- FREE_AND_NULL(user);
- FREE_AND_NULL(pass);
-
- gitno_connection_data_free_ptrs(&conndata);
+ git_net_url_dispose(&conndata);
}
void test_network_urlparse__trivial(void)
{
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "http://example.com/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "8080");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_p(user, NULL);
- cl_assert_equal_p(pass, NULL);
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_urlparse__root(void)
{
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "http://example.com/", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "8080");
- cl_assert_equal_s(path, "/");
- cl_assert_equal_p(user, NULL);
- cl_assert_equal_p(pass, NULL);
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
-void test_network_urlparse__just_hostname(void)
+void test_network_urlparse__implied_root(void)
{
- cl_git_fail_with(GIT_EINVALIDSPEC,
- gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "http://example.com", "8080"));
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_network_urlparse__implied_root_custom_port(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://example.com:42"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "42");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_urlparse__encoded_password(void)
{
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user:pass%2fis%40bad@hostname.com:1234/", "1"));
- cl_assert_equal_s(host, "hostname.com");
- cl_assert_equal_s(port, "1234");
- cl_assert_equal_s(path, "/");
- cl_assert_equal_s(user, "user");
- cl_assert_equal_s(pass, "pass/is@bad");
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass%2fis%40bad@hostname.com:1234/"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "hostname.com");
+ cl_assert_equal_s(conndata.port, "1234");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_urlparse__user(void)
{
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user@example.com/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "8080");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_s(user, "user");
- cl_assert_equal_p(pass, NULL);
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_urlparse__user_pass(void)
{
/* user:pass@hostname.tld/resource */
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user:pass@example.com/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "8080");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_s(user, "user");
- cl_assert_equal_s(pass, "pass");
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
void test_network_urlparse__port(void)
{
/* hostname.tld:port/resource */
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://example.com:9191/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "9191");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_p(user, NULL);
- cl_assert_equal_p(pass, NULL);
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_urlparse__user_port(void)
{
/* user@hostname.tld:port/resource */
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user@example.com:9191/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "9191");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_s(user, "user");
- cl_assert_equal_p(pass, NULL);
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_network_urlparse__user_pass_port(void)
{
/* user:pass@hostname.tld:port/resource */
- cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user:pass@example.com:9191/resource", "8080"));
- cl_assert_equal_s(host, "example.com");
- cl_assert_equal_s(port, "9191");
- cl_assert_equal_s(path, "/resource");
- cl_assert_equal_s(user, "user");
- cl_assert_equal_s(pass, "pass");
-}
-
-void test_network_urlparse__optional_path(void)
-{
- cl_git_fail(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
- "https://user:pass@example.com:9191", "8080"));
-
- cl_git_pass(gitno_extract_url_parts(&host, &port, NULL, &user, &pass,
- "https://user:pass@example.com:9191", "8080"));
-}
-
-void test_network_urlparse__connection_data_http(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "http://example.com/foo/bar/baz", "bar/baz"));
- cl_assert_equal_s(conndata.host, "example.com");
- cl_assert_equal_s(conndata.port, "80");
- cl_assert_equal_s(conndata.path, "/foo/");
- cl_assert_equal_p(conndata.user, NULL);
- cl_assert_equal_p(conndata.pass, NULL);
- cl_assert_equal_i(conndata.use_ssl, false);
-}
-
-void test_network_urlparse__connection_data_ssl(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "https://example.com/foo/bar/baz", "bar/baz"));
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource"));
+ cl_assert_equal_s(conndata.scheme, "https");
cl_assert_equal_s(conndata.host, "example.com");
- cl_assert_equal_s(conndata.port, "443");
- cl_assert_equal_s(conndata.path, "/foo/");
- cl_assert_equal_p(conndata.user, NULL);
- cl_assert_equal_p(conndata.pass, NULL);
- cl_assert_equal_i(conndata.use_ssl, true);
-}
-
-void test_network_urlparse__encoded_username_password(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
- cl_assert_equal_s(conndata.host, "example.com");
- cl_assert_equal_s(conndata.port, "443");
- cl_assert_equal_s(conndata.path, "/foo/");
- cl_assert_equal_s(conndata.user, "user/name");
- cl_assert_equal_s(conndata.pass, "pass@word%zyx%v");
- cl_assert_equal_i(conndata.use_ssl, true);
-}
-
-void test_network_urlparse__connection_data_cross_host_redirect(void)
-{
- conndata.host = git__strdup("bar.com");
- cl_git_fail_with(gitno_connection_data_from_url(&conndata,
- "https://foo.com/bar/baz", NULL),
- -1);
-}
-
-void test_network_urlparse__connection_data_http_downgrade(void)
-{
- conndata.use_ssl = true;
- cl_git_fail_with(gitno_connection_data_from_url(&conndata,
- "http://foo.com/bar/baz", NULL),
- -1);
-}
-
-void test_network_urlparse__connection_data_relative_redirect(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "http://foo.com/bar/baz/biff", NULL));
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "/zap/baz/biff?bam", NULL));
- cl_assert_equal_s(conndata.host, "foo.com");
- cl_assert_equal_s(conndata.port, "80");
- cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
- cl_assert_equal_p(conndata.user, NULL);
- cl_assert_equal_p(conndata.pass, NULL);
- cl_assert_equal_i(conndata.use_ssl, false);
-}
-
-void test_network_urlparse__connection_data_relative_redirect_ssl(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "https://foo.com/bar/baz/biff", NULL));
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "/zap/baz/biff?bam", NULL));
- cl_assert_equal_s(conndata.host, "foo.com");
- cl_assert_equal_s(conndata.port, "443");
- cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
- cl_assert_equal_p(conndata.user, NULL);
- cl_assert_equal_p(conndata.pass, NULL);
- cl_assert_equal_i(conndata.use_ssl, true);
-}
-
-/* Run this under valgrind */
-void test_network_urlparse__connection_data_cleanup(void)
-{
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "http://foo.com/bar/baz/biff", "baz/biff"));
- cl_git_pass(gitno_connection_data_from_url(&conndata,
- "https://foo.com/bar/baz/biff", "baz/biff"));
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
diff --git a/tests/online/clone.c b/tests/online/clone.c
index 0d0334cbb..b7042f3d6 100644
--- a/tests/online/clone.c
+++ b/tests/online/clone.c
@@ -682,12 +682,6 @@ void test_online_clone__ssh_memory_auth(void)
cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
-void test_online_clone__url_with_no_path_returns_EINVALIDSPEC(void)
-{
- cl_git_fail_with(git_clone(&g_repo, "http://github.com", "./foo", &g_options),
- GIT_EINVALIDSPEC);
-}
-
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
@@ -847,3 +841,25 @@ void test_online_clone__proxy_auto_not_detected(void)
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
+
+void test_online_clone__proxy_cred_callback_after_failed_url_creds(void)
+{
+ git_buf url = GIT_BUF_INIT;
+
+ if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
+ cl_skip();
+
+ cl_git_pass(git_buf_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/",
+ _remote_proxy_scheme ? _remote_proxy_scheme : "http",
+ _remote_proxy_host));
+
+ g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
+ g_options.fetch_opts.proxy_opts.url = url.ptr;
+ g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
+ g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
+ called_proxy_creds = 0;
+ cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
+ cl_assert(called_proxy_creds);
+
+ git_buf_dispose(&url);
+}