|
|
/*--
Copyright (c) 1995-1998 Microsoft Corporation Module Name: AUTH.CPP Author: Arul Menezes Abstract: Authentication --*/ #include "pch.h"
#pragma hdrstop
#include "httpd.h"
#define IsAccessAllowed(a,b,c,d) TRUE
#define NTLM_DOMAIN TEXT("Domain") // dummy data.
#define SEC_SUCCESS(Status) ((Status) >= 0)
#ifndef OLD_CE_BUILD
BOOL NTLMServerContext( PSEC_WINNT_AUTH_IDENTITY pAuthIdentity, PAUTH_NTLM pAS,BYTE *pIn, DWORD cbIn, BYTE *pOut, DWORD *pcbOut, BOOL *pfDone );
BOOL NTLMClientContext( PSEC_WINNT_AUTH_IDENTITY pAuthIdentity, PAUTH_NTLM pAS, BYTE *pIn, DWORD cbIn, BYTE *pOut, DWORD *pcbOut ); #endif
AUTHLEVEL GetAuthFromACL(PAUTH_NTLM pAuth, PWSTR wszUser, PWSTR wszVRootUserList);
void AuthInitialize(CReg *pReg, BOOL * pfBasicAuth, BOOL * pfNTLMAuth) { *pfBasicAuth = pReg->ValueDW(RV_BASIC); #ifdef OLD_CE_BUILD
// No passthrough Auth on CE 2.11 devices, set it to 0 to avoid misconfigurations
*pfNTLMAuth = 0; #else
*pfNTLMAuth = pReg->ValueDW(RV_NTLM); #endif
}
// For calls to Basic Authentication, only called during the parsing stage.
BOOL HandleBasicAuth(PSTR pszData, PSTR* ppszUser, PSTR *ppszPassword, AUTHLEVEL* pAuth, PAUTH_NTLM pNTLMState, WCHAR *wszVRootUserList) { char szUserName[MAXUSERPASS]; DWORD dwLen = sizeof(szUserName);
*pAuth = AUTH_PUBLIC; *ppszUser = NULL;
// decode the base64
Base64Decode(pszData, szUserName, &dwLen);
// find the password
PSTR pszPassword = strchr(szUserName, ':'); if (!pszPassword) { TraceTag(ttidWebServer, "Bad Format for Basic userpass(%s)-->(%s)", pszData, szUserName); return FALSE; } *pszPassword++ = 0; // seperate user & pass
WCHAR wszPassword[MAXUSERPASS]; MyA2W(pszPassword, wszPassword, CCHSIZEOF(wszPassword));
WCHAR wszUserName[MAXUSERPASS]; MyA2W(szUserName,wszUserName, CCHSIZEOF(wszUserName));
// We save the data no matter what, for logging purposes and for possible
// GetServerVariable call.
*ppszUser = MySzDupA(szUserName); *ppszPassword = MySzDupA(pszPassword);
// If AUTH_USER has been granted, check to see if they're an administrator.
// In BASIC we can only check against the name (no NTLM group info)
#ifdef UNDER_CE
if (CheckPassword(wszPassword)) { *pAuth = GetAuthFromACL(NULL,wszUserName,wszVRootUserList); return TRUE; } #endif
#ifndef OLD_CE_BUILD
if (g_pVars->m_fNTLMAuth && BasicToNTLM(pNTLMState, wszPassword,wszUserName,pAuth,wszVRootUserList)) { return TRUE; } #endif
TraceTag(ttidWebServer, "Failed logon with Basic userpass(%s)-->(%s)(%s)", pszData, szUserName, pszPassword); return FALSE; }
#ifdef OLD_CE_BUILD
// The web server beta doesn't have NTLM, but it uses this file so we
// don't use AuthStub.cpp here
BOOL CHttpRequest::HandleNTLMAuth(PSTR pszNTLMData) { return FALSE; }
BOOL NTLMInitLib(PAUTH_NTLM pNTLMState) { return FALSE; }
void FreeNTLMHandles(PAUTH_NTLM pNTLMState) { ; } #else
// This function is called 2 times during an NTLM auth session. The first
// time it has the Client user name, domain,... which it forwards to DC or
// looks in registry for (this NTLM detail is transparent to httpd)
// On 2nd time it has the client's response, which either is or is not
// enough to grant access to page. On 2nd pass, free up all NTLM context data.
// FILTER NOTES: On IIS no user name or password info is given to the filter
// on NTLM calls, so neither do we. (WinCE does give BASIC data, though).
// The main reason this is is that
BOOL CHttpRequest::HandleNTLMAuth(PSTR pszNTLMData) { DEBUG_CODE_INIT; BOOL ret = FALSE; DWORD dwIn; DWORD dwOut; BOOL fDone = FALSE; PBYTE pOutBuf = NULL; PBYTE pInBuf = NULL; // Base64 decoded data from pszNTLMData
// Are the NTLM libs loaded already?
if (NTLM_NO_INIT_LIB == m_NTLMState.m_Conversation) { if ( ! NTLMInitLib(&m_NTLMState)) myretleave(FALSE,94); }
dwOut = g_pVars->m_cbNTLMMax; if (NULL == (pOutBuf = MyRgAllocNZ(BYTE,dwOut))) myleave(360);
if (NULL == m_pszNTLMOutBuf) { // We will later Base64Encode pOutBuf later, encoding writes 4 outbut bytes
// for every 3 input bytes
if (NULL == (m_pszNTLMOutBuf = MyRgAllocNZ(CHAR,dwOut*(4/3) + 1))) myleave(361); }
dwIn = strlen(pszNTLMData) + 1; if (NULL == (pInBuf = MyRgAllocNZ(BYTE,dwIn))) myleave(363);
Base64Decode(pszNTLMData,(PSTR) pInBuf,&dwIn);
// On the 1st pass this gets a data blob to be sent back to the client
// broweser in pOutBuf, which is encoded to m_pszNTLMOutBuf. On the 2nd
// pass it either authenticates or fails.
if (! NTLMServerContext(NULL, &m_NTLMState, pInBuf, dwIn,pOutBuf,&dwOut,&fDone)) { // Note: We MUST free the m_pszNTMLOutBuf on 2nd pass failure. If the
// client recieves the blob on a failure
// it will consider the web server to be malfunctioning and will not send
// another set of data, and will not prompt the user for a password.
MyFree(m_pszNTLMOutBuf);
// Setting to DONE will cause the local structs to be freed; they must
// be fresh in case browser attempts to do NTLM again with new user name/
// password on same session. Don't bother unloading the lib.
m_NTLMState.m_Conversation = NTLM_DONE; myleave(362); }
if (fDone) { TraceTag(ttidWebServer, "NTLM Successfully authenticated user"); m_AuthLevelGranted = GetAuthFromACL(&m_NTLMState,NULL,m_wszVRootUserList);
m_dwAuthFlags |= m_AuthLevelGranted; m_NTLMState.m_Conversation = NTLM_DONE; MyFree(m_pszNTLMOutBuf);
myretleave(TRUE,0); } Base64Encode(pOutBuf,dwOut,m_pszNTLMOutBuf);
ret = TRUE; done: TraceTag(ttidWebServer, "HandleNTLMAuthent died, err = %d, gle = %d",err,GetLastError());
MyFree(pOutBuf); MyFree(pInBuf); return ret; }
// Sets up NTLM info in the passed data structure. This fcn loads the library,
// gets the function table, and determines the maximum buffer size.
// stolen from osinternal\comm\test\security\sockauth\security.c
BOOL NTLMInitLib(PAUTH_NTLM pNTLMState) { DEBUG_CODE_INIT; FARPROC pInit; SECURITY_STATUS ss; PSecPkgInfo pkgInfo; BOOL ret = FALSE; PSecurityFunctionTable pLocal = NULL;
// load and initialize the ntlm ssp
//
if (g_pVars->m_pNTLMFuncs) { pNTLMState->m_Conversation = NTLM_NO_INIT_CONTEXT; return TRUE; }
g_pVars->m_hNTLMLib = LoadLibrary (NTLM_DLL_NAME); if (NULL == g_pVars->m_hNTLMLib) myleave(700);
pInit = (FARPROC) GetProcAddress (g_pVars->m_hNTLMLib, SECURITY_ENTRYPOINT_CE); if (NULL == pInit) myleave(701);
pLocal = (PSecurityFunctionTable) pInit (); if (NULL == pLocal) myleave(702);
// Query for the package we're interested in
//
ss = pLocal->QuerySecurityPackageInfo (NTLM_PACKAGE_NAME, &pkgInfo); if (!SEC_SUCCESS(ss)) myleave(703);
g_pVars->m_cbNTLMMax = pkgInfo->cbMaxToken; pLocal->FreeContextBuffer (pkgInfo);
// The libraries have been set, but pNTLMState's structures are still empty
pNTLMState->m_Conversation = NTLM_NO_INIT_CONTEXT;
TraceTag(ttidWebServer, "NTLM Libs successfully initialized");
ret = TRUE; done: if (FALSE == ret) { // Don't worry about freeing pkgInfo, it was freed right after creation
// anyway - no chance to go wrong
MyFreeLib (g_pVars->m_hNTLMLib);
// Set everything to false so httpd doesn't think we have legit data later.
memset(pNTLMState, 0 , sizeof(AUTH_NTLM)); } else { g_pVars->m_pNTLMFuncs = pLocal; // Flag that shows we've initialized
}
TraceTag(ttidWebServer, "NTLMInitLib failed, err = %d, GLE = 0x%08x",err, GetLastError()); return ret; }
// Unload the contexts. The library is NOT freed in this call, only freed
// in CHttpRequest destructor.
void FreeNTLMHandles(PAUTH_NTLM pNTLMState) { if (NULL == pNTLMState || NULL == g_pVars->m_pNTLMFuncs) return;
if (pNTLMState->m_fHaveCtxtHandle) g_pVars->m_pNTLMFuncs->DeleteSecurityContext (&pNTLMState->m_hctxt);
if (pNTLMState->m_fHaveCredHandle) g_pVars->m_pNTLMFuncs->FreeCredentialHandle (&pNTLMState->m_hcred);
pNTLMState->m_fHaveCredHandle = FALSE; pNTLMState->m_fHaveCtxtHandle = FALSE; }
// Given Basic authentication data, we try to "forge" and NTLM request
// This fcn simulates a client+server talking to each other, though it's in the
// same proc. The client is "virtual," doesn't refer to the http client
// pNTLMState is CHttpRequest::m_NTLMState
BOOL BasicToNTLM(PAUTH_NTLM pNTLMState, WCHAR * wszPassword, WCHAR * wszRemoteUser, AUTHLEVEL *pAuth, WCHAR *wszVRootUserList) { DEBUG_CODE_INIT;
AUTH_NTLM ClientState; // forges the client role
AUTH_NTLM ServerState; // forges the server role
BOOL fDone = FALSE; PBYTE pClientOutBuf = NULL; PBYTE pServerOutBuf = NULL; DWORD cbServerBuf; DWORD cbClientBuf;
DEBUGCHK(wszPassword != NULL && wszRemoteUser != NULL && pNTLMState != NULL);
SEC_WINNT_AUTH_IDENTITY AuthIdentityClient = { (unsigned short *)wszRemoteUser, wcslen(wszRemoteUser), (unsigned short *)NTLM_DOMAIN,sizeof(NTLM_DOMAIN)/sizeof(TCHAR) - 1, (unsigned short *)wszPassword, wcslen(wszPassword), 0}; // dummy domain needed
memset(&ServerState,0,sizeof(AUTH_NTLM)); memset(&ClientState,0,sizeof(AUTH_NTLM));
// 1st pass through, load up library.
if (NTLM_NO_INIT_LIB == pNTLMState->m_Conversation) { if ( ! NTLMInitLib(pNTLMState)) myleave(369);
pNTLMState->m_Conversation = NTLM_NO_INIT_CONTEXT; }
// NTLM auth functions seem to expect that these buffer will be zeroed.
pClientOutBuf = MyRgAllocZ(BYTE,g_pVars->m_cbNTLMMax); if (NULL == pClientOutBuf) myleave(370);
pServerOutBuf = MyRgAllocZ(BYTE,g_pVars->m_cbNTLMMax); if (NULL == pServerOutBuf) myleave(371);
ServerState.m_Conversation = NTLM_NO_INIT_CONTEXT; ClientState.m_Conversation = NTLM_NO_INIT_CONTEXT;
cbClientBuf = cbServerBuf = g_pVars->m_cbNTLMMax;
// Main loop that forges client and server talking.
while (!fDone) { cbClientBuf = g_pVars->m_cbNTLMMax; if (! NTLMClientContext(&AuthIdentityClient,&ClientState,pServerOutBuf, cbServerBuf, pClientOutBuf, &cbClientBuf)) { myleave(372); }
cbServerBuf = g_pVars->m_cbNTLMMax; if (! NTLMServerContext(&AuthIdentityClient,&ServerState, pClientOutBuf, cbClientBuf, pServerOutBuf, &cbServerBuf, &fDone)) { myleave(373); } }
done: TraceTag(ttidWebServer, "Unable to convert Basic Auth to NTLM Auth, err = %d",err);
if (fDone) { *pAuth = GetAuthFromACL(&ServerState,wszRemoteUser,wszVRootUserList); }
MyFree(pClientOutBuf); MyFree(pServerOutBuf);
FreeNTLMHandles(&ServerState); FreeNTLMHandles(&ClientState);
return fDone; }
// This calls the DC (or goes to registry in local case), either getting a
// data blob to return to client or granting auth or denying.
// stolen from osinternal\comm\test\security\sockauth\security.c
BOOL NTLMServerContext( PSEC_WINNT_AUTH_IDENTITY pAuthIdentity, PAUTH_NTLM pAS, // NTLM state info
BYTE *pIn, DWORD cbIn, BYTE *pOut, DWORD *pcbOut, BOOL *pfDone) { SECURITY_STATUS ss; TimeStamp Lifetime; SecBufferDesc OutBuffDesc; SecBuffer OutSecBuff; SecBufferDesc InBuffDesc; SecBuffer InSecBuff; ULONG ContextAttributes;
if (NTLM_NO_INIT_CONTEXT == pAS->m_Conversation) { ss = g_pVars->m_pNTLMFuncs->AcquireCredentialsHandle ( NULL, // principal
NTLM_PACKAGE_NAME, SECPKG_CRED_INBOUND, NULL, // LOGON id
pAuthIdentity, NULL, // get key fn
NULL, // get key arg
&pAS->m_hcred, &Lifetime ); if (SEC_SUCCESS (ss)) pAS->m_fHaveCredHandle = TRUE; else { TraceTag(ttidWebServer, "NTLM AcquireCreds failed: %X", ss); return(FALSE); } }
// prepare output buffer
//
OutBuffDesc.ulVersion = 0; OutBuffDesc.cBuffers = 1; OutBuffDesc.pBuffers = &OutSecBuff;
OutSecBuff.cbBuffer = *pcbOut; OutSecBuff.BufferType = SECBUFFER_TOKEN; OutSecBuff.pvBuffer = pOut;
// prepare input buffer
//
InBuffDesc.ulVersion = 0; InBuffDesc.cBuffers = 1; InBuffDesc.pBuffers = &InSecBuff;
InSecBuff.cbBuffer = cbIn; InSecBuff.BufferType = SECBUFFER_TOKEN; InSecBuff.pvBuffer = pIn;
ss = g_pVars->m_pNTLMFuncs->AcceptSecurityContext ( &pAS->m_hcred, (pAS->m_Conversation == NTLM_PROCESSING) ? &pAS->m_hctxt : NULL, &InBuffDesc, 0, // context requirements
SECURITY_NATIVE_DREP, &pAS->m_hctxt, &OutBuffDesc, &ContextAttributes, &Lifetime ); if (!SEC_SUCCESS (ss)) { TraceTag(ttidWebServer, "NTLM init context failed: %X", ss); return FALSE; }
pAS->m_fHaveCtxtHandle = TRUE;
// Complete token -- if applicable
//
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss)) { if (g_pVars->m_pNTLMFuncs->CompleteAuthToken) { ss = g_pVars->m_pNTLMFuncs->CompleteAuthToken (&pAS->m_hctxt, &OutBuffDesc); if (!SEC_SUCCESS(ss)) { TraceTag(ttidWebServer, " NTLM complete failed: %X", ss); return FALSE; } } else { TraceTag(ttidWebServer, "Complete not supported."); return FALSE; } }
*pcbOut = OutSecBuff.cbBuffer; pAS->m_Conversation = NTLM_PROCESSING;
*pfDone = !((SEC_I_CONTINUE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss));
return TRUE; }
// Forges the client browser's part in NTLM communication if the browser
// sent a Basic request. This is used primarily for Netscape clients, which only
// supports Basic.
// stolen from osinternal\comm\test\security\sockauth\security.c
BOOL NTLMClientContext( PSEC_WINNT_AUTH_IDENTITY pAuthIdentity, PAUTH_NTLM pAS, // NTLM state info
BYTE *pIn, DWORD cbIn, BYTE *pOut, DWORD *pcbOut) { SECURITY_STATUS ss; TimeStamp Lifetime; SecBufferDesc OutBuffDesc; SecBuffer OutSecBuff; SecBufferDesc InBuffDesc; SecBuffer InSecBuff; ULONG ContextAttributes;
if (NTLM_NO_INIT_CONTEXT == pAS->m_Conversation) { ss = g_pVars->m_pNTLMFuncs->AcquireCredentialsHandle ( NULL, // principal
NTLM_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, // LOGON id
pAuthIdentity, // auth data
NULL, // get key fn
NULL, // get key arg
&pAS->m_hcred, &Lifetime ); if (SEC_SUCCESS (ss)) pAS->m_fHaveCredHandle = TRUE; else { TraceTag(ttidWebServer, "AcquireCreds failed: %X", ss); return(FALSE); } }
// prepare output buffer
//
OutBuffDesc.ulVersion = 0; OutBuffDesc.cBuffers = 1; OutBuffDesc.pBuffers = &OutSecBuff;
OutSecBuff.cbBuffer = *pcbOut; OutSecBuff.BufferType = SECBUFFER_TOKEN; OutSecBuff.pvBuffer = pOut;
// prepare input buffer
if (NTLM_NO_INIT_CONTEXT != pAS->m_Conversation) { InBuffDesc.ulVersion = 0; InBuffDesc.cBuffers = 1; InBuffDesc.pBuffers = &InSecBuff;
InSecBuff.cbBuffer = cbIn; InSecBuff.BufferType = SECBUFFER_TOKEN; InSecBuff.pvBuffer = pIn; }
ss = g_pVars->m_pNTLMFuncs->InitializeSecurityContext ( &pAS->m_hcred, (pAS->m_Conversation == NTLM_PROCESSING) ? &pAS->m_hctxt : NULL, NULL, 0, // context requirements
0, // reserved1
SECURITY_NATIVE_DREP, (pAS->m_Conversation == NTLM_PROCESSING) ? &InBuffDesc : NULL, 0, // reserved2
&pAS->m_hctxt, &OutBuffDesc, &ContextAttributes, &Lifetime ); if (!SEC_SUCCESS (ss)) { TraceTag(ttidWebServer, "init context failed: %X", ss); return FALSE; }
pAS->m_fHaveCtxtHandle = TRUE;
// Complete token -- if applicable
//
if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss)) { if (g_pVars->m_pNTLMFuncs->CompleteAuthToken) { ss = g_pVars->m_pNTLMFuncs->CompleteAuthToken (&pAS->m_hctxt, &OutBuffDesc); if (!SEC_SUCCESS(ss)) { TraceTag(ttidWebServer, "complete failed: %X", ss); return FALSE; } } else { TraceTag(ttidWebServer, "Complete not supported."); return FALSE; } }
*pcbOut = OutSecBuff.cbBuffer; pAS->m_Conversation = NTLM_PROCESSING;
return TRUE; } #endif // OLD_CE_BUILD
// Called after a user has been successfully authenticated with either BASIC or NTLM.
// See \winceos\comm\security\authhlp to see ACL algorithm function/description.
// Our algorithm: AdminUsers reg value is set and user is a member, always grant
// Admin auth. If no AdminUser key is set always grant Admin auth level,
// UNLESS this vroot has a UserList set. If user is a member, then grant
// ADMIN_USER, otherwise set to AUTH_PUBLIC (no auth).
// If user is not in admin list but in UserList for vroot, grant AUTH_USER.
AUTHLEVEL GetAuthFromACL(PAUTH_NTLM pAuth, PWSTR wszUser, PWSTR wszVRootUserList) { AUTHLEVEL AuthGranted = AUTH_USER; PWSTR wsz = NULL; // User (in function)
PWSTR wszGroup = NULL; SecPkgContext_Names pkgName;
#if defined(UNDER_CE) && !defined (OLD_CE_BUILD)
SecPkgContext_GroupNames ContextGroups; ContextGroups.msGroupNames = NULL; #endif
pkgName.sUserName = NULL;
// If we're called from NTLM request (pAuth != NULL) then we need
// to get the user name if we don't have it. On BASIC request, we
// have wszUser name already.
if (pAuth && !wszUser) { if ( SEC_SUCCESS(g_pVars->m_pNTLMFuncs->QueryContextAttributes(&(pAuth->m_hctxt), SECPKG_ATTR_NAMES, &pkgName))) { wsz = pkgName.sUserName; } else goto done; // If we can't get user name, don't bother continuing
} else { wsz = wszUser; }
#if defined(UNDER_CE) && !defined (OLD_CE_BUILD)
if (pAuth) g_pVars->m_pNTLMFuncs->QueryContextAttributes(&(pAuth->m_hctxt),SECPKG_ATTR_GROUP_NAMES,&ContextGroups);
wszGroup = ContextGroups.msGroupNames; #endif
TraceTag(ttidWebServer, "ADmin Users = %s, wsz = %s, pkgName.sUserName = %s, group name = %s\r\n", g_pVars->m_wszAdminUsers,wsz,pkgName.sUserName,wszGroup);
// Administrators always get admin access and access to the page, even if
// they're barred in the VRoot list. If there is a vroot list and we fail
// the IsAccessAllowed test, we set the auth granted to 0 - this
// will deny access. If no VRoot user list is set, keep us at AUTH_USER.
if ( !g_pVars->m_wszAdminUsers && !wszVRootUserList) AuthGranted = AUTH_ADMIN; else if (g_pVars->m_wszAdminUsers && IsAccessAllowed(wsz,wszGroup,g_pVars->m_wszAdminUsers,FALSE)) AuthGranted = AUTH_ADMIN; else if (wszVRootUserList && IsAccessAllowed(wsz,wszGroup,wszVRootUserList,FALSE)) { // If there is no Admin list set, grant admin
if (!g_pVars->m_wszAdminUsers) AuthGranted = AUTH_ADMIN; else AuthGranted = AUTH_USER; } else if (wszVRootUserList) AuthGranted = AUTH_PUBLIC;
done: if (pkgName.sUserName) g_pVars->m_pNTLMFuncs->FreeContextBuffer(pkgName.sUserName);
#if defined(UNDER_CE) && !defined (OLD_CE_BUILD)
if (ContextGroups.msGroupNames) g_pVars->m_pNTLMFuncs->FreeContextBuffer(ContextGroups.msGroupNames); #endif
TraceTag(ttidWebServer, "HTTPD: GetAuthFromACL Admin Priveleges granted = %d",AuthGranted); return AuthGranted; }
|