Commit a7f65f03 by Edward Thomson

ntlm: add ntlmclient as a dependency

Include https://github.com/ethomson/ntlmclient as a dependency.
parent 79fc8281
......@@ -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()
......
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)
......
......@@ -27,6 +27,7 @@
#cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_WINHTTP 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 1
......
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