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) ...@@ -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) 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.") 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) IF (UNIX AND NOT APPLE)
OPTION(ENABLE_REPRODUCIBLE_BUILDS "Enable reproducible builds" OFF) OPTION(ENABLE_REPRODUCIBLE_BUILDS "Enable reproducible builds" OFF)
ENDIF() ENDIF()
......
...@@ -38,18 +38,25 @@ Write-Host "## Configuring test environment" ...@@ -38,18 +38,25 @@ Write-Host "## Configuring test environment"
Write-Host "##############################################################################" Write-Host "##############################################################################"
if (-not $Env:SKIP_PROXY_TESTS) { 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 ""
Write-Host "Starting HTTP proxy..." Write-Host "Starting HTTP proxy (NTLM)..."
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 --port 8090 --credentials foo:bar --auth-type ntlm --quiet
javaw -jar poxyproxy.jar -d --port 8080 --credentials foo:bar --quiet
} }
Write-Host "" if (-not $Env:SKIP_OFFLINE_TESTS) {
Write-Host "##############################################################################" Write-Host ""
Write-Host "## Running (offline) tests" Write-Host "##############################################################################"
Write-Host "##############################################################################" Write-Host "## Running (offline) tests"
Write-Host "##############################################################################"
run_test offline run_test offline
}
if ($Env:RUN_INVASIVE_TESTS) { if ($Env:RUN_INVASIVE_TESTS) {
Write-Host "" Write-Host ""
...@@ -76,14 +83,24 @@ if (-not $Env:SKIP_ONLINE_TESTS) { ...@@ -76,14 +83,24 @@ if (-not $Env:SKIP_ONLINE_TESTS) {
} }
if (-not $Env:SKIP_PROXY_TESTS) { if (-not $Env:SKIP_PROXY_TESTS) {
# Test HTTP Basic authentication
Write-Host "" Write-Host ""
Write-Host "Running proxy tests" Write-Host "Running proxy tests (Basic authentication)"
Write-Host "" Write-Host ""
$Env:GITTEST_REMOTE_PROXY_HOST="localhost:8080" $Env:GITTEST_REMOTE_PROXY_HOST="localhost:8080"
$Env:GITTEST_REMOTE_PROXY_USER="foo" $Env:GITTEST_REMOTE_PROXY_USER="foo"
$Env:GITTEST_REMOTE_PROXY_PASS="bar" $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 run_test proxy
$Env:GITTEST_REMOTE_PROXY_HOST=$null $Env:GITTEST_REMOTE_PROXY_HOST=$null
......
...@@ -78,9 +78,15 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then ...@@ -78,9 +78,15 @@ if [ -z "$SKIP_GITDAEMON_TESTS" ]; then
fi fi
if [ -z "$SKIP_PROXY_TESTS" ]; then if [ -z "$SKIP_PROXY_TESTS" ]; then
echo "Starting HTTP proxy..." curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.7.0/poxyproxy-0.7.0.jar >poxyproxy.jar
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 & 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 fi
if [ -z "$SKIP_SSH_TESTS" ]; then if [ -z "$SKIP_SSH_TESTS" ]; then
...@@ -175,7 +181,7 @@ fi ...@@ -175,7 +181,7 @@ fi
if [ -z "$SKIP_PROXY_TESTS" ]; then if [ -z "$SKIP_PROXY_TESTS" ]; then
echo "" echo ""
echo "Running proxy tests" echo "Running proxy tests (Basic authentication)"
echo "" echo ""
export GITTEST_REMOTE_PROXY_HOST="localhost:8080" export GITTEST_REMOTE_PROXY_HOST="localhost:8080"
...@@ -185,6 +191,18 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then ...@@ -185,6 +191,18 @@ if [ -z "$SKIP_PROXY_TESTS" ]; then
unset GITTEST_REMOTE_PROXY_HOST unset GITTEST_REMOTE_PROXY_HOST
unset GITTEST_REMOTE_PROXY_USER unset GITTEST_REMOTE_PROXY_USER
unset GITTEST_REMOTE_PROXY_PASS 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 fi
if [ -z "$SKIP_SSH_TESTS" ]; then 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.
*/
#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 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 <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);
}
/*
* 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() ...@@ -396,6 +396,15 @@ ELSE()
ENDIF() ENDIF()
ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support") 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 # Optional external dependency: libgssapi
IF (USE_GSSAPI) IF (USE_GSSAPI)
FIND_PACKAGE(GSSAPI) FIND_PACKAGE(GSSAPI)
......
...@@ -25,8 +25,10 @@ ...@@ -25,8 +25,10 @@
#cmakedefine GIT_SSH 1 #cmakedefine GIT_SSH 1
#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1 #cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_GSSAPI 1 #cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_WINHTTP 1 #cmakedefine GIT_WINHTTP 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_HTTPS 1 #cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 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) ...@@ -119,192 +119,79 @@ int gitno__match_host(const char *pattern, const char *host)
return -1; return -1;
} }
static const char *default_port_http = "80"; int gitno_connection_data_handle_redirect(
static const char *default_port_https = "443"; git_net_url *url,
const char *redirect_str,
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,
const char *service_suffix) const char *service_suffix)
{ {
int error = -1; git_net_url tmp = GIT_NET_URL_INIT;
const char *default_port = NULL, *path_search_start = NULL; int error = 0;
char *original_host = NULL;
/* service_suffix is optional */
assert(data && url);
/* Save these for comparison later */ assert(url && redirect_str);
original_host = data->host;
data->host = NULL;
gitno_connection_data_free_ptrs(data);
if (!git__prefixcmp(url, prefix_http)) { if (redirect_str[0] == '/') {
path_search_start = url + strlen(prefix_http); git__free(url->path);
default_port = default_port_http;
if (data->use_ssl) { if ((url->path = git__strdup(redirect_str)) == NULL) {
git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed"); error = -1;
goto cleanup; goto done;
}
} else if (!git__prefixcmp(url, prefix_https)) {
path_search_start = url + strlen(prefix_https);
default_port = default_port_https;
data->use_ssl = true;
} else if (url[0] == '/')
default_port = gitno__default_port(data);
if (!default_port) {
git_error_set(GIT_ERROR_NET, "unrecognized URL prefix");
goto cleanup;
} }
} else {
git_net_url *original = url;
error = gitno_extract_url_parts( if ((error = git_net_url_parse(&tmp, redirect_str)) < 0)
&data->host, &data->port, &data->path, &data->user, &data->pass, goto done;
url, default_port);
if (url[0] == '/') { /* Validate that this is a legal redirection */
/* Relative redirect; reuse original host name and port */
path_search_start = url;
git__free(data->host);
data->host = original_host;
original_host = NULL;
}
if (!error) { if (original->scheme &&
const char *path = strchr(path_search_start, '/'); strcmp(original->scheme, tmp.scheme) != 0 &&
size_t pathlen = strlen(path); strcmp(tmp.scheme, "https") != 0) {
size_t suffixlen = service_suffix ? strlen(service_suffix) : 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; 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( if (original->host &&
char **host_out, git__strcasecmp(original->host, tmp.host) != 0) {
char **port_out, git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
char **path_out, original->host, tmp.host);
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 (http_parser_parse_url(url, strlen(url), false, &u)) { error = -1;
git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url);
error = GIT_EINVALIDSPEC;
goto done; goto done;
} }
has_host = !!(u.field_set & (1 << UF_HOST)); git_net_url_swap(url, &tmp);
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);
} }
if (has_port) { /* Remove the service suffix if it was given to us */
const char *url_port = url + u.field_data[UF_PORT].off; if (service_suffix) {
size_t url_port_len = u.field_data[UF_PORT].len; const char *service_query = strchr(service_suffix, '?');
git_buf_put(&port, url_port, url_port_len); size_t suffix_len = service_query ?
} else { (size_t)(service_query - service_suffix) : strlen(service_suffix);
git_buf_puts(&port, default_port); size_t path_len = strlen(url->path);
}
if (has_path && path_out) { if (suffix_len && path_len >= suffix_len) {
const char *url_path = url + u.field_data[UF_PATH].off; size_t suffix_offset = path_len - suffix_len;
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 (has_userinfo) { if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
const char *url_userinfo = url + u.field_data[UF_USERINFO].off; (!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
size_t url_userinfo_len = u.field_data[UF_USERINFO].len; /* Ensure we leave a minimum of '/' as the path */
const char *colon = memchr(url_userinfo, ':', url_userinfo_len); if (suffix_offset == 0)
suffix_offset++;
if (colon) { url->path[suffix_offset] = '\0';
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__free(url->query);
git_buf_decode_percent(&password, url_password, url_password_len); url->query = NULL;
} else { }
git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
} }
} }
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: done:
git_buf_dispose(&host); git_net_url_dispose(&tmp);
git_buf_dispose(&port);
git_buf_dispose(&path);
git_buf_dispose(&username);
git_buf_dispose(&password);
return error; return error;
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "posix.h" #include "posix.h"
#include "stream.h" #include "stream.h"
#include "net.h"
#ifdef GIT_OPENSSL #ifdef GIT_OPENSSL
# include <openssl/ssl.h> # include <openssl/ssl.h>
...@@ -64,38 +65,15 @@ int gitno_recv(gitno_buffer *buf); ...@@ -64,38 +65,15 @@ int gitno_recv(gitno_buffer *buf);
void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume(gitno_buffer *buf, const char *ptr);
void gitno_consume_n(gitno_buffer *buf, size_t cons); 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, * This replaces all the pointers in `data` with freshly-allocated strings,
* that the caller is responsible for freeing. * that the caller is responsible for freeing.
* `gitno_connection_data_free_ptrs` is good for this. * `gitno_connection_data_free_ptrs` is good for this.
*/ */
int gitno_connection_data_from_url( int gitno_connection_data_handle_redirect(
gitno_connection_data *data, git_net_url *data,
const char *url, const char *url,
const char *service_suffix); 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 #endif
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
static int basic_next_token( static int basic_next_token(
git_buf *out, git_buf *out,
git_http_auth_context *ctx, git_http_auth_context *ctx,
const char *header_name,
git_cred *c) git_cred *c)
{ {
git_cred_userpass_plaintext *cred; git_cred_userpass_plaintext *cred;
...@@ -32,9 +31,8 @@ static int basic_next_token( ...@@ -32,9 +31,8 @@ static int basic_next_token(
git_buf_printf(&raw, "%s:%s", cred->username, cred->password); git_buf_printf(&raw, "%s:%s", cred->username, cred->password);
if (git_buf_oom(&raw) || if (git_buf_oom(&raw) ||
git_buf_printf(out, "%s: Basic ", header_name) < 0 || git_buf_puts(out, "Basic ") < 0 ||
git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 || git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0)
git_buf_puts(out, "\r\n") < 0)
goto on_error; goto on_error;
error = 0; error = 0;
...@@ -50,24 +48,26 @@ on_error: ...@@ -50,24 +48,26 @@ on_error:
static git_http_auth_context basic_context = { static git_http_auth_context basic_context = {
GIT_AUTHTYPE_BASIC, GIT_AUTHTYPE_BASIC,
GIT_CREDTYPE_USERPASS_PLAINTEXT, GIT_CREDTYPE_USERPASS_PLAINTEXT,
0,
NULL, NULL,
basic_next_token, basic_next_token,
NULL,
NULL NULL
}; };
int git_http_auth_basic( 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; *out = &basic_context;
return 0; return 0;
} }
int git_http_auth_dummy( 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; *out = NULL;
return 0; return 0;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
typedef enum { typedef enum {
GIT_AUTHTYPE_BASIC = 1, GIT_AUTHTYPE_BASIC = 1,
GIT_AUTHTYPE_NEGOTIATE = 2, GIT_AUTHTYPE_NEGOTIATE = 2,
GIT_AUTHTYPE_NTLM = 4,
} git_http_authtype_t; } git_http_authtype_t;
typedef struct git_http_auth_context git_http_auth_context; typedef struct git_http_auth_context git_http_auth_context;
...@@ -27,11 +28,17 @@ struct git_http_auth_context { ...@@ -27,11 +28,17 @@ struct git_http_auth_context {
/** Supported credentials */ /** Supported credentials */
git_credtype_t credtypes; git_credtype_t credtypes;
/** Connection affinity or request affinity */
unsigned connection_affinity : 1;
/** Sets the challenge on the authentication context */ /** Sets the challenge on the authentication context */
int (*set_challenge)(git_http_auth_context *ctx, const char *challenge); int (*set_challenge)(git_http_auth_context *ctx, const char *challenge);
/** Gets the next authentication token from the context */ /** 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 */ /** Frees the authentication context */
void (*free)(git_http_auth_context *ctx); void (*free)(git_http_auth_context *ctx);
...@@ -50,15 +57,15 @@ typedef struct { ...@@ -50,15 +57,15 @@ typedef struct {
/** Function to initialize an authentication context */ /** Function to initialize an authentication context */
int (*init_context)( int (*init_context)(
git_http_auth_context **out, git_http_auth_context **out,
const gitno_connection_data *connection_data); const git_net_url *url);
} git_http_auth_scheme; } git_http_auth_scheme;
int git_http_auth_dummy( int git_http_auth_dummy(
git_http_auth_context **out, git_http_auth_context **out,
const gitno_connection_data *connection_data); const git_net_url *url);
int git_http_auth_basic( int git_http_auth_basic(
git_http_auth_context **out, git_http_auth_context **out,
const gitno_connection_data *connection_data); const git_net_url *url);
#endif #endif
...@@ -73,7 +73,6 @@ static int negotiate_set_challenge( ...@@ -73,7 +73,6 @@ static int negotiate_set_challenge(
static int negotiate_next_token( static int negotiate_next_token(
git_buf *buf, git_buf *buf,
git_http_auth_context *c, git_http_auth_context *c,
const char *header_name,
git_cred *cred) git_cred *cred)
{ {
http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
...@@ -156,9 +155,8 @@ static int negotiate_next_token( ...@@ -156,9 +155,8 @@ static int negotiate_next_token(
goto done; 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_encode_base64(buf, output_token.value, output_token.length);
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf)) if (git_buf_oom(buf))
error = -1; error = -1;
...@@ -170,6 +168,15 @@ done: ...@@ -170,6 +168,15 @@ done:
return error; 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) static void negotiate_context_free(git_http_auth_context *c)
{ {
http_auth_negotiate_context *ctx = (http_auth_negotiate_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) ...@@ -194,7 +201,7 @@ static void negotiate_context_free(git_http_auth_context *c)
static int negotiate_init_context( static int negotiate_init_context(
http_auth_negotiate_context *ctx, http_auth_negotiate_context *ctx,
const gitno_connection_data *connection_data) const git_net_url *url)
{ {
OM_uint32 status_major, status_minor; OM_uint32 status_major, status_minor;
gss_OID item, *oid; gss_OID item, *oid;
...@@ -235,7 +242,7 @@ static int negotiate_init_context( ...@@ -235,7 +242,7 @@ static int negotiate_init_context(
} }
git_buf_puts(&ctx->target, "HTTP@"); 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)) if (git_buf_oom(&ctx->target))
return -1; return -1;
...@@ -248,7 +255,7 @@ static int negotiate_init_context( ...@@ -248,7 +255,7 @@ static int negotiate_init_context(
int git_http_auth_negotiate( int git_http_auth_negotiate(
git_http_auth_context **out, git_http_auth_context **out,
const gitno_connection_data *connection_data) const git_net_url *url)
{ {
http_auth_negotiate_context *ctx; http_auth_negotiate_context *ctx;
...@@ -257,15 +264,17 @@ int git_http_auth_negotiate( ...@@ -257,15 +264,17 @@ int git_http_auth_negotiate(
ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
GIT_ERROR_CHECK_ALLOC(ctx); GIT_ERROR_CHECK_ALLOC(ctx);
if (negotiate_init_context(ctx, connection_data) < 0) { if (negotiate_init_context(ctx, url) < 0) {
git__free(ctx); git__free(ctx);
return -1; return -1;
} }
ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE; ctx->parent.type = GIT_AUTHTYPE_NEGOTIATE;
ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT; ctx->parent.credtypes = GIT_CREDTYPE_DEFAULT;
ctx->parent.connection_affinity = 1;
ctx->parent.set_challenge = negotiate_set_challenge; ctx->parent.set_challenge = negotiate_set_challenge;
ctx->parent.next_token = negotiate_next_token; ctx->parent.next_token = negotiate_next_token;
ctx->parent.is_complete = negotiate_is_complete;
ctx->parent.free = negotiate_context_free; ctx->parent.free = negotiate_context_free;
*out = (git_http_auth_context *)ctx; *out = (git_http_auth_context *)ctx;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
extern int git_http_auth_negotiate( extern int git_http_auth_negotiate(
git_http_auth_context **out, git_http_auth_context **out,
const gitno_connection_data *connection_data); const git_net_url *url);
#else #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( ...@@ -192,8 +192,9 @@ static int _git_uploadpack_ls(
const char *url, const char *url,
git_smart_subtransport_stream **stream) 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 *stream_url = url;
const char *host, *port;
git_proto_stream *s; git_proto_stream *s;
int error; int error;
...@@ -202,17 +203,15 @@ static int _git_uploadpack_ls( ...@@ -202,17 +203,15 @@ static int _git_uploadpack_ls(
if (!git__prefixcmp(url, prefix_git)) if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(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; 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); error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
git_net_url_dispose(&urldata);
if (error < 0) { if (error < 0) {
git_proto_stream_free(*stream); git_proto_stream_free(*stream);
...@@ -251,7 +250,7 @@ static int _git_receivepack_ls( ...@@ -251,7 +250,7 @@ static int _git_receivepack_ls(
const char *url, const char *url,
git_smart_subtransport_stream **stream) 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 *stream_url = url;
git_proto_stream *s; git_proto_stream *s;
int error; int error;
...@@ -260,16 +259,12 @@ static int _git_receivepack_ls( ...@@ -260,16 +259,12 @@ static int _git_receivepack_ls(
if (!git__prefixcmp(url, prefix_git)) if (!git__prefixcmp(url, prefix_git))
stream_url += strlen(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; 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_net_url_dispose(&urldata);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
if (error < 0) { if (error < 0) {
git_proto_stream_free(*stream); git_proto_stream_free(*stream);
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
#include "buffer.h" #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) GIT_INLINE(int) git_http__user_agent(git_buf *buf)
{ {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "global.h" #include "global.h"
#include "git2.h" #include "git2.h"
#include "buffer.h" #include "buffer.h"
#include "net.h"
#include "netops.h" #include "netops.h"
#include "smart.h" #include "smart.h"
#include "cred.h" #include "cred.h"
...@@ -258,8 +259,7 @@ static int ssh_stream_alloc( ...@@ -258,8 +259,7 @@ static int ssh_stream_alloc(
} }
static int git_ssh_extract_url_parts( static int git_ssh_extract_url_parts(
char **host, git_net_url *urldata,
char **username,
const char *url) const char *url)
{ {
char *colon, *at; char *colon, *at;
...@@ -271,11 +271,11 @@ static int git_ssh_extract_url_parts( ...@@ -271,11 +271,11 @@ static int git_ssh_extract_url_parts(
at = strchr(url, '@'); at = strchr(url, '@');
if (at) { if (at) {
start = at + 1; start = at + 1;
*username = git__substrdup(url, at - url); urldata->username = git__substrdup(url, at - url);
GIT_ERROR_CHECK_ALLOC(*username); GIT_ERROR_CHECK_ALLOC(urldata->username);
} else { } else {
start = url; start = url;
*username = NULL; urldata->username = NULL;
} }
if (colon == NULL || (colon < start)) { if (colon == NULL || (colon < start)) {
...@@ -283,8 +283,8 @@ static int git_ssh_extract_url_parts( ...@@ -283,8 +283,8 @@ static int git_ssh_extract_url_parts(
return -1; return -1;
} }
*host = git__substrdup(start, colon - start); urldata->host = git__substrdup(start, colon - start);
GIT_ERROR_CHECK_ALLOC(*host); GIT_ERROR_CHECK_ALLOC(urldata->host);
return 0; return 0;
} }
...@@ -506,14 +506,15 @@ static int _git_ssh_session_create( ...@@ -506,14 +506,15 @@ static int _git_ssh_session_create(
return 0; return 0;
} }
#define SSH_DEFAULT_PORT "22"
static int _git_ssh_setup_conn( static int _git_ssh_setup_conn(
ssh_subtransport *t, ssh_subtransport *t,
const char *url, const char *url,
const char *cmd, const char *cmd,
git_smart_subtransport_stream **stream) 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 *default_port="22";
int auth_methods, error = 0; int auth_methods, error = 0;
size_t i; size_t i;
ssh_stream *s; ssh_stream *s;
...@@ -535,19 +536,22 @@ static int _git_ssh_setup_conn( ...@@ -535,19 +536,22 @@ static int _git_ssh_setup_conn(
const char *p = ssh_prefixes[i]; const char *p = ssh_prefixes[i];
if (!git__prefixcmp(url, p)) { 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 done;
goto post_extract; 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; 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: 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) (error = git_stream_connect(s->io)) < 0)
goto done; goto done;
...@@ -583,7 +587,7 @@ post_extract: ...@@ -583,7 +587,7 @@ post_extract:
cert_ptr = &cert; 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 (error < 0 && error != GIT_PASSTHROUGH) {
if (!git_error_last()) if (!git_error_last())
...@@ -594,21 +598,21 @@ post_extract: ...@@ -594,21 +598,21 @@ post_extract:
} }
/* we need the username to ask for auth methods */ /* 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) if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0)
goto done; goto done;
user = git__strdup(((git_cred_username *) cred)->username); urldata.username = git__strdup(((git_cred_username *) cred)->username);
cred->free(cred); cred->free(cred);
cred = NULL; cred = NULL;
if (!user) if (!urldata.username)
goto done; goto done;
} else if (user && pass) { } else if (urldata.username && urldata.password) {
if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) if ((error = git_cred_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0)
goto done; 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; goto done;
error = GIT_EAUTH; error = GIT_EAUTH;
...@@ -622,10 +626,10 @@ post_extract: ...@@ -622,10 +626,10 @@ post_extract:
cred = NULL; 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; 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"); git_error_set(GIT_ERROR_SSH, "username does not match previous request");
error = -1; error = -1;
goto done; goto done;
...@@ -662,11 +666,7 @@ done: ...@@ -662,11 +666,7 @@ done:
if (cred) if (cred)
cred->free(cred); cred->free(cred);
git__free(host); git_net_url_dispose(&urldata);
git__free(port);
git__free(path);
git__free(user);
git__free(pass);
return error; return error;
} }
......
...@@ -62,4 +62,4 @@ ADD_TEST(invasive "${libgit2_BINARY_DIR}/libgit2_clar" -v -score::ftruncate -sf ...@@ -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(online "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline)
ADD_TEST(gitdaemon "${libgit2_BINARY_DIR}/libgit2_clar" -v -sonline::push) 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(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);
}
...@@ -682,12 +682,6 @@ void test_online_clone__ssh_memory_auth(void) ...@@ -682,12 +682,6 @@ void test_online_clone__ssh_memory_auth(void)
cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options)); 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) static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{ {
GIT_UNUSED(cert); GIT_UNUSED(cert);
...@@ -847,3 +841,25 @@ void test_online_clone__proxy_auto_not_detected(void) ...@@ -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)); 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