windows-nt/Source/XPSP1/NT/net/upnp/host/upnphost/udhhttp/auth.cpp
2020-09-26 16:20:57 +08:00

715 lines
23 KiB
C++

/*--
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;
}