winhttp: Add support for NTLM and Negotiate authentication.

This commit is contained in:
Hans Leidekker 2013-04-30 11:32:59 +02:00 committed by Alexandre Julliard
parent d63ef4d855
commit a506d0aa3b
3 changed files with 392 additions and 87 deletions

View File

@ -1206,29 +1206,45 @@ BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD heade
#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0]))
static const WCHAR basicW[] = {'B','a','s','i','c',0};
static const WCHAR ntlmW[] = {'N','T','L','M',0};
static const WCHAR passportW[] = {'P','a','s','s','p','o','r','t',0};
static const WCHAR digestW[] = {'D','i','g','e','s','t',0};
static const WCHAR negotiateW[] = {'N','e','g','o','t','i','a','t','e',0};
static const struct
{
const WCHAR *str;
unsigned int len;
DWORD scheme;
}
auth_schemes[] =
{
{ basicW, ARRAYSIZE(basicW) - 1, WINHTTP_AUTH_SCHEME_BASIC },
{ ntlmW, ARRAYSIZE(ntlmW) - 1, WINHTTP_AUTH_SCHEME_NTLM },
{ passportW, ARRAYSIZE(passportW) - 1, WINHTTP_AUTH_SCHEME_PASSPORT },
{ digestW, ARRAYSIZE(digestW) - 1, WINHTTP_AUTH_SCHEME_DIGEST },
{ negotiateW, ARRAYSIZE(negotiateW) - 1, WINHTTP_AUTH_SCHEME_NEGOTIATE }
};
static const unsigned int num_auth_schemes = sizeof(auth_schemes)/sizeof(auth_schemes[0]);
static enum auth_scheme scheme_from_flag( DWORD flag )
{
int i;
for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i;
return SCHEME_INVALID;
}
static DWORD auth_scheme_from_header( WCHAR *header )
{
static const WCHAR basic[] = {'B','a','s','i','c'};
static const WCHAR ntlm[] = {'N','T','L','M'};
static const WCHAR passport[] = {'P','a','s','s','p','o','r','t'};
static const WCHAR digest[] = {'D','i','g','e','s','t'};
static const WCHAR negotiate[] = {'N','e','g','o','t','i','a','t','e'};
if (!strncmpiW( header, basic, ARRAYSIZE(basic) ) &&
(header[ARRAYSIZE(basic)] == ' ' || !header[ARRAYSIZE(basic)])) return WINHTTP_AUTH_SCHEME_BASIC;
if (!strncmpiW( header, ntlm, ARRAYSIZE(ntlm) ) &&
(header[ARRAYSIZE(ntlm)] == ' ' || !header[ARRAYSIZE(ntlm)])) return WINHTTP_AUTH_SCHEME_NTLM;
if (!strncmpiW( header, passport, ARRAYSIZE(passport) ) &&
(header[ARRAYSIZE(passport)] == ' ' || !header[ARRAYSIZE(passport)])) return WINHTTP_AUTH_SCHEME_PASSPORT;
if (!strncmpiW( header, digest, ARRAYSIZE(digest) ) &&
(header[ARRAYSIZE(digest)] == ' ' || !header[ARRAYSIZE(digest)])) return WINHTTP_AUTH_SCHEME_DIGEST;
if (!strncmpiW( header, negotiate, ARRAYSIZE(negotiate) ) &&
(header[ARRAYSIZE(negotiate)] == ' ' || !header[ARRAYSIZE(negotiate)])) return WINHTTP_AUTH_SCHEME_NEGOTIATE;
unsigned int i;
for (i = 0; i < num_auth_schemes; i++)
{
if (!strncmpiW( header, auth_schemes[i].str, auth_schemes[i].len ) &&
(header[auth_schemes[i].len] == ' ' || !header[auth_schemes[i].len])) return auth_schemes[i].scheme;
}
return 0;
}
@ -1341,72 +1357,341 @@ static UINT encode_base64( const char *bin, unsigned int len, WCHAR *base64 )
return n;
}
static BOOL set_credentials( request_t *request, DWORD target, DWORD scheme, LPCWSTR username, LPCWSTR password )
static inline char decode_char( WCHAR c )
{
static const WCHAR basic[] = {'B','a','s','i','c',' ',0};
const WCHAR *auth_scheme, *auth_target;
WCHAR *auth_header;
DWORD len, auth_data_len;
char *auth_data;
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return 64;
}
static unsigned int decode_base64( const WCHAR *base64, unsigned int len, char *buf )
{
unsigned int i = 0;
char c0, c1, c2, c3;
const WCHAR *p = base64;
while (len >= 4)
{
if ((c0 = decode_char( p[0] )) > 63) return 0;
if ((c1 = decode_char( p[1] )) > 63) return 0;
if ((c2 = decode_char( p[2] )) > 63) return 0;
if ((c3 = decode_char( p[3] )) > 63) return 0;
if (buf)
{
buf[i + 0] = (c0 << 2) | (c1 >> 4);
buf[i + 1] = (c1 << 4) | (c2 >> 2);
buf[i + 2] = (c2 << 6) | c3;
}
len -= 4;
i += 3;
p += 4;
}
if (p[2] == '=')
{
if ((c0 = decode_char( p[0] )) > 63) return 0;
if ((c1 = decode_char( p[1] )) > 63) return 0;
if (buf) buf[i] = (c0 << 2) | (c1 >> 4);
i++;
}
else if (p[3] == '=')
{
if ((c0 = decode_char( p[0] )) > 63) return 0;
if ((c1 = decode_char( p[1] )) > 63) return 0;
if ((c2 = decode_char( p[2] )) > 63) return 0;
if (buf)
{
buf[i + 0] = (c0 << 2) | (c1 >> 4);
buf[i + 1] = (c1 << 4) | (c2 >> 2);
}
i += 2;
}
return i;
}
static struct authinfo *alloc_authinfo(void)
{
struct authinfo *ret;
if (!(ret = heap_alloc( sizeof(*ret) ))) return NULL;
SecInvalidateHandle( &ret->cred );
SecInvalidateHandle( &ret->ctx );
memset( &ret->exp, 0, sizeof(ret->exp) );
ret->scheme = 0;
ret->attr = 0;
ret->max_token = 0;
ret->data = NULL;
ret->data_len = 0;
ret->finished = FALSE;
return ret;
}
void destroy_authinfo( struct authinfo *authinfo )
{
if (!authinfo) return;
if (SecIsValidHandle( &authinfo->ctx ))
DeleteSecurityContext( &authinfo->ctx );
if (SecIsValidHandle( &authinfo->cred ))
FreeCredentialsHandle( &authinfo->cred );
heap_free( authinfo->data );
heap_free( authinfo );
}
static BOOL get_authvalue( request_t *request, DWORD level, DWORD scheme, WCHAR *buffer, DWORD len )
{
DWORD size, index = 0;
for (;;)
{
size = len;
if (!query_headers( request, level, NULL, buffer, &size, &index )) return FALSE;
if (auth_scheme_from_header( buffer ) == scheme) break;
}
return TRUE;
}
static BOOL do_authorization( request_t *request, DWORD target, DWORD scheme_flag )
{
struct authinfo *authinfo, **auth_ptr;
enum auth_scheme scheme = scheme_from_flag( scheme_flag );
const WCHAR *auth_target, *username, *password;
WCHAR auth_value[2048], *auth_reply;
DWORD len = sizeof(auth_value), len_scheme, flags;
BOOL ret;
if (scheme == SCHEME_INVALID) return FALSE;
switch (target)
{
case WINHTTP_AUTH_TARGET_SERVER:
if (!get_authvalue( request, WINHTTP_QUERY_WWW_AUTHENTICATE, scheme_flag, auth_value, len ))
return FALSE;
auth_ptr = &request->authinfo;
auth_target = attr_authorization;
username = request->connect->username;
password = request->connect->password;
break;
case WINHTTP_AUTH_TARGET_PROXY:
if (!get_authvalue( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, scheme_flag, auth_value, len ))
return FALSE;
auth_ptr = &request->proxy_authinfo;
auth_target = attr_proxy_authorization;
username = request->connect->session->proxy_username;
password = request->connect->session->proxy_password;
break;
default:
WARN("unknown target %x\n", target);
return FALSE;
}
authinfo = *auth_ptr;
switch (scheme)
{
case SCHEME_BASIC:
{
int userlen, passlen;
if (!username || !password) return FALSE;
if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE;
userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL );
passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL );
authinfo->data_len = userlen + 1 + passlen;
if (!(authinfo->data = heap_alloc( authinfo->data_len ))) return FALSE;
WideCharToMultiByte( CP_UTF8, 0, username, -1, authinfo->data, userlen, NULL, NULL );
authinfo->data[userlen] = ':';
WideCharToMultiByte( CP_UTF8, 0, password, -1, authinfo->data + userlen + 1, passlen, NULL, NULL );
authinfo->scheme = SCHEME_BASIC;
authinfo->finished = TRUE;
break;
}
case SCHEME_NTLM:
case SCHEME_NEGOTIATE:
{
SECURITY_STATUS status;
SecBufferDesc out_desc, in_desc;
SecBuffer out, in;
ULONG flags = ISC_REQ_CONNECTION|ISC_REQ_USE_DCE_STYLE|ISC_REQ_MUTUAL_AUTH|ISC_REQ_DELEGATE;
const WCHAR *p;
BOOL first = FALSE;
if (!authinfo)
{
TimeStamp exp;
SEC_WINNT_AUTH_IDENTITY_W id;
WCHAR *domain, *user;
if (!username || !password || !(authinfo = alloc_authinfo())) return FALSE;
first = TRUE;
domain = (WCHAR *)username;
user = strchrW( username, '\\' );
if (user) user++;
else
{
user = (WCHAR *)username;
domain = NULL;
}
id.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
id.User = user;
id.UserLength = strlenW( user );
id.Domain = domain;
id.DomainLength = domain ? user - domain - 1 : 0;
id.Password = (WCHAR *)password;
id.PasswordLength = strlenW( password );
status = AcquireCredentialsHandleW( NULL, (SEC_WCHAR *)auth_schemes[scheme].str,
SECPKG_CRED_OUTBOUND, NULL, &id, NULL, NULL,
&authinfo->cred, &exp );
if (status == SEC_E_OK)
{
PSecPkgInfoW info;
status = QuerySecurityPackageInfoW( (SEC_WCHAR *)auth_schemes[scheme].str, &info );
if (status == SEC_E_OK)
{
authinfo->max_token = info->cbMaxToken;
FreeContextBuffer( info );
}
}
if (status != SEC_E_OK)
{
WARN("AcquireCredentialsHandleW for scheme %s failed with error 0x%08x\n",
debugstr_w(auth_schemes[scheme].str), status);
heap_free( authinfo );
return FALSE;
}
authinfo->scheme = scheme;
}
else if (authinfo->finished) return FALSE;
if ((strlenW( auth_value ) < auth_schemes[authinfo->scheme].len ||
strncmpiW( auth_value, auth_schemes[authinfo->scheme].str, auth_schemes[authinfo->scheme].len )))
{
ERR("authentication scheme changed from %s to %s\n",
debugstr_w(auth_schemes[authinfo->scheme].str), debugstr_w(auth_value));
return FALSE;
}
in.BufferType = SECBUFFER_TOKEN;
in.cbBuffer = 0;
in.pvBuffer = NULL;
in_desc.ulVersion = 0;
in_desc.cBuffers = 1;
in_desc.pBuffers = &in;
p = auth_value + auth_schemes[scheme].len;
if (*p == ' ')
{
int len = strlenW( ++p );
in.cbBuffer = decode_base64( p, len, NULL );
if (!(in.pvBuffer = heap_alloc( in.cbBuffer ))) return FALSE;
decode_base64( p, len, in.pvBuffer );
}
out.BufferType = SECBUFFER_TOKEN;
out.cbBuffer = authinfo->max_token;
if (!(out.pvBuffer = heap_alloc( authinfo->max_token )))
{
heap_free( in.pvBuffer );
return FALSE;
}
out_desc.ulVersion = 0;
out_desc.cBuffers = 1;
out_desc.pBuffers = &out;
status = InitializeSecurityContextW( first ? &authinfo->cred : NULL, first ? NULL : &authinfo->ctx,
first ? request->connect->servername : NULL, flags, 0,
SECURITY_NETWORK_DREP, in.pvBuffer ? &in_desc : NULL, 0,
&authinfo->ctx, &out_desc, &authinfo->attr, &authinfo->exp );
heap_free( in.pvBuffer );
if (status == SEC_E_OK)
{
heap_free( authinfo->data );
authinfo->data = out.pvBuffer;
authinfo->data_len = out.cbBuffer;
authinfo->finished = TRUE;
TRACE("sending last auth packet\n");
}
else if (status == SEC_I_CONTINUE_NEEDED)
{
heap_free( authinfo->data );
authinfo->data = out.pvBuffer;
authinfo->data_len = out.cbBuffer;
TRACE("sending next auth packet\n");
}
else
{
ERR("InitializeSecurityContextW failed with error 0x%08x\n", status);
heap_free( out.pvBuffer );
destroy_authinfo( authinfo );
*auth_ptr = NULL;
return FALSE;
}
break;
}
default:
ERR("invalid scheme %u\n", scheme);
return FALSE;
}
*auth_ptr = authinfo;
len_scheme = auth_schemes[authinfo->scheme].len;
len = len_scheme + 1 + ((authinfo->data_len + 2) * 4) / 3;
if (!(auth_reply = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE;
memcpy( auth_reply, auth_schemes[authinfo->scheme].str, len_scheme * sizeof(WCHAR) );
auth_reply[len_scheme] = ' ';
encode_base64( authinfo->data, authinfo->data_len, auth_reply + len_scheme + 1 );
flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE;
ret = process_header( request, auth_target, auth_reply, flags, TRUE );
heap_free( auth_reply );
return ret;
}
static BOOL set_credentials( request_t *request, DWORD target, DWORD scheme, const WCHAR *username,
const WCHAR *password )
{
if (!username || !password)
{
set_last_error( ERROR_INVALID_PARAMETER );
return FALSE;
}
switch (target)
{
case WINHTTP_AUTH_TARGET_SERVER: auth_target = attr_authorization; break;
case WINHTTP_AUTH_TARGET_PROXY: auth_target = attr_proxy_authorization; break;
default:
WARN("unknown target %x\n", target);
return FALSE;
}
switch (scheme)
case WINHTTP_AUTH_TARGET_SERVER:
{
case WINHTTP_AUTH_SCHEME_BASIC:
{
int userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL );
int passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL );
TRACE("basic authentication\n");
auth_scheme = basic;
auth_data_len = userlen + 1 + passlen;
if (!(auth_data = heap_alloc( auth_data_len ))) return FALSE;
WideCharToMultiByte( CP_UTF8, 0, username, -1, auth_data, userlen, NULL, NULL );
auth_data[userlen] = ':';
WideCharToMultiByte( CP_UTF8, 0, password, -1, auth_data + userlen + 1, passlen, NULL, NULL );
heap_free( request->connect->username );
if (!(request->connect->username = strdupW( username ))) return FALSE;
heap_free( request->connect->password );
if (!(request->connect->password = strdupW( password ))) return FALSE;
break;
}
case WINHTTP_AUTH_SCHEME_NTLM:
case WINHTTP_AUTH_SCHEME_PASSPORT:
case WINHTTP_AUTH_SCHEME_DIGEST:
case WINHTTP_AUTH_SCHEME_NEGOTIATE:
FIXME("unimplemented authentication scheme %x\n", scheme);
return FALSE;
default:
WARN("unknown authentication scheme %x\n", scheme);
return FALSE;
}
len = strlenW( auth_scheme ) + ((auth_data_len + 2) * 4) / 3;
if (!(auth_header = heap_alloc( (len + 1) * sizeof(WCHAR) )))
case WINHTTP_AUTH_TARGET_PROXY:
{
heap_free( auth_data );
heap_free( request->connect->session->proxy_username );
if (!(request->connect->session->proxy_username = strdupW( username ))) return FALSE;
heap_free( request->connect->session->proxy_password );
if (!(request->connect->session->proxy_password = strdupW( password ))) return FALSE;
break;
}
default:
WARN("unknown target %u\n", target);
return FALSE;
}
strcpyW( auth_header, auth_scheme );
encode_base64( auth_data, auth_data_len, auth_header + strlenW( auth_header ) );
ret = process_header( request, auth_target, auth_header, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE, TRUE );
heap_free( auth_data );
heap_free( auth_header );
return ret;
return TRUE;
}
/***********************************************************************
@ -1440,8 +1725,7 @@ BOOL WINAPI WinHttpSetCredentials( HINTERNET hrequest, DWORD target, DWORD schem
static BOOL handle_authorization( request_t *request, DWORD status )
{
DWORD schemes, level, target;
const WCHAR *username, *password;
DWORD i, schemes, first, level, target;
switch (status)
{
@ -1460,23 +1744,15 @@ static BOOL handle_authorization( request_t *request, DWORD status )
return FALSE;
}
if (!query_auth_schemes( request, level, &schemes, NULL )) return FALSE;
if (!query_auth_schemes( request, level, &schemes, &first )) return FALSE;
if (do_authorization( request, target, first )) return TRUE;
if (target == WINHTTP_AUTH_TARGET_SERVER)
schemes &= ~first;
for (i = 0; i < num_auth_schemes; i++)
{
username = request->connect->username;
password = request->connect->password;
if (!(schemes & auth_schemes[i].scheme)) continue;
if (do_authorization( request, target, auth_schemes[i].scheme )) return TRUE;
}
else
{
username = request->connect->session->proxy_username;
password = request->connect->session->proxy_password;
}
if (schemes & WINHTTP_AUTH_SCHEME_BASIC)
return set_credentials( request, target, WINHTTP_AUTH_SCHEME_BASIC, username, password );
FIXME("unsupported authentication scheme\n");
return FALSE;
}

View File

@ -552,6 +552,9 @@ static void request_destroy( object_header_t *hdr )
release_object( &request->connect->hdr );
destroy_authinfo( request->authinfo );
destroy_authinfo( request->proxy_authinfo );
heap_free( request->verb );
heap_free( request->path );
heap_free( request->version );

View File

@ -148,6 +148,29 @@ typedef struct
BOOL is_request; /* part of request headers? */
} header_t;
enum auth_scheme
{
SCHEME_INVALID = -1,
SCHEME_BASIC,
SCHEME_NTLM,
SCHEME_PASSPORT,
SCHEME_DIGEST,
SCHEME_NEGOTIATE
};
struct authinfo
{
enum auth_scheme scheme;
CredHandle cred;
CtxtHandle ctx;
TimeStamp exp;
ULONG attr;
ULONG max_token;
char *data;
unsigned int data_len;
BOOL finished; /* finished authenticating */
};
typedef struct
{
object_header_t hdr;
@ -174,6 +197,8 @@ typedef struct
DWORD num_headers;
WCHAR **accept_types;
DWORD num_accept_types;
struct authinfo *authinfo;
struct authinfo *proxy_authinfo;
} request_t;
typedef struct _task_header_t task_header_t;
@ -252,7 +277,8 @@ BOOL set_cookies( request_t *, const WCHAR * ) DECLSPEC_HIDDEN;
BOOL add_cookie_headers( request_t * ) DECLSPEC_HIDDEN;
BOOL add_request_headers( request_t *, LPCWSTR, DWORD, DWORD ) DECLSPEC_HIDDEN;
void delete_domain( domain_t * ) DECLSPEC_HIDDEN;
BOOL set_server_for_hostname( connect_t *connect, LPCWSTR server, INTERNET_PORT port ) DECLSPEC_HIDDEN;
BOOL set_server_for_hostname( connect_t *, LPCWSTR, INTERNET_PORT ) DECLSPEC_HIDDEN;
void destroy_authinfo( struct authinfo * ) DECLSPEC_HIDDEN;
extern HRESULT WinHttpRequest_create( IUnknown *, void ** ) DECLSPEC_HIDDEN;