[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