diff --git a/lib/Makefile.am b/lib/Makefile.am index 64cfaa74f..ebec015d8 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -68,7 +68,8 @@ strequal.h easy.c security.h security.c krb4.c krb4.h memdebug.c \ memdebug.h inet_ntoa_r.h http_chunks.c http_chunks.h strtok.c strtok.h \ connect.c connect.h llist.c llist.h hash.c hash.h multi.c \ content_encoding.c content_encoding.h share.c share.h http_digest.c \ -md5.c md5.h http_digest.h http_negotiate.c http_negotiate.h +md5.c md5.h http_digest.h http_negotiate.c http_negotiate.h \ +http_ntlm.c http_ntlm.h noinst_HEADERS = setup.h transfer.h diff --git a/lib/base64.c b/lib/base64.c index 0f461eabd..ee533189f 100644 --- a/lib/base64.c +++ b/lib/base64.c @@ -232,13 +232,17 @@ int main(int argc, char **argv, char **envp) int base64Len; unsigned char *data; int dataLen; + int i; base64 = (char *)suck(&base64Len); data = (unsigned char *)malloc(base64Len * 3/4 + 8); dataLen = Curl_base64_decode(base64, data); fprintf(stderr, "%d\n", dataLen); - fwrite(data,1,dataLen,stdout); + + for(i=0; i < dataLen; i++) + printf("%02x ", data[i]); + puts(""); free(base64); free(data); return 0; diff --git a/lib/http.c b/lib/http.c index e5d2b49d3..b06de06a8 100644 --- a/lib/http.c +++ b/lib/http.c @@ -90,6 +90,7 @@ #include "strequal.h" #include "ssluse.h" #include "http_digest.h" +#include "http_ntlm.h" #define _MPRINTF_REPLACE /* use our functions only */ #include @@ -696,7 +697,12 @@ CURLcode Curl_http(struct connectdata *conn) return result; } else #endif - if(data->state.digest.nonce) { + if(data->state.ntlm.state) { + result = Curl_output_ntlm(conn); + if(result) + return result; + } + else if(data->state.digest.nonce) { result = Curl_output_digest(conn, (unsigned char *)request, (unsigned char *)ppath); diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c new file mode 100644 index 000000000..841342526 --- /dev/null +++ b/lib/http_ntlm.c @@ -0,0 +1,501 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2003, Daniel Stenberg, , 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. + * + * $Id$ + ***************************************************************************/ +#include "setup.h" + +/* All NTLM details here: http://www.innovation.ch/java/ntlm.html */ + +#ifndef CURL_DISABLE_HTTP +#ifdef USE_SSLEAY +/* We need OpenSSL for the crypto lib to provide us with MD4 and DES */ + +/* -- WIN32 approved -- */ +#include +#include +#include +#include +#include + +#include "urldata.h" +#include "sendf.h" +#include "strequal.h" +#include "base64.h" +#include "http_ntlm.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include +#include + +/* The last #include file should be: */ +#ifdef MALLOCDEBUG +#include "memdebug.h" +#endif + +/* + The one and only master resource for NTLM "hacking": + + ====> http://www.innovation.ch/java/ntlm.html <==== + + Brought to the world by Ronald Tschalär. +*/ + +/* Test example header: + +WWW-Authenticate: NTLM + +*/ + +CURLntlm Curl_input_ntlm(struct connectdata *conn, + char *header) /* rest of the www-authenticate: + header */ +{ + struct SessionHandle *data=conn->data; + + /* skip initial whitespaces */ + while(*header && isspace((int)*header)) + header++; + + if(checkprefix("NTLM", header)) { + char buffer[256]; + header += strlen("NTLM"); + + while(*header && isspace((int)*header)) + header++; + + if(*header) { + /* we got a type-2 message here */ + + /* My test-IE session reveived this type-2: + + TlRMTVNTUAACAAAAAgACADAAAAAGgoEAc51AYVDgyNcAAAAAAAAAAG4AbgA\ + yAAAAQ0MCAAQAQwBDAAEAEgBFAEwASQBTAEEAQgBFAFQASAAEABgAYwBjAC4\ + AaQBjAGUAZABlAHYALgBuAHUAAwAsAGUAbABpAHMAYQBiAGUAdABoAC4AYwB\ + jAC4AaQBjAGUAZABlAHYALgBuAHUAAAAAAA== + + which translates to this: + + 4e 54 4c 4d 53 53 50 00 02 00 00 00 02 00 02 00 30 00 00 00 06 82 81 00 + 73 9d 40 61 50 e0 c8 d7 00 00 00 00 00 00 00 00 6e 00 6e 00 32 00 00 00 + 43 43 02 00 04 00 43 00 43 00 01 00 12 00 45 00 4c 00 49 00 53 00 41 00 + 42 00 45 00 54 00 48 00 04 00 18 00 63 00 63 00 2e 00 69 00 63 00 65 00 + 64 00 65 00 76 00 2e 00 6e 00 75 00 03 00 2c 00 65 00 6c 00 69 00 73 00 + 61 00 62 00 65 00 74 00 68 00 2e 00 63 00 63 00 2e 00 69 00 63 00 65 00 + 64 00 65 00 76 00 2e 00 6e 00 75 00 00 00 00 00 + + */ + + int size = Curl_base64_decode(header, buffer); + + data->state.ntlm.state = NTLMSTATE_TYPE2; /* we got a type-2 */ + + if(size == 48) + /* the nonce of interest is index [24 .. 31], 8 bytes */ + memcpy(data->state.ntlm.nonce, &buffer[24], 8); + } + else { + data->state.ntlm.state = NTLMSTATE_TYPE1; /* we should sent away a + type-1 */ + } + } + return CURLNTLM_FINE; +} + +/* + * 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(unsigned char *key_56, + DES_key_schedule *ks) +{ + DES_cblock key; + + key[0] = key_56[0]; + key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); + key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); + key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); + key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); + key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); + key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); + key[7] = (key_56[6] << 1) & 0xFF; + + DES_set_odd_parity(&key); + DES_set_key(&key, ks); +} + + /* + * 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 calc_resp(unsigned char *keys, + unsigned char *plaintext, + unsigned char *results) +{ + des_key_schedule ks; + + setup_des_key(keys, &ks); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) results, &ks, + DES_ENCRYPT); + + setup_des_key(keys+7, &ks); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+8), &ks, + DES_ENCRYPT); + + setup_des_key(keys+14, &ks); + DES_ecb_encrypt((DES_cblock*) plaintext, (DES_cblock*) (results+16), &ks, + DES_ENCRYPT); +} + +static void mkhash(char *password, + unsigned char *nonce, /* 8 bytes */ + unsigned char *lmresp, /* must fit 0x18 bytes */ + unsigned char *ntresp) /* must fit 0x18 bytes */ +{ + /* setup LanManager password */ + unsigned char lmbuffer[0x18]; + unsigned char ntbuffer[0x18]; + + unsigned char lm_pw[14]; + int len = strlen(password); + unsigned char magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 }; + int i; + + if (len > 14) + len = 14; + + for (i=0; i> 8) + +/* this is for creating ntlm header output */ +CURLcode Curl_output_ntlm(struct connectdata *conn) +{ + struct SessionHandle *data=conn->data; + const char *domain="HEMMA"; + const char *host="LILLASYSTER"; + int domlen=strlen(domain); + int hostlen = strlen(host); + int hostoff; /* host name offset */ + int domoff; /* domain name offset */ + int size; + char *base64=NULL; + + unsigned char ntlm[256]; /* enough, unless the host/domain is very long */ + if(NTLMSTATE_TYPE1 == data->state.ntlm.state) { + hostoff = 32; + domoff = hostoff + hostlen; + + /* IE used this in the initial dump: + + Authorization: NTLM \ + TlRMTVNTUAABAAAABoIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA\r\n + + This translates into: + + 4e 54 4c 4d 53 53 50 00 01 00 00 00 06 82 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 30 00 00 00 00 00 00 00 30 00 00 00 + + Which isn't following the web spec. This uses 0x8206 instead of 0xb203 + and sends a longer chunk of data than we do! Interestingly, there's no + host or domain either. + + */ + + snprintf((char *)ntlm, sizeof(ntlm), "NTLMSSP%c" + "\x01" /* type 1 */ + "%c%c%c" + "\x03\xb2" + "%c%c" + "%c%c" /* domain length */ + "%c%c" /* domain length */ + "%c%c" /* domain name offset */ + "%c%c" /* host length */ + "%c%c" /* host length */ + "%c%c" /* host name offset */ + "%c%c" + "%s" /* host name */ + "%s", /* domain string */ + 0,0,0,0,0,0, + SHORTPAIR(domlen), + SHORTPAIR(domlen), + SHORTPAIR(domoff), + SHORTPAIR(hostlen), + SHORTPAIR(hostlen), + SHORTPAIR(hostoff), + 0,0, + host, domain); + + /* initial packet length */ + size = 8 + 1 + 3 + 18 + hostlen + domlen; +#if 0 + #define CHUNK "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x06\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00\x00\x00\x00\x00\x30\x00\x00\x00" + memcpy(ntlm, CHUNK, sizeof(CHUNK)-1); + size = sizeof(CHUNK)-1; +#endif + /* now keeper of the base64 encoded package size */ + size = Curl_base64_encode(ntlm, size, &base64); + + if(size >0 ) { + conn->allocptr.userpwd = aprintf("Authorization: NTLM %s\r\n", + base64); + free(base64); + } + else + return CURLE_OUT_OF_MEMORY; /* FIX TODO */ + } + else { + /* We are not in the first state, create a type-3 message */ + + /* + My test-IE session sent this type-3: + + TlRMTVNTUAADAAAAGAAYAEoAAAAAAAAAYgAAAAUABQA0AAAABgAGADk\ + AAAALAAsAPwAAAEhFTU1BZGFuaWVsTElMTEFTWVNURVJPVPJELoebUg\ + 4SvW0ed2QmKu0SjX4qNrI= + + Which translates to: + + 4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 00 4a 00 00 00 00 00 00 00 + 62 00 00 00 05 00 05 00 34 00 00 00 06 00 06 00 39 00 00 00 0b 00 0b 00 + 3f 00 00 00 48 45 4d 4d 41 64 61 6e 69 65 6c 4c 49 4c 4c 41 53 59 53 54 + 45 52 4f 54 f2 44 2e 87 9b 52 0e 12 bd 6d 1e 77 64 26 2a ed 12 8d 7e 2a + 36 b2 + + Note how the domain + username + hostname ARE NOT unicoded in any way. + Domain and hostname are uppercase, while username are case sensitive. + + We sent (badly): + + 4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 00 56 00 00 00 00 00 00 00 + 6e 00 00 00 05 00 05 00 40 00 00 00 06 00 06 00 45 00 00 00 0b 00 0b 00 + 4b 00 00 00 00 00 00 00 6c 00 00 00 01 82 48 45 4d 4d 41 64 61 6e 69 65 + 6c 4c 49 4c 4c 41 53 59 53 54 45 52 86 99 4a 4f 1a 54 93 85 f9 a4 85 d7 + ed 14 17 31 8c a6 4d e9 c1 b1 23 a7 + */ + + int lmrespoff; + int ntrespoff; + int useroff; + unsigned char lmresp[0x18+1]; + unsigned char ntresp[0x18+1]; + + int userlen = strlen(data->state.user); + + mkhash(data->state.passwd, &data->state.ntlm.nonce[0], lmresp, ntresp); + + /* these are going unicode */ + domlen *= 2; + userlen *= 2; + hostlen *= 2; + + domoff = 64; /* always */ + useroff = domoff + domlen; + hostoff = useroff + userlen; + lmrespoff = hostoff + hostlen; + ntrespoff = lmrespoff + 0x18; + + /* Create the big type-3 message binary blob */ + size = snprintf((char *)ntlm, sizeof(ntlm), + "NTLMSSP%c" + "\x03" /* type 3 */ + "%c%c%c" /* 3 zeroes */ + + "%c%c%c%c" /* LanManager length twice */ + "%c%c" /* LanManager offset */ + "%c%c" /* 2 zeroes */ + + "%c%c%c%c" /* NT-response length twice */ + "%c%c" /* NT-response offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* domain length */ + "%c%c" /* domain length */ + "%c%c" /* domain name offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* user length */ + "%c%c" /* user length */ + "%c%c" /* user offset */ + "%c%c" /* 2 zeroes */ + + "%c%c" /* host length */ + "%c%c" /* host length */ + "%c%c" /* host offset */ + "%c%c%c%c%c%c" /* 6 zeroes */ + + "\xff\xff" /* message length */ + "%c%c" /* 2 zeroes */ + + "\x01\x82" /* flags */ + "%c%c" /* 2 zeroes */ + + /* domain string */ + /* user string */ + /* host string */ + /* LanManager response */ + /* NT response */ + , + 0, + 0,0,0, + + SHORTPAIR(0x18), /* LanManager response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(lmrespoff), + 0x0, 0x0, + + SHORTPAIR(0x18), /* NT-response length, twice */ + SHORTPAIR(0x18), + SHORTPAIR(ntrespoff), + 0x0, 0x0, + + 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); + + /* size is now 64 */ +#if 1 + ascii_to_unicode(&ntlm[size], (unsigned char *)domain, TRUE); + size += domlen; + + ascii_to_unicode(&ntlm[size], (unsigned char *)data->state.user, FALSE); + size += userlen; + + ascii_to_unicode(&ntlm[size], (unsigned char *)host, TRUE); + size += hostlen; +#else + strcpy(&ntlm[size], (unsigned char *)domain); + size += domlen; + + strcpy(&ntlm[size], (unsigned char *)data->state.user); + size += userlen; + + strcpy(&ntlm[size], (unsigned char *)host); + size += hostlen; +#endif + + /* we append the binary hashes to the end of the blob */ + if(size < ((int)sizeof(ntlm) - 0x18)) { + memcpy(&ntlm[size], lmresp, 0x18); + size += 0x18; + } +#ifdef USE_NT + if(size < ((int)sizeof(ntlm) - 0x18)) { + memcpy(&ntlm[size+0x18], ntresp, 0x18); + size += 0x18*2; + } +#endif + + ntlm[56] = size & 0xff; + ntlm[57] = size >> 8; + + /* convert the binary blob into base64 */ + size = Curl_base64_encode(ntlm, size, &base64); + + if(size >0 ) { + conn->allocptr.userpwd = aprintf("Authorization: NTLM %s\r\n", + base64); + free(base64); + } + else + return CURLE_OUT_OF_MEMORY; /* FIX TODO */ + + } + + return CURLE_OK; +} +#endif /* USE_SSLEAY */ +#endif /* !CURL_DISABLE_HTTP */ diff --git a/lib/http_ntlm.h b/lib/http_ntlm.h new file mode 100644 index 000000000..30e0b67a1 --- /dev/null +++ b/lib/http_ntlm.h @@ -0,0 +1,67 @@ +#ifndef __HTTP_NTLM_H +#define __HTTP_NTLM_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2003, Daniel Stenberg, , 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. + * + * $Id$ + ***************************************************************************/ + +typedef enum { + CURLNTLM_NONE, /* not a ntlm */ + CURLNTLM_BAD, /* an ntlm, but one we don't like */ + CURLNTLM_FIRST, /* the first 401-reply we got with NTLM */ + CURLNTLM_FINE, /* an ntlm we act on */ + + CURLNTLM_LAST /* last entry in this enum, don't use */ +} CURLntlm; + +/* this is for ntlm header input */ +CURLntlm Curl_input_ntlm(struct connectdata *conn, char *header); + +/* this is for creating ntlm header output */ +CURLcode Curl_output_ntlm(struct connectdata *conn); + +void Curl_ntlm_cleanup(struct SessionHandle *data); + + +/* type-1 octet-stream, sent in the first NTLM-authenticated request + +byte protocol[8]; 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' +byte type; 0x01 +byte zero[3]; +short flags; 0xb203 +byte zero[2]; + +short dom_len; domain string length +short dom_len; domain string length +short dom_off; domain string offset +byte zero[2]; + +short host_len; host string length +short host_len; host string length +short host_off; host string offset (always 0x20) +byte zero[2]; + +byte host[*]; host string (ASCII) +byte dom[*]; domain string (ASCII) + +*/ + +#endif diff --git a/lib/transfer.c b/lib/transfer.c index 2eda08889..7d060c689 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -96,6 +96,7 @@ #include "getinfo.h" #include "ssluse.h" #include "http_digest.h" +#include "http_ntlm.h" #ifdef GSSAPI #include "http_negotiate.h" #endif @@ -736,6 +737,17 @@ CURLcode Curl_readwrite(struct connectdata *conn, conn->newurl = strdup(data->change.url); } #endif + else if(Curl_compareheader(k->p, + "WWW-Authenticate:", "NTLM") && + (401 == k->httpcode) && + data->set.httpntlm /* NTLM authentication is + activated */) { + CURLntlm ntlm; + ntlm = Curl_input_ntlm(conn, + k->p+strlen("WWW-Authenticate:")); + + conn->newurl = strdup(data->change.url); /* clone string */ + } else if(checkprefix("WWW-Authenticate:", k->p) && (401 == k->httpcode) && data->set.httpdigest /* Digest authentication is diff --git a/lib/url.c b/lib/url.c index c328e5ac4..f02eec0cc 100644 --- a/lib/url.c +++ b/lib/url.c @@ -855,17 +855,32 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, ...) /* default */ data->set.httpdigest = FALSE; data->set.httpnegotiate = FALSE; + data->set.httpntlm = FALSE; break; case CURLHTTP_DIGEST: /* Enable HTTP Digest authentication */ data->set.httpdigest = TRUE; data->set.httpnegotiate = FALSE; + data->set.httpntlm = FALSE; break; + case CURLHTTP_NTLM: + /* Enable HTTP NTLM authentication */ +#ifdef USE_SSLEAY + /* We can only support NTLM if OpenSSL is present, as we need their + crypto package for it */ + data->set.httpdigest = FALSE; + data->set.httpnegotiate = FALSE; + data->set.httpntlm = TRUE; + break; +#else + /* fall-through */ +#endif case CURLHTTP_NEGOTIATE: #ifdef GSSAPI /* Enable HTTP Negotaiate authentication */ data->set.httpdigest = FALSE; data->set.httpnegotiate = TRUE; + data->set.httpntlm = FALSE; break; #else /* fall-through */ diff --git a/lib/urldata.h b/lib/urldata.h index 459c5c468..4a2e97f06 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -164,6 +164,20 @@ struct digestdata { int algo; }; +typedef enum { + NTLMSTATE_NONE, + NTLMSTATE_TYPE1, + NTLMSTATE_TYPE2, + NTLMSTATE_TYPE3, + NTLMSTATE_LAST +} curlntlm; + +/* Struct used for Digest challenge-response authentication */ +struct ntlmdata { + curlntlm state; + unsigned char nonce[8]; +}; + #ifdef GSSAPI struct negotiatedata { OM_uint32 status; @@ -640,6 +654,7 @@ struct UrlState { is always set TRUE when curl_easy_perform() is called. */ struct digestdata digest; + struct ntlmdata ntlm; #ifdef GSSAPI struct negotiatedata negotiate; @@ -688,8 +703,9 @@ struct UserDefined { char *set_proxy; /* proxy to use */ long use_port; /* which port to use (when not using default) */ char *userpwd; /* , if used */ - bool httpdigest; /* if HTTP Digest is enabled */ + bool httpdigest; /* if HTTP Digest authentication is enabled */ bool httpnegotiate; /* if HTTP Negotiate authentication is enabled */ + bool httpntlm; /* if HTTP NTLM authentication is enabled */ char *set_range; /* range, if used. See README for detailed specification on this syntax. */ long followlocation; /* as in HTTP Location: */