diff options
Diffstat (limited to 'lib/http_ntlm.c')
-rw-r--r-- | lib/http_ntlm.c | 1305 |
1 files changed, 1305 insertions, 0 deletions
diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c new file mode 100644 index 000000000..f5b696a69 --- /dev/null +++ b/lib/http_ntlm.c @@ -0,0 +1,1305 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +#include "setup.h" + +/* NTLM details: + + http://davenport.sourceforge.net/ntlm.html + http://www.innovation.ch/java/ntlm.html + + Another implementation: + http://lxr.mozilla.org/mozilla/source/security/manager/ssl/src/nsNTLMAuthModule.cpp + +*/ + +#ifndef CURL_DISABLE_HTTP +#ifdef USE_NTLM + +#define DEBUG_ME 0 + +/* -- WIN32 approved -- */ +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <ctype.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) +#include <netdb.h> +#endif + +#include "urldata.h" +#include "easyif.h" /* for Curl_convert_... prototypes */ +#include "sendf.h" +#include "rawstr.h" +#include "curl_base64.h" +#include "http_ntlm.h" +#include "url.h" +#include "curl_gethostname.h" +#include "curl_memory.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include <curl/mprintf.h> + +/* "NTLMSSP" signature is always in ASCII regardless of the platform */ +#define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" + +#ifdef USE_SSLEAY +#include "ssluse.h" +# ifdef USE_OPENSSL +# include <openssl/des.h> +# ifndef OPENSSL_NO_MD4 +# include <openssl/md4.h> +# endif +# include <openssl/md5.h> +# include <openssl/ssl.h> +# include <openssl/rand.h> +# else +# include <des.h> +# ifndef OPENSSL_NO_MD4 +# include <md4.h> +# endif +# include <md5.h> +# include <ssl.h> +# include <rand.h> +# endif + +#if OPENSSL_VERSION_NUMBER < 0x00907001L +#define DES_key_schedule des_key_schedule +#define DES_cblock des_cblock +#define DES_set_odd_parity des_set_odd_parity +#define DES_set_key des_set_key +#define DES_ecb_encrypt des_ecb_encrypt + +/* This is how things were done in the old days */ +#define DESKEY(x) x +#define DESKEYARG(x) x +#else +/* Modern version */ +#define DESKEYARG(x) *x +#define DESKEY(x) &x +#endif + +#ifdef OPENSSL_NO_MD4 +/* This requires MD4, but OpenSSL was compiled without it */ +#define USE_NTRESPONSES 0 +#define USE_NTLM2SESSION 0 +#endif + +#elif defined(USE_GNUTLS) + +#include "gtls.h" +#include <gcrypt.h> + +#define MD5_DIGEST_LENGTH 16 +#define MD4_DIGEST_LENGTH 16 + +#elif defined(USE_NSS) + +#include "curl_md4.h" +#include "nssg.h" +#include <nss.h> +#include <pk11pub.h> +#include <hasht.h> +#define MD5_DIGEST_LENGTH MD5_LENGTH + +#elif defined(USE_WINDOWS_SSPI) + +#include "curl_sspi.h" + +#else +# error "Can't compile NTLM support without a crypto library." +#endif + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef USE_NTRESPONSES +/* Define this to make the type-3 message include the NT response message */ +#define USE_NTRESPONSES 1 + +/* Define this to make the type-3 message include the NTLM2Session response + message, requires USE_NTRESPONSES. */ +#define USE_NTLM2SESSION 1 +#endif + +#ifndef USE_WINDOWS_SSPI +/* this function converts from the little endian format used in the incoming + package to whatever endian format we're using natively */ +static unsigned int readint_le(unsigned char *buf) /* must point to a + 4 bytes buffer*/ +{ + return ((unsigned int)buf[0]) | ((unsigned int)buf[1] << 8) | + ((unsigned int)buf[2] << 16) | ((unsigned int)buf[3] << 24); +} +#endif + +#if DEBUG_ME +# define DEBUG_OUT(x) x +static void print_flags(FILE *handle, unsigned long flags) +{ + if(flags & NTLMFLAG_NEGOTIATE_UNICODE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_UNICODE "); + if(flags & NTLMFLAG_NEGOTIATE_OEM) + fprintf(handle, "NTLMFLAG_NEGOTIATE_OEM "); + if(flags & NTLMFLAG_REQUEST_TARGET) + fprintf(handle, "NTLMFLAG_REQUEST_TARGET "); + if(flags & (1<<3)) + fprintf(handle, "NTLMFLAG_UNKNOWN_3 "); + if(flags & NTLMFLAG_NEGOTIATE_SIGN) + fprintf(handle, "NTLMFLAG_NEGOTIATE_SIGN "); + if(flags & NTLMFLAG_NEGOTIATE_SEAL) + fprintf(handle, "NTLMFLAG_NEGOTIATE_SEAL "); + if(flags & NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE "); + if(flags & NTLMFLAG_NEGOTIATE_LM_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_LM_KEY "); + if(flags & NTLMFLAG_NEGOTIATE_NETWARE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_NETWARE "); + if(flags & NTLMFLAG_NEGOTIATE_NTLM_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM_KEY "); + if(flags & (1<<10)) + fprintf(handle, "NTLMFLAG_UNKNOWN_10 "); + if(flags & NTLMFLAG_NEGOTIATE_ANONYMOUS) + fprintf(handle, "NTLMFLAG_NEGOTIATE_ANONYMOUS "); + if(flags & NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED) + fprintf(handle, "NTLMFLAG_NEGOTIATE_DOMAIN_SUPPLIED "); + if(flags & NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED) + fprintf(handle, "NTLMFLAG_NEGOTIATE_WORKSTATION_SUPPLIED "); + if(flags & NTLMFLAG_NEGOTIATE_LOCAL_CALL) + fprintf(handle, "NTLMFLAG_NEGOTIATE_LOCAL_CALL "); + if(flags & NTLMFLAG_NEGOTIATE_ALWAYS_SIGN) + fprintf(handle, "NTLMFLAG_NEGOTIATE_ALWAYS_SIGN "); + if(flags & NTLMFLAG_TARGET_TYPE_DOMAIN) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_DOMAIN "); + if(flags & NTLMFLAG_TARGET_TYPE_SERVER) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_SERVER "); + if(flags & NTLMFLAG_TARGET_TYPE_SHARE) + fprintf(handle, "NTLMFLAG_TARGET_TYPE_SHARE "); + if(flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) + fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM2_KEY "); + if(flags & NTLMFLAG_REQUEST_INIT_RESPONSE) + fprintf(handle, "NTLMFLAG_REQUEST_INIT_RESPONSE "); + if(flags & NTLMFLAG_REQUEST_ACCEPT_RESPONSE) + fprintf(handle, "NTLMFLAG_REQUEST_ACCEPT_RESPONSE "); + if(flags & NTLMFLAG_REQUEST_NONNT_SESSION_KEY) + fprintf(handle, "NTLMFLAG_REQUEST_NONNT_SESSION_KEY "); + if(flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) + fprintf(handle, "NTLMFLAG_NEGOTIATE_TARGET_INFO "); + if(flags & (1<<24)) + fprintf(handle, "NTLMFLAG_UNKNOWN_24 "); + if(flags & (1<<25)) + fprintf(handle, "NTLMFLAG_UNKNOWN_25 "); + if(flags & (1<<26)) + fprintf(handle, "NTLMFLAG_UNKNOWN_26 "); + if(flags & (1<<27)) + fprintf(handle, "NTLMFLAG_UNKNOWN_27 "); + if(flags & (1<<28)) + fprintf(handle, "NTLMFLAG_UNKNOWN_28 "); + if(flags & NTLMFLAG_NEGOTIATE_128) + fprintf(handle, "NTLMFLAG_NEGOTIATE_128 "); + if(flags & NTLMFLAG_NEGOTIATE_KEY_EXCHANGE) + fprintf(handle, "NTLMFLAG_NEGOTIATE_KEY_EXCHANGE "); + if(flags & NTLMFLAG_NEGOTIATE_56) + fprintf(handle, "NTLMFLAG_NEGOTIATE_56 "); +} + +static void print_hex(FILE *handle, const char *buf, size_t len) +{ + const char *p = buf; + fprintf(stderr, "0x"); + while(len-- > 0) + fprintf(stderr, "%02.2x", (unsigned int)*p++); +} +#else +# define DEBUG_OUT(x) +#endif + +/* + (*) = A "security buffer" is a triplet consisting of two shorts and one + long: + + 1. a 'short' containing the length of the buffer in bytes + 2. a 'short' containing the allocated space for the buffer in bytes + 3. a 'long' containing the offset to the start of the buffer from the + beginning of the NTLM message, in bytes. +*/ + + +CURLntlm Curl_input_ntlm(struct connectdata *conn, + bool proxy, /* if proxy or not */ + const char *header) /* rest of the www-authenticate: + header */ +{ + /* point to the correct struct with this */ + struct ntlmdata *ntlm; +#ifndef USE_WINDOWS_SSPI + static const char type2_marker[] = { 0x02, 0x00, 0x00, 0x00 }; +#endif + +#ifdef USE_NSS + if(CURLE_OK != Curl_nss_force_init(conn->data)) + return CURLNTLM_BAD; +#endif + + ntlm = proxy?&conn->proxyntlm:&conn->ntlm; + + /* skip initial whitespaces */ + while(*header && ISSPACE(*header)) + header++; + + if(checkprefix("NTLM", header)) { + header += strlen("NTLM"); + + while(*header && ISSPACE(*header)) + header++; + + if(*header) { + /* We got a type-2 message here: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x02000000) + 12 Target Name security buffer(*) + 20 Flags long + 24 Challenge 8 bytes + (32) Context (optional) 8 bytes (two consecutive longs) + (40) Target Information (optional) security buffer(*) + 32 (48) start of data block + */ + size_t size; + unsigned char *buffer; + size = Curl_base64_decode(header, &buffer); + if(!buffer) + return CURLNTLM_BAD; + + ntlm->state = NTLMSTATE_TYPE2; /* we got a type-2 */ + +#ifdef USE_WINDOWS_SSPI + ntlm->type_2 = malloc(size+1); + if(ntlm->type_2 == NULL) { + free(buffer); + return CURLE_OUT_OF_MEMORY; + } + ntlm->n_type_2 = size; + memcpy(ntlm->type_2, buffer, size); +#else + ntlm->flags = 0; + + if((size < 32) || + (memcmp(buffer, NTLMSSP_SIGNATURE, 8) != 0) || + (memcmp(buffer+8, type2_marker, sizeof(type2_marker)) != 0)) { + /* This was not a good enough type-2 message */ + free(buffer); + return CURLNTLM_BAD; + } + + ntlm->flags = readint_le(&buffer[20]); + memcpy(ntlm->nonce, &buffer[24], 8); + + DEBUG_OUT({ + fprintf(stderr, "**** TYPE2 header flags=0x%08.8lx ", ntlm->flags); + print_flags(stderr, ntlm->flags); + fprintf(stderr, "\n nonce="); + print_hex(stderr, (char *)ntlm->nonce, 8); + fprintf(stderr, "\n****\n"); + fprintf(stderr, "**** Header %s\n ", header); + }); +#endif + free(buffer); + } + else { + if(ntlm->state >= NTLMSTATE_TYPE1) + return CURLNTLM_BAD; + + ntlm->state = NTLMSTATE_TYPE1; /* we should sent away a type-1 */ + } + } + return CURLNTLM_FINE; +} + +#ifndef USE_WINDOWS_SSPI + +#ifdef USE_SSLEAY +/* + * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * key schedule ks is also set. + */ +static void setup_des_key(const unsigned char *key_56, + DES_key_schedule DESKEYARG(ks)) +{ + DES_cblock key; + + key[0] = key_56[0]; + key[1] = (unsigned char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (unsigned char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (unsigned char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (unsigned char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (unsigned char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (unsigned char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (unsigned char) ((key_56[6] << 1) & 0xFF); + + DES_set_odd_parity(&key); + DES_set_key(&key, ks); +} + +#else /* defined(USE_SSLEAY) */ + +/* + * Turns a 56 bit key into the 64 bit, odd parity key. Used by GnuTLS and NSS. + */ +static void extend_key_56_to_64(const unsigned char *key_56, char *key) +{ + key[0] = key_56[0]; + key[1] = (unsigned char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (unsigned char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (unsigned char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (unsigned char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (unsigned char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (unsigned char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (unsigned char) ((key_56[6] << 1) & 0xFF); +} + +#if defined(USE_GNUTLS) + +/* + * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. + */ +static void setup_des_key(const unsigned char *key_56, + gcry_cipher_hd_t *des) +{ + char key[8]; + extend_key_56_to_64(key_56, key); + gcry_cipher_setkey(*des, key, 8); +} + +#elif defined(USE_NSS) + +/* + * Expands a 56 bit key KEY_56 to 64 bit and encrypts 64 bit of data, using + * the expanded key. The caller is responsible for giving 64 bit of valid + * data is IN and (at least) 64 bit large buffer as OUT. + */ +static bool encrypt_des(const unsigned char *in, unsigned char *out, + const unsigned char *key_56) +{ + const CK_MECHANISM_TYPE mech = CKM_DES_ECB; /* DES cipher in ECB mode */ + PK11SlotInfo *slot = NULL; + char key[8]; /* expanded 64 bit key */ + SECItem key_item; + PK11SymKey *symkey = NULL; + SECItem *param = NULL; + PK11Context *ctx = NULL; + int out_len; /* not used, required by NSS */ + bool rv = FALSE; + + /* use internal slot for DES encryption (requires NSS to be initialized) */ + slot = PK11_GetInternalKeySlot(); + if(!slot) + return FALSE; + + /* expand the 56 bit key to 64 bit and wrap by NSS */ + extend_key_56_to_64(key_56, key); + key_item.data = (unsigned char *)key; + key_item.len = /* hard-wired */ 8; + symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_ENCRYPT, + &key_item, NULL); + if(!symkey) + goto fail; + + /* create DES encryption context */ + param = PK11_ParamFromIV(mech, /* no IV in ECB mode */ NULL); + if(!param) + goto fail; + ctx = PK11_CreateContextBySymKey(mech, CKA_ENCRYPT, symkey, param); + if(!ctx) + goto fail; + + /* perform the encryption */ + if(SECSuccess == PK11_CipherOp(ctx, out, &out_len, /* outbuflen */ 8, + (unsigned char *)in, /* inbuflen */ 8) + && SECSuccess == PK11_Finalize(ctx)) + rv = /* all OK */ TRUE; + +fail: + /* cleanup */ + if(ctx) + PK11_DestroyContext(ctx, PR_TRUE); + if(symkey) + PK11_FreeSymKey(symkey); + if(param) + SECITEM_FreeItem(param, PR_TRUE); + PK11_FreeSlot(slot); + return rv; +} + +#endif /* defined(USE_NSS) */ + +#endif /* defined(USE_SSLEAY) */ + + /* + * takes a 21 byte array and treats it as 3 56-bit DES keys. The + * 8 byte plaintext is encrypted with each key and the resulting 24 + * bytes are stored in the results array. + */ +static void lm_resp(const unsigned char *keys, + const unsigned char *plaintext, + unsigned char *results) +{ +#ifdef USE_SSLEAY + DES_key_schedule ks; + + setup_des_key(keys, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys+7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+8), + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(keys+14, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+16), + DESKEY(ks), DES_ENCRYPT); +#elif defined(USE_GNUTLS) + gcry_cipher_hd_t des; + + gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); + setup_des_key(keys, &des); + gcry_cipher_encrypt(des, results, 8, plaintext, 8); + gcry_cipher_close(des); + + gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); + setup_des_key(keys+7, &des); + gcry_cipher_encrypt(des, results+8, 8, plaintext, 8); + gcry_cipher_close(des); + + gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); + setup_des_key(keys+14, &des); + gcry_cipher_encrypt(des, results+16, 8, plaintext, 8); + gcry_cipher_close(des); +#elif defined(USE_NSS) + encrypt_des(plaintext, results, keys); + encrypt_des(plaintext, results+8, keys+7); + encrypt_des(plaintext, results+16, keys+14); +#endif +} + + +/* + * Set up lanmanager hashed password + */ +static void mk_lm_hash(struct SessionHandle *data, + const char *password, + unsigned char *lmbuffer /* 21 bytes */) +{ + unsigned char pw[14]; + static const unsigned char magic[] = { + 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 /* i.e. KGS!@#$% */ + }; + size_t len = CURLMIN(strlen(password), 14); + + Curl_strntoupper((char *)pw, password, len); + memset(&pw[len], 0, 14-len); + +#ifdef CURL_DOES_CONVERSIONS + /* + * The LanManager hashed password needs to be created using the + * password in the network encoding not the host encoding. + */ + if(data) + Curl_convert_to_network(data, (char *)pw, 14); +#else + (void)data; +#endif + + { + /* Create LanManager hashed password. */ + +#ifdef USE_SSLEAY + DES_key_schedule ks; + + setup_des_key(pw, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)lmbuffer, + DESKEY(ks), DES_ENCRYPT); + + setup_des_key(pw+7, DESKEY(ks)); + DES_ecb_encrypt((DES_cblock *)magic, (DES_cblock *)(lmbuffer+8), + DESKEY(ks), DES_ENCRYPT); +#elif defined(USE_GNUTLS) + gcry_cipher_hd_t des; + + gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); + setup_des_key(pw, &des); + gcry_cipher_encrypt(des, lmbuffer, 8, magic, 8); + gcry_cipher_close(des); + + gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); + setup_des_key(pw+7, &des); + gcry_cipher_encrypt(des, lmbuffer+8, 8, magic, 8); + gcry_cipher_close(des); +#elif defined(USE_NSS) + encrypt_des(magic, lmbuffer, pw); + encrypt_des(magic, lmbuffer+8, pw+7); +#endif + + memset(lmbuffer + 16, 0, 21 - 16); + } +} + +#if USE_NTRESPONSES +static void ascii_to_unicode_le(unsigned char *dest, const char *src, + size_t srclen) +{ + size_t i; + for (i=0; i<srclen; i++) { + dest[2*i] = (unsigned char)src[i]; + dest[2*i+1] = '\0'; + } +} + +/* + * Set up nt hashed passwords + */ +static CURLcode mk_nt_hash(struct SessionHandle *data, + const char *password, + unsigned char *ntbuffer /* 21 bytes */) +{ + size_t len = strlen(password); + unsigned char *pw = malloc(len*2); + if(!pw) + return CURLE_OUT_OF_MEMORY; + + ascii_to_unicode_le(pw, password, len); + +#ifdef CURL_DOES_CONVERSIONS + /* + * The NT hashed password needs to be created using the + * password in the network encoding not the host encoding. + */ + if(data) + Curl_convert_to_network(data, (char *)pw, len*2); +#else + (void)data; +#endif + + { + /* Create NT hashed password. */ +#ifdef USE_SSLEAY + MD4_CTX MD4pw; + MD4_Init(&MD4pw); + MD4_Update(&MD4pw, pw, 2*len); + MD4_Final(ntbuffer, &MD4pw); +#elif defined(USE_GNUTLS) + gcry_md_hd_t MD4pw; + gcry_md_open(&MD4pw, GCRY_MD_MD4, 0); + gcry_md_write(MD4pw, pw, 2*len); + memcpy (ntbuffer, gcry_md_read (MD4pw, 0), MD4_DIGEST_LENGTH); + gcry_md_close(MD4pw); +#elif defined(USE_NSS) + Curl_md4it(ntbuffer, pw, 2*len); +#endif + + memset(ntbuffer + 16, 0, 21 - 16); + } + + free(pw); + return CURLE_OK; +} +#endif + + +#endif + +#ifdef USE_WINDOWS_SSPI + +static void +ntlm_sspi_cleanup(struct ntlmdata *ntlm) +{ + if(ntlm->type_2) { + free(ntlm->type_2); + ntlm->type_2 = NULL; + } + if(ntlm->has_handles) { + s_pSecFn->DeleteSecurityContext(&ntlm->c_handle); + s_pSecFn->FreeCredentialsHandle(&ntlm->handle); + ntlm->has_handles = 0; + } + if(ntlm->p_identity) { + if(ntlm->identity.User) free(ntlm->identity.User); + if(ntlm->identity.Password) free(ntlm->identity.Password); + if(ntlm->identity.Domain) free(ntlm->identity.Domain); + ntlm->p_identity = NULL; + } +} + +#endif + +#define SHORTPAIR(x) ((x) & 0xff), (((x) >> 8) & 0xff) +#define LONGQUARTET(x) ((x) & 0xff), (((x) >> 8)&0xff), \ + (((x) >>16)&0xff), (((x)>>24) & 0xff) + +#define HOSTNAME_MAX 1024 + +/* this is for creating ntlm header output */ +CURLcode Curl_output_ntlm(struct connectdata *conn, + bool proxy) +{ + const char *domain=""; /* empty */ + char host [HOSTNAME_MAX+ 1] = ""; /* empty */ +#ifndef USE_WINDOWS_SSPI + size_t domlen = strlen(domain); + size_t hostlen = strlen(host); + size_t hostoff; /* host name offset */ + size_t domoff; /* domain name offset */ +#endif + size_t size; + char *base64=NULL; + unsigned char ntlmbuf[1024]; /* enough, unless the user+host+domain is very + long */ + + /* point to the address of the pointer that holds the string to sent to the + server, which is for a plain host or for a HTTP proxy */ + char **allocuserpwd; + + /* point to the name and password for this */ + const char *userp; + const char *passwdp; + /* point to the correct struct with this */ + struct ntlmdata *ntlm; + struct auth *authp; + + DEBUGASSERT(conn); + DEBUGASSERT(conn->data); + +#ifdef USE_NSS + if(CURLE_OK != Curl_nss_force_init(conn->data)) + return CURLE_OUT_OF_MEMORY; +#endif + + if(proxy) { + allocuserpwd = &conn->allocptr.proxyuserpwd; + userp = conn->proxyuser; + passwdp = conn->proxypasswd; + ntlm = &conn->proxyntlm; + authp = &conn->data->state.authproxy; + } + else { + allocuserpwd = &conn->allocptr.userpwd; + userp = conn->user; + passwdp = conn->passwd; + ntlm = &conn->ntlm; + authp = &conn->data->state.authhost; + } + authp->done = FALSE; + + /* not set means empty */ + if(!userp) + userp=""; + + if(!passwdp) + passwdp=""; + +#ifdef USE_WINDOWS_SSPI + if (s_hSecDll == NULL) { + /* not thread safe and leaks - use curl_global_init() to avoid */ + CURLcode err = Curl_sspi_global_init(); + if (s_hSecDll == NULL) + return err; + } +#endif + + switch(ntlm->state) { + case NTLMSTATE_TYPE1: + default: /* for the weird cases we (re)start here */ +#ifdef USE_WINDOWS_SSPI + { + SecBuffer buf; + SecBufferDesc desc; + SECURITY_STATUS status; + ULONG attrs; + const char *user; + int domlen; + TimeStamp tsDummy; /* For Windows 9x compatibility of SPPI calls */ + + ntlm_sspi_cleanup(ntlm); + + user = strchr(userp, '\\'); + if(!user) + user = strchr(userp, '/'); + + if(user) { + domain = userp; + domlen = user - userp; + user++; + } + else { + user = userp; + domain = ""; + domlen = 0; + } + + if(user && *user) { + /* note: initialize all of this before doing the mallocs so that + * it can be cleaned up later without leaking memory. + */ + ntlm->p_identity = &ntlm->identity; + memset(ntlm->p_identity, 0, sizeof(*ntlm->p_identity)); + if((ntlm->identity.User = (unsigned char *)strdup(user)) == NULL) + return CURLE_OUT_OF_MEMORY; + ntlm->identity.UserLength = strlen(user); + if((ntlm->identity.Password = (unsigned char *)strdup(passwdp)) == NULL) + return CURLE_OUT_OF_MEMORY; + ntlm->identity.PasswordLength = strlen(passwdp); + if((ntlm->identity.Domain = malloc(domlen+1)) == NULL) + return CURLE_OUT_OF_MEMORY; + strncpy((char *)ntlm->identity.Domain, domain, domlen); + ntlm->identity.Domain[domlen] = '\0'; + ntlm->identity.DomainLength = domlen; + ntlm->identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + } + else { + ntlm->p_identity = NULL; + } + + if(s_pSecFn->AcquireCredentialsHandleA( + NULL, (char *)"NTLM", SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, + NULL, NULL, &ntlm->handle, &tsDummy + ) != SEC_E_OK) { + return CURLE_OUT_OF_MEMORY; + } + + desc.ulVersion = SECBUFFER_VERSION; + desc.cBuffers = 1; + desc.pBuffers = &buf; + buf.cbBuffer = sizeof(ntlmbuf); + buf.BufferType = SECBUFFER_TOKEN; + buf.pvBuffer = ntlmbuf; + + status = s_pSecFn->InitializeSecurityContextA(&ntlm->handle, NULL, + (char *) host, + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONNECTION, + 0, SECURITY_NETWORK_DREP, + NULL, 0, + &ntlm->c_handle, &desc, + &attrs, &tsDummy); + + if(status == SEC_I_COMPLETE_AND_CONTINUE || + status == SEC_I_CONTINUE_NEEDED) { + s_pSecFn->CompleteAuthToken(&ntlm->c_handle, &desc); + } + else if(status != SEC_E_OK) { + s_pSecFn->FreeCredentialsHandle(&ntlm->handle); + return CURLE_RECV_ERROR; + } + + ntlm->has_handles = 1; + size = buf.cbBuffer; + } +#else + hostoff = 0; + domoff = hostoff + hostlen; /* This is 0: remember that host and domain + are empty */ + + /* Create and send a type-1 message: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x01000000) + 12 Flags long + 16 Supplied Domain security buffer(*) + 24 Supplied Workstation security buffer(*) + 32 start of data block + + */ +#if USE_NTLM2SESSION +#define NTLM2FLAG NTLMFLAG_NEGOTIATE_NTLM2_KEY +#else +#define NTLM2FLAG 0 +#endif + snprintf((char *)ntlmbuf, sizeof(ntlmbuf), NTLMSSP_SIGNATURE "%c" + "\x01%c%c%c" /* 32-bit type = 1 */ + "%c%c%c%c" /* 32-bit NTLM flag field */ + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host name offset */ + "%c%c" /* 2 zeroes */ + "%s" /* host name */ + "%s", /* domain string */ + 0, /* trailing zero */ + 0,0,0, /* part of type-1 long */ + + LONGQUARTET( + NTLMFLAG_NEGOTIATE_OEM| + NTLMFLAG_REQUEST_TARGET| + NTLMFLAG_NEGOTIATE_NTLM_KEY| + NTLM2FLAG| + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN + ), + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0,0, + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0,0, + host /* this is empty */, domain /* this is empty */); + + /* initial packet length */ + size = 32 + hostlen + domlen; +#endif + + DEBUG_OUT({ + fprintf(stderr, "**** TYPE1 header flags=0x%02.2x%02.2x%02.2x%02.2x 0x%08.8x ", + LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM| + NTLMFLAG_REQUEST_TARGET| + NTLMFLAG_NEGOTIATE_NTLM_KEY| + NTLM2FLAG| + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN), + NTLMFLAG_NEGOTIATE_OEM| + NTLMFLAG_REQUEST_TARGET| + NTLMFLAG_NEGOTIATE_NTLM_KEY| + NTLM2FLAG| + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); + print_flags(stderr, + NTLMFLAG_NEGOTIATE_OEM| + NTLMFLAG_REQUEST_TARGET| + NTLMFLAG_NEGOTIATE_NTLM_KEY| + NTLM2FLAG| + NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); + fprintf(stderr, "\n****\n"); + }); + + /* now size is the size of the base64 encoded package size */ + size = Curl_base64_encode(NULL, (char *)ntlmbuf, size, &base64); + + if(size >0 ) { + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy?"Proxy-":"", + base64); + DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); + free(base64); + } + else + return CURLE_OUT_OF_MEMORY; /* FIX TODO */ + + break; + + case NTLMSTATE_TYPE2: + /* We received the type-2 message already, create a type-3 message: + + Index Description Content + 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" + (0x4e544c4d53535000) + 8 NTLM Message Type long (0x03000000) + 12 LM/LMv2 Response security buffer(*) + 20 NTLM/NTLMv2 Response security buffer(*) + 28 Domain Name security buffer(*) + 36 User Name security buffer(*) + 44 Workstation Name security buffer(*) + (52) Session Key (optional) security buffer(*) + (60) Flags (optional) long + 52 (64) start of data block + + */ + + { +#ifdef USE_WINDOWS_SSPI + SecBuffer type_2, type_3; + SecBufferDesc type_2_desc, type_3_desc; + SECURITY_STATUS status; + ULONG attrs; + TimeStamp tsDummy; /* For Windows 9x compatibility of SPPI calls */ + + type_2_desc.ulVersion = type_3_desc.ulVersion = SECBUFFER_VERSION; + type_2_desc.cBuffers = type_3_desc.cBuffers = 1; + type_2_desc.pBuffers = &type_2; + type_3_desc.pBuffers = &type_3; + + type_2.BufferType = SECBUFFER_TOKEN; + type_2.pvBuffer = ntlm->type_2; + type_2.cbBuffer = ntlm->n_type_2; + type_3.BufferType = SECBUFFER_TOKEN; + type_3.pvBuffer = ntlmbuf; + type_3.cbBuffer = sizeof(ntlmbuf); + + status = s_pSecFn->InitializeSecurityContextA(&ntlm->handle, &ntlm->c_handle, + (char *) host, + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONNECTION, + 0, SECURITY_NETWORK_DREP, &type_2_desc, + 0, &ntlm->c_handle, &type_3_desc, + &attrs, &tsDummy); + + if(status != SEC_E_OK) + return CURLE_RECV_ERROR; + + size = type_3.cbBuffer; + + ntlm_sspi_cleanup(ntlm); + +#else + int lmrespoff; + unsigned char lmresp[24]; /* fixed-size */ +#if USE_NTRESPONSES + int ntrespoff; + unsigned char ntresp[24]; /* fixed-size */ +#endif + size_t useroff; + const char *user; + size_t userlen; + + user = strchr(userp, '\\'); + if(!user) + user = strchr(userp, '/'); + + if(user) { + domain = userp; + domlen = (user - domain); + user++; + } + else + user = userp; + userlen = strlen(user); + + if(Curl_gethostname(host, HOSTNAME_MAX)) { + infof(conn->data, "gethostname() failed, continuing without!"); + hostlen = 0; + } + else { + /* If the workstation if configured with a full DNS name (i.e. + * workstation.somewhere.net) gethostname() returns the fully qualified + * name, which NTLM doesn't like. + */ + char *dot = strchr(host, '.'); + if(dot) + *dot = '\0'; + hostlen = strlen(host); + } + +#if USE_NTLM2SESSION + /* We don't support NTLM2 if we don't have USE_NTRESPONSES */ + if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { + unsigned char ntbuffer[0x18]; + unsigned char tmp[0x18]; + unsigned char md5sum[MD5_DIGEST_LENGTH]; + unsigned char entropy[8]; + + /* Need to create 8 bytes random data */ +#ifdef USE_SSLEAY + MD5_CTX MD5pw; + Curl_ossl_seed(conn->data); /* Initiate the seed if not already done */ + RAND_bytes(entropy,8); +#elif defined(USE_GNUTLS) + gcry_md_hd_t MD5pw; + Curl_gtls_seed(conn->data); /* Initiate the seed if not already done */ + gcry_randomize(entropy, 8, GCRY_STRONG_RANDOM); +#elif defined(USE_NSS) + PK11Context *MD5pw; + unsigned int outlen; + Curl_nss_seed(conn->data); /* Initiate the seed if not already done */ + PK11_GenerateRandom(entropy, 8); +#endif + + /* 8 bytes random data as challenge in lmresp */ + memcpy(lmresp,entropy,8); + /* Pad with zeros */ + memset(lmresp+8,0,0x10); + + /* Fill tmp with challenge(nonce?) + entropy */ + memcpy(tmp,&ntlm->nonce[0],8); + memcpy(tmp+8,entropy,8); + +#ifdef USE_SSLEAY + MD5_Init(&MD5pw); + MD5_Update(&MD5pw, tmp, 16); + MD5_Final(md5sum, &MD5pw); +#elif defined(USE_GNUTLS) + gcry_md_open(&MD5pw, GCRY_MD_MD5, 0); + gcry_md_write(MD5pw, tmp, MD5_DIGEST_LENGTH); + memcpy(md5sum, gcry_md_read (MD5pw, 0), MD5_DIGEST_LENGTH); + gcry_md_close(MD5pw); +#elif defined(USE_NSS) + MD5pw = PK11_CreateDigestContext(SEC_OID_MD5); + PK11_DigestOp(MD5pw, tmp, 16); + PK11_DigestFinal(MD5pw, md5sum, &outlen, MD5_DIGEST_LENGTH); + PK11_DestroyContext(MD5pw, PR_TRUE); +#endif + + /* We shall only use the first 8 bytes of md5sum, + but the des code in lm_resp only encrypt the first 8 bytes */ + if(mk_nt_hash(conn->data, passwdp, ntbuffer) == CURLE_OUT_OF_MEMORY) + return CURLE_OUT_OF_MEMORY; + lm_resp(ntbuffer, md5sum, ntresp); + + /* End of NTLM2 Session code */ + } + else +#endif + { + +#if USE_NTRESPONSES + unsigned char ntbuffer[0x18]; +#endif + unsigned char lmbuffer[0x18]; + +#if USE_NTRESPONSES + if(mk_nt_hash(conn->data, passwdp, ntbuffer) == CURLE_OUT_OF_MEMORY) + return CURLE_OUT_OF_MEMORY; + lm_resp(ntbuffer, &ntlm->nonce[0], ntresp); +#endif + + mk_lm_hash(conn->data, passwdp, lmbuffer); + lm_resp(lmbuffer, &ntlm->nonce[0], lmresp); + /* A safer but less compatible alternative is: + * lm_resp(ntbuffer, &ntlm->nonce[0], lmresp); + * See http://davenport.sourceforge.net/ntlm.html#ntlmVersion2 */ + } + + lmrespoff = 64; /* size of the message header */ +#if USE_NTRESPONSES + ntrespoff = lmrespoff + 0x18; + domoff = ntrespoff + 0x18; +#else + domoff = lmrespoff + 0x18; +#endif + useroff = domoff + domlen; + hostoff = useroff + userlen; + + /* + * In the case the server sets the flag NTLMFLAG_NEGOTIATE_UNICODE, we + * need to filter it off because libcurl doesn't UNICODE encode the + * strings it packs into the NTLM authenticate packet. + */ + ntlm->flags &= ~NTLMFLAG_NEGOTIATE_UNICODE; + + /* Create the big type-3 message binary blob */ + size = snprintf((char *)ntlmbuf, sizeof(ntlmbuf), + NTLMSSP_SIGNATURE "%c" + "\x03%c%c%c" /* type-3, 32 bits */ + + "%c%c" /* LanManager length */ + "%c%c" /* LanManager allocated space */ + "%c%c" /* LanManager offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* NT-response length */ + "%c%c" /* NT-response allocated space */ + "%c%c" /* NT-response offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* domain length */ + "%c%c" /* domain allocated space */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* user length */ + "%c%c" /* user allocated space */ + "%c%c" /* user offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* host length */ + "%c%c" /* host allocated space */ + "%c%c" /* host offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* session key length (unknown purpose) */ + "%c%c" /* session key allocated space (unknown purpose) */ + "%c%c" /* session key offset (unknown purpose) */ + "%c%c" /* 2 zeroes */ + + "%c%c%c%c" /* flags */ + + /* domain string */ + /* user string */ + /* host string */ + /* LanManager response */ + /* NT response */ + , + 0, /* zero termination */ + 0,0,0, /* type-3 long, the 24 upper bits */ + + SHORTPAIR(0x18), /* LanManager response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(lmrespoff), + 0x0, 0x0, + +#if USE_NTRESPONSES + SHORTPAIR(0x18), /* NT-response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(ntrespoff), + 0x0, 0x0, +#else + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, +#endif + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + 0x0, 0x0, + + SHORTPAIR(userlen), + SHORTPAIR(userlen), + SHORTPAIR(useroff), + 0x0, 0x0, + + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0x0, 0x0, + + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + 0x0, 0x0, + + LONGQUARTET(ntlm->flags)); + DEBUGASSERT(size==64); + + DEBUGASSERT(size == (size_t)lmrespoff); + /* We append the binary hashes */ + if(size < (sizeof(ntlmbuf) - 0x18)) { + memcpy(&ntlmbuf[size], lmresp, 0x18); + size += 0x18; + } + + DEBUG_OUT({ + fprintf(stderr, "**** TYPE3 header lmresp="); + print_hex(stderr, (char *)&ntlmbuf[lmrespoff], 0x18); + }); + +#if USE_NTRESPONSES + if(size < (sizeof(ntlmbuf) - 0x18)) { + DEBUGASSERT(size == (size_t)ntrespoff); + memcpy(&ntlmbuf[size], ntresp, 0x18); + size += 0x18; + } + + DEBUG_OUT({ + fprintf(stderr, "\n ntresp="); + print_hex(stderr, (char *)&ntlmbuf[ntrespoff], 0x18); + }); + +#endif + + DEBUG_OUT({ + fprintf(stderr, "\n flags=0x%02.2x%02.2x%02.2x%02.2x 0x%08.8x ", + LONGQUARTET(ntlm->flags), ntlm->flags); + print_flags(stderr, ntlm->flags); + fprintf(stderr, "\n****\n"); + }); + + + /* Make sure that the domain, user and host strings fit in the target + buffer before we copy them there. */ + if(size + userlen + domlen + hostlen >= sizeof(ntlmbuf)) { + failf(conn->data, "user + domain + host name too big"); + return CURLE_OUT_OF_MEMORY; + } + + DEBUGASSERT(size == domoff); + memcpy(&ntlmbuf[size], domain, domlen); + size += domlen; + + DEBUGASSERT(size == useroff); + memcpy(&ntlmbuf[size], user, userlen); + size += userlen; + + DEBUGASSERT(size == hostoff); + memcpy(&ntlmbuf[size], host, hostlen); + size += hostlen; + +#ifdef CURL_DOES_CONVERSIONS + /* convert domain, user, and host to ASCII but leave the rest as-is */ + if(CURLE_OK != Curl_convert_to_network(conn->data, + (char *)&ntlmbuf[domoff], + size-domoff)) { + return CURLE_CONV_FAILED; + } +#endif /* CURL_DOES_CONVERSIONS */ + +#endif + + /* convert the binary blob into base64 */ + size = Curl_base64_encode(NULL, (char *)ntlmbuf, size, &base64); + + if(size >0 ) { + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", + proxy?"Proxy-":"", + base64); + DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); + free(base64); + } + else + return CURLE_OUT_OF_MEMORY; /* FIX TODO */ + + ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ + authp->done = TRUE; + } + break; + + case NTLMSTATE_TYPE3: + /* connection is already authenticated, + * don't send a header in future requests */ + if(*allocuserpwd) { + free(*allocuserpwd); + *allocuserpwd=NULL; + } + authp->done = TRUE; + break; + } + + return CURLE_OK; +} + + +void +Curl_ntlm_cleanup(struct connectdata *conn) +{ +#ifdef USE_WINDOWS_SSPI + ntlm_sspi_cleanup(&conn->ntlm); + ntlm_sspi_cleanup(&conn->proxyntlm); +#else + (void)conn; +#endif +} + + +#endif /* USE_NTLM */ +#endif /* !CURL_DISABLE_HTTP */ |