Unverified Commit 110b5895 by Edward Thomson Committed by GitHub

Merge pull request #5052 from libgit2/ethomson/netrefactor

Add NTLM support for HTTP(s) servers and proxies
parents f4584a1e 7ea8630e
......@@ -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()
......
......@@ -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
......
......@@ -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
......
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})
/*
* 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__ */
/*
* 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__ */
/*
* 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);
}
/*
* 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__ */
/*
* 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);
}
}
/*
* 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__ */
/*
* 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);
}
/*
* 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__ */
/*
* 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);
}
/*
* 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__ */
/*
* 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__ */
/*
* 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__ */
/*
* 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);
}
/*
* 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);
}
// 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
/*
* 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;
}
/*
* 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__ */
......@@ -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)
......
......@@ -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
......
/*
* 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;
}
/*
* 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
......@@ -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;
}
} 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;
if ((url->path = git__strdup(redirect_str)) == NULL) {
error = -1;
goto done;
}
} 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 (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);
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);
}
/* 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;
}
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;
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);
if (http_parser_parse_url(url, strlen(url), false, &u)) {
git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url);
error = GIT_EINVALIDSPEC;
error = -1;
goto done;
}
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));
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);
git_net_url_swap(url, &tmp);
}
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);
}
/* 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_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;
}
if (suffix_len && path_len >= suffix_len) {
size_t suffix_offset = path_len - suffix_len;
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 (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 (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);
url->path[suffix_offset] = '\0';
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;
}
......@@ -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
......@@ -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;
......
......@@ -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
......@@ -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;
......
......@@ -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
......
/*
* 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 */
/*
* 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
......@@ -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);
......
......@@ -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;
cred = server->url_cred;
}
git_buf token = GIT_BUF_INIT;
int error = 0;
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)
{
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_context *context;
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;
}
/*
* 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 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 (!allowed_types) {
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;
error = 1;
}
}
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;
}
}
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;
} else {
assert(*creds);
}
if (!((*creds)->credtype & allowed_types)) {
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. */
/* 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;
}
}
}
git_error_set(GIT_ERROR_NET, "%s authentication required but no callback set",
type);
t->parse_error = PARSE_ERROR_GENERIC;
return t->parse_error;
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;
git_net_url_dispose(&t->proxy.url);
if (t->proxy.url.use_ssl) {
git_error_set(GIT_ERROR_NET, "SSL connections to proxy are not supported");
return -1;
}
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;
}
/* 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);
}
......
......@@ -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)
{
......
......@@ -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;
}
......
......@@ -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);
GIT_UNUSED(username_from_url);
GIT_UNUSED(payload);
if ((allowed_types & GIT_CREDTYPE_DEFAULT) && *username == '\0' && *password == '\0')
return git_cred_default_new(cred);
return 1;
}
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,8 +254,8 @@ static int fallback_cred_acquire_cb(
pISM->lpVtbl->Release(pISM);
}
if (SUCCEEDED(hCoInitResult))
/* Only unitialize if the call to CoInitializeEx was successful. */
if (SUCCEEDED(hCoInitResult))
CoUninitialize();
}
......@@ -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)
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.url.host);
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);
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,14 +462,9 @@ 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)
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?
* http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
......@@ -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)
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)
if ((error = apply_credentials(s->request, &t->server.url, WINHTTP_AUTH_TARGET_SERVER, t->server.cred, t->server.auth_mechanisms)) < 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,69 +1117,36 @@ 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;
}
/* Handle authentication failures */
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);
/* 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);
if (error < 0) {
return error;
} else if (!error) {
assert(t->proxy.cred);
winhttp_stream_close(s);
/* Successfully acquired a credential */
goto replay;
}
}
}
if (HTTP_STATUS_OK != status_code) {
git_error_set(GIT_ERROR_NET, "request failed with status code: %lu", status_code);
......@@ -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);
......
......@@ -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)
#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);
}
#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);
}
......@@ -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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment