[3/3] winhttp: Add support for NTLM and Negotiate authentication.
Hans Leidekker
hans at codeweavers.com
Tue Apr 30 04:32:59 CDT 2013
---
dlls/winhttp/request.c | 424 +++++++++++++++++++++++++++++++++-------
dlls/winhttp/session.c | 3 +
dlls/winhttp/winhttp_private.h | 28 ++-
3 files changed, 380 insertions(+), 75 deletions(-)
diff --git a/dlls/winhttp/request.c b/dlls/winhttp/request.c
index 8d579b3..861ddef 100644
--- a/dlls/winhttp/request.c
+++ b/dlls/winhttp/request.c
@@ -1206,29 +1206,45 @@ BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD heade
#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0]))
-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;
+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};
- if (!strncmpiW( header, ntlm, ARRAYSIZE(ntlm) ) &&
- (header[ARRAYSIZE(ntlm)] == ' ' || !header[ARRAYSIZE(ntlm)])) return WINHTTP_AUTH_SCHEME_NTLM;
+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]);
- if (!strncmpiW( header, passport, ARRAYSIZE(passport) ) &&
- (header[ARRAYSIZE(passport)] == ' ' || !header[ARRAYSIZE(passport)])) return WINHTTP_AUTH_SCHEME_PASSPORT;
+static enum auth_scheme scheme_from_flag( DWORD flag )
+{
+ int i;
- if (!strncmpiW( header, digest, ARRAYSIZE(digest) ) &&
- (header[ARRAYSIZE(digest)] == ' ' || !header[ARRAYSIZE(digest)])) return WINHTTP_AUTH_SCHEME_DIGEST;
+ for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i;
+ return SCHEME_INVALID;
+}
- if (!strncmpiW( header, negotiate, ARRAYSIZE(negotiate) ) &&
- (header[ARRAYSIZE(negotiate)] == ' ' || !header[ARRAYSIZE(negotiate)])) return WINHTTP_AUTH_SCHEME_NEGOTIATE;
+static DWORD auth_scheme_from_header( WCHAR *header )
+{
+ 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,74 +1357,343 @@ 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;
- BOOL ret;
+ 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;
+}
- if (!username || !password)
+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)
{
- set_last_error( ERROR_INVALID_PARAMETER );
- return FALSE;
+ 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: auth_target = attr_authorization; break;
- case WINHTTP_AUTH_TARGET_PROXY: auth_target = attr_proxy_authorization; break;
+ 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 WINHTTP_AUTH_SCHEME_BASIC:
+ case 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 );
+ int userlen, passlen;
- TRACE("basic authentication\n");
+ if (!username || !password) return FALSE;
+ if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE;
- auth_scheme = basic;
- auth_data_len = userlen + 1 + passlen;
- if (!(auth_data = heap_alloc( auth_data_len ))) 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 );
- 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 );
+ 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 = ∈
+
+ 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;
}
- 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);
+ ERR("invalid scheme %u\n", scheme);
return FALSE;
}
+ *auth_ptr = authinfo;
- len = strlenW( auth_scheme ) + ((auth_data_len + 2) * 4) / 3;
- if (!(auth_header = heap_alloc( (len + 1) * sizeof(WCHAR) )))
- {
- heap_free( auth_data );
- return FALSE;
- }
- strcpyW( auth_header, auth_scheme );
- encode_base64( auth_data, auth_data_len, auth_header + strlenW( auth_header ) );
+ 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;
- ret = process_header( request, auth_target, auth_header, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE, TRUE );
+ 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 );
- heap_free( auth_data );
- heap_free( auth_header );
+ 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:
+ {
+ 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_TARGET_PROXY:
+ {
+ 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;
+ }
+ return TRUE;
+}
+
/***********************************************************************
* WinHttpSetCredentials (winhttp.@)
*/
@@ -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;
- }
- else
- {
- username = request->connect->session->proxy_username;
- password = request->connect->session->proxy_password;
+ if (!(schemes & auth_schemes[i].scheme)) continue;
+ if (do_authorization( request, target, auth_schemes[i].scheme )) return TRUE;
}
-
- if (schemes & WINHTTP_AUTH_SCHEME_BASIC)
- return set_credentials( request, target, WINHTTP_AUTH_SCHEME_BASIC, username, password );
-
- FIXME("unsupported authentication scheme\n");
return FALSE;
}
diff --git a/dlls/winhttp/session.c b/dlls/winhttp/session.c
index 394fddd..660eae5 100644
--- a/dlls/winhttp/session.c
+++ b/dlls/winhttp/session.c
@@ -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 );
diff --git a/dlls/winhttp/winhttp_private.h b/dlls/winhttp/winhttp_private.h
index d47bed3..f3e9496 100644
--- a/dlls/winhttp/winhttp_private.h
+++ b/dlls/winhttp/winhttp_private.h
@@ -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;
--
1.7.10.4
More information about the wine-patches
mailing list