windows-nt/Source/XPSP1/NT/ds/adsi/msext/cuar.cxx

1816 lines
46 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
//---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995
//
// File: cuar.cxx
//
// Contents: Account Restrictions Propset for the User object
//
// History: 11-1-95 krishnag Created.
//
// PROPERTY_RW(AccountDisabled, boolean, 1) I
// PROPERTY_RW(AccountExpirationDate, DATE, 2) I
// PROPERTY_RO(AccountCanExpire, boolean, 3) I
// PROPERTY_RO(PasswordCanExpire, boolean, 4) I
// PROPERTY_RW(GraceLoginsAllowed, long, 5) NI
// PROPERTY_RW(GraceLoginsRemaining, long, 6) NI
// PROPERTY_RW(IsAccountLocked, boolean, 7) I
// PROPERTY_RW(IsAdmin, boolean, 8) I
// PROPERTY_RW(LoginHours, VARIANT, 9) I
// PROPERTY_RW(LoginWorkstations, VARIANT, 10) I
// PROPERTY_RW(MaxLogins, long, 11) I
// PROPERTY_RW(MaxStorage, long, 12) I
// PROPERTY_RW(PasswordExpirationDate, DATE, 13) I
// PROPERTY_RW(PasswordRequired, boolean, 14) I
// PROPERTY_RW(RequireUniquePassword,boolean, 15) I
//
//
//----------------------------------------------------------------------------
#include "ldap.hxx"
#pragma hdrstop
#include <lm.h>
#include <winldap.h>
#include "..\ldapc\ldpcache.hxx"
#include "..\ldapc\ldaputil.hxx"
#include "..\ldapc\parse.hxx"
#include <dsgetdc.h>
#include <sspi.h>
HRESULT
BuildLDAPPathFromADsPath2(
LPWSTR szADsPathName,
LPWSTR *pszLDAPServer,
LPWSTR *pszLDAPDn,
DWORD * pdwPort
);
DWORD
GetDefaultServer(
DWORD dwPort,
BOOL fVerify,
LPWSTR szDomainDnsName,
LPWSTR szServerName,
BOOL fWriteable
);
HRESULT
GetDomainDNSNameFromHost(
LPWSTR szHostName,
SEC_WINNT_AUTH_IDENTITY& AuthI,
CCredentials &Credentials,
DWORD dwPort,
LPWSTR * ppszHostName
);
//
// The list of server entries - detailing SSL support
//
PSERVSSLENTRY gpServerSSLList = NULL;
//
// Critical Section and support routines to protect list
//
CRITICAL_SECTION g_ServerListCritSect;
//
// Flag that indicates if kerberos is being used.
//
const unsigned long KERB_SUPPORT_FLAGS = ISC_RET_MUTUAL_AUTH ;
//
// Routines that support cacheing server SSL info for perf
//
#define STRING_LENGTH(p) ( p ? wcslen(p) : 0)
//
// Get the status of SSL support on the server pszServerName
// 0 indicates that the server was not in our list.
//
DWORD ReadServerSupportsSSL( LPWSTR pszServerName)
{
ENTER_SERVERLIST_CRITICAL_SECTION();
PSERVSSLENTRY pServerList = gpServerSSLList;
DWORD dwRetVal = 0;
//
// Keep going through the list until we hit the end or
// we find an entry that matches.
//
while ((pServerList != NULL) && (dwRetVal == 0)) {
#ifdef WIN95
if (!(_wcsicmp(pszServerName, pServerList->pszServerName))) {
#else
if (CompareStringW(
LOCALE_SYSTEM_DEFAULT,
NORM_IGNORECASE,
pszServerName,
-1,
pServerList->pszServerName,
-1
) == CSTR_EQUAL ) {
#endif
dwRetVal = pServerList->dwFlags;
}
pServerList = pServerList->pNext;
}
LEAVE_SERVERLIST_CRITICAL_SECTION();
return dwRetVal;
}
HRESULT UpdateServerSSLSupportStatus(
PWSTR pszServerName,
DWORD dwFlags
)
{
HRESULT hr = S_OK;
PSERVSSLENTRY pServEntry = NULL;
ENTER_SERVERLIST_CRITICAL_SECTION()
PSERVSSLENTRY pServerList = gpServerSSLList;
DWORD dwRetVal = 0;
ADsAssert(pszServerName && *pszServerName);
//
// Keep going through the list until we hit the end or
// we find an entry that matches.
//
while ((pServerList != NULL) && (dwRetVal == 0)) {
#ifdef WIN95
if (!(_wcsicmp(pszServerName, pServerList->pszServerName))) {
#else
if (CompareStringW(
LOCALE_SYSTEM_DEFAULT,
NORM_IGNORECASE,
pszServerName,
-1,
pServerList->pszServerName,
-1
) == CSTR_EQUAL ) {
#endif
pServerList->dwFlags = dwFlags;
LEAVE_SERVERLIST_CRITICAL_SECTION()
RRETURN(S_OK);
}
pServerList = pServerList->pNext;
}
pServEntry = (PSERVSSLENTRY) AllocADsMem(sizeof(SERVSSLENTRY));
if (!pServEntry) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
pServEntry->pszServerName = AllocADsStr(pszServerName);
if (!pServEntry->pszServerName) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
pServEntry->dwFlags = dwFlags;
pServEntry->pNext = gpServerSSLList;
gpServerSSLList = pServEntry;
error:
if (FAILED(hr) && pServEntry) {
//
// Free only pServEntry as the string cannot have
// a value in the error case
//
FreeADsMem(pServEntry);
}
LEAVE_SERVERLIST_CRITICAL_SECTION();
RRETURN(hr);
}
void FreeServerSSLSupportList()
{
PSERVSSLENTRY pList = gpServerSSLList;
PSERVSSLENTRY pPrevEntry = NULL;
while (pList) {
pPrevEntry = pList;
FreeADsStr(pList->pszServerName);
pList = pList->pNext;
FreeADsMem(pPrevEntry);
}
}
#if (!defined(WIN95))
//
// Take a AuthI struct and return a cred handle.
//
HRESULT
ConvertAuthIdentityToCredHandle(
SEC_WINNT_AUTH_IDENTITY& AuthI,
OUT PCredHandle CredentialsHandle
)
{
SECURITY_STATUS secStatus = SEC_E_OK;
TimeStamp Lifetime;
secStatus = AcquireCredentialsHandleWrapper(
NULL, // New principal
MICROSOFT_KERBEROS_NAME_W,
SECPKG_CRED_OUTBOUND,
NULL,
&AuthI,
NULL,
NULL,
CredentialsHandle,
&Lifetime
);
if (secStatus != SEC_E_OK) {
RRETURN(E_FAIL);
} else {
RRETURN(S_OK);
}
}
//
// ***** Caller must free the strings put in the ****
// ***** AuthIdentity struct later. ****
//
HRESULT
GetAuthIdentityForCaller(
CCredentials& Credentials,
IADs * pIADs,
OUT SEC_WINNT_AUTH_IDENTITY *pAuthI,
BOOL fEnforceMutualAuth
)
{
HRESULT hr = S_OK;
LPWSTR pszNTLMUser = NULL;
LPWSTR pszNTLMDomain = NULL;
LPWSTR pszDefaultServer = NULL;
LPWSTR dn = NULL;
LPWSTR passwd = NULL;
IADsObjOptPrivate * pADsPrivObjectOptions = NULL;
ULONG ulFlags = 0;
if (fEnforceMutualAuth) {
hr = pIADs->QueryInterface(
IID_IADsObjOptPrivate,
(void **)&pADsPrivObjectOptions
);
BAIL_ON_FAILURE(hr);
hr = pADsPrivObjectOptions->GetOption (
LDAP_MUTUAL_AUTH_STATUS,
(void *) &ulFlags
);
BAIL_ON_FAILURE(hr);
if (!(ulFlags & KERB_SUPPORT_FLAGS)) {
BAIL_ON_FAILURE(hr = E_FAIL);
}
}
hr = Credentials.GetUserName(&dn);
BAIL_ON_FAILURE(hr);
hr = Credentials.GetPassword(&passwd);
BAIL_ON_FAILURE(hr);
//
// Get the userName and password into the auth struct.
//
hr = LdapCrackUserDNtoNTLMUser2(
dn,
&pszNTLMUser,
&pszNTLMDomain
);
if (FAILED(hr)) {
hr = LdapCrackUserDNtoNTLMUser(
dn,
&pszNTLMUser,
&pszNTLMDomain
);
BAIL_ON_FAILURE(hr);
}
//
// If the domain name is NULL and enforceMutualAuth is false,
// then we need to throw in the defaultDomainName. This will
// be needed subsequently for the LogonUser call.
//
if (!fEnforceMutualAuth && !pszNTLMDomain) {
//
// Call GetDefaultServer.
//
pszDefaultServer = (LPWSTR) AllocADsMem(sizeof(WCHAR) * MAX_PATH);
if (!pszDefaultServer) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
pszNTLMDomain = (LPWSTR) AllocADsMem(sizeof(WCHAR) * MAX_PATH);
if (!pszNTLMDomain) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
hr = GetDefaultServer(
-1, // this will use the default ldap port
FALSE,
pszNTLMDomain,
pszDefaultServer,
TRUE
);
BAIL_ON_FAILURE(hr);
}
pAuthI->User = (PWCHAR)pszNTLMUser;
pAuthI->UserLength = (pszNTLMUser == NULL)? 0: wcslen(pszNTLMUser);
pAuthI->Domain = (PWCHAR)pszNTLMDomain;
pAuthI->DomainLength = (pszNTLMDomain == NULL)? 0: wcslen(pszNTLMDomain);
pAuthI->Password = (PWCHAR)passwd;
pAuthI->PasswordLength = (passwd == NULL)? 0: wcslen(passwd);
pAuthI->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
error:
if (FAILED(hr)) {
//
// Free the strings
//
if (pszNTLMUser) {
FreeADsStr(pszNTLMUser);
}
if (pszNTLMDomain) {
FreeADsStr(pszNTLMDomain);
}
if (passwd) {
FreeADsStr(passwd);
}
}
if (pADsPrivObjectOptions) {
pADsPrivObjectOptions->Release();
}
//
// Always free the dn
//
if (dn) {
FreeADsStr(dn);
}
if (pszDefaultServer) {
FreeADsMem(pszDefaultServer);
}
RRETURN(hr);
}
#endif
// Class CLDAPUser
STDMETHODIMP
CLDAPUser::get_AccountDisabled(THIS_ VARIANT_BOOL FAR* retval)
{
if ( retval == NULL )
RRETURN( E_ADS_BAD_PARAMETER );
LONG lUserAcctControl;
HRESULT hr = get_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
&lUserAcctControl );
if ( SUCCEEDED(hr))
*retval = lUserAcctControl & UF_ACCOUNTDISABLE ?
VARIANT_TRUE : VARIANT_FALSE;
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::put_AccountDisabled(THIS_ VARIANT_BOOL fAccountDisabled)
{
LONG lUserAcctControl;
HRESULT hr = get_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
&lUserAcctControl );
if ( SUCCEEDED(hr))
{
if ( fAccountDisabled )
lUserAcctControl |= UF_ACCOUNTDISABLE;
else
lUserAcctControl &= ~UF_ACCOUNTDISABLE;
hr = put_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
lUserAcctControl );
}
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::get_AccountExpirationDate(THIS_ DATE FAR* retval)
{
GET_PROPERTY_FILETIME((IADsUser *)this, AccountExpirationDate);
}
STDMETHODIMP
CLDAPUser::put_AccountExpirationDate(THIS_ DATE daAccountExpirationDate)
{
PUT_PROPERTY_FILETIME((IADsUser *)this, AccountExpirationDate);
}
STDMETHODIMP
CLDAPUser::get_GraceLoginsAllowed(THIS_ long FAR* retval)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::put_GraceLoginsAllowed(THIS_ long lGraceLoginsAllowed)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::get_GraceLoginsRemaining(THIS_ long FAR* retval)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::put_GraceLoginsRemaining(THIS_ long lGraceLoginsRemaining)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::get_IsAccountLocked(THIS_ VARIANT_BOOL FAR* retval)
{
HRESULT hr = S_OK;
VARIANT var;
IADsLargeInteger *pLargeInt = NULL;
LONG LowPart, HighPart;
if ( retval == NULL )
RRETURN( E_ADS_BAD_PARAMETER );
VariantInit(&var);
hr = _pADs->Get(TEXT("lockoutTime"), &var);
if (SUCCEEDED(hr)) {
//
// There's a lockoutTime, we need to determine
// if it equals 0 (== not locked-out).
//
ADsAssert(V_VT(&var) == VT_DISPATCH);
if (V_VT(&var) != VT_DISPATCH) {
BAIL_ON_FAILURE(hr = E_FAIL);
}
hr = V_DISPATCH(&var)->QueryInterface(IID_IADsLargeInteger,
reinterpret_cast<void**>(&pLargeInt)
);
BAIL_ON_FAILURE(hr);
hr = pLargeInt->get_LowPart(&LowPart);
BAIL_ON_FAILURE(hr);
hr = pLargeInt->get_HighPart(&HighPart);
BAIL_ON_FAILURE(hr);
if ( (LowPart != 0) || (HighPart != 0) ) {
*retval = VARIANT_TRUE;
}
else {
*retval = VARIANT_FALSE;
}
}
else if (hr == E_ADS_PROPERTY_NOT_FOUND) {
//
// If there's no lockoutTime, the account is not
// locked-out.
//
*retval = VARIANT_FALSE;
hr = S_OK;
}
else {
BAIL_ON_FAILURE(hr);
}
error:
if (pLargeInt) {
pLargeInt->Release();
}
VariantClear(&var);
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::put_IsAccountLocked(THIS_ VARIANT_BOOL fIsAccountLocked)
{
HRESULT hr = S_OK;
if (fIsAccountLocked) {
//
// You cannot set an account to a locked state.
//
RRETURN(E_ADS_BAD_PARAMETER);
}
hr = put_LONG_Property( (IADsUser *)this,
TEXT("lockoutTime"),
0 );
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::get_LoginHours(THIS_ VARIANT FAR* retval)
{
GET_PROPERTY_VARIANT((IADsUser *)this, LoginHours);
}
STDMETHODIMP
CLDAPUser::put_LoginHours(THIS_ VARIANT vLoginHours)
{
PUT_PROPERTY_VARIANT((IADsUser *)this, LoginHours);
}
STDMETHODIMP
CLDAPUser::get_LoginWorkstations(THIS_ VARIANT FAR* retval)
{
GET_PROPERTY_BSTRARRAY((IADsUser *)this,LoginWorkstations);
}
STDMETHODIMP
CLDAPUser::put_LoginWorkstations(THIS_ VARIANT vLoginWorkstations)
{
PUT_PROPERTY_BSTRARRAY((IADsUser *)this,LoginWorkstations);
}
STDMETHODIMP
CLDAPUser::get_MaxLogins(THIS_ long FAR* retval)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::put_MaxLogins(THIS_ long lMaxLogins)
{
RRETURN(E_ADS_PROPERTY_NOT_SUPPORTED);
}
STDMETHODIMP
CLDAPUser::get_MaxStorage(THIS_ long FAR* retval)
{
GET_PROPERTY_LONG((IADsUser *)this, MaxStorage);
}
STDMETHODIMP
CLDAPUser::put_MaxStorage(THIS_ long lMaxStorage)
{
PUT_PROPERTY_LONG((IADsUser *)this, MaxStorage);
}
STDMETHODIMP
CLDAPUser::get_PasswordExpirationDate(THIS_ DATE FAR* retval)
{
GET_PROPERTY_DATE((IADsUser *)this, PasswordExpirationDate);
}
STDMETHODIMP
CLDAPUser::put_PasswordExpirationDate(THIS_ DATE daPasswordExpirationDate)
{
PUT_PROPERTY_DATE((IADsUser *)this, PasswordExpirationDate);
}
STDMETHODIMP
CLDAPUser::get_PasswordRequired(THIS_ VARIANT_BOOL FAR* retval)
{
if ( retval == NULL )
RRETURN( E_ADS_BAD_PARAMETER );
LONG lUserAcctControl;
HRESULT hr = get_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
&lUserAcctControl );
if ( SUCCEEDED(hr))
*retval = lUserAcctControl & UF_PASSWD_NOTREQD ?
VARIANT_FALSE: VARIANT_TRUE;
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::put_PasswordRequired(THIS_ VARIANT_BOOL fPasswordRequired)
{
LONG lUserAcctControl;
HRESULT hr = get_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
&lUserAcctControl );
if ( SUCCEEDED(hr))
{
if ( fPasswordRequired )
lUserAcctControl &= ~UF_PASSWD_NOTREQD;
else
lUserAcctControl |= UF_PASSWD_NOTREQD;
hr = put_LONG_Property( (IADsUser *)this,
TEXT("userAccountControl"),
lUserAcctControl );
}
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::get_PasswordMinimumLength(THIS_ LONG FAR* retval)
{
GET_PROPERTY_LONG((IADsUser *)this, PasswordMinimumLength);
}
STDMETHODIMP
CLDAPUser::put_PasswordMinimumLength(THIS_ LONG lPasswordMinimumLength)
{
PUT_PROPERTY_LONG((IADsUser *)this, PasswordMinimumLength);
}
STDMETHODIMP
CLDAPUser::get_RequireUniquePassword(THIS_ VARIANT_BOOL FAR* retval)
{
GET_PROPERTY_VARIANT_BOOL((IADsUser *)this, RequireUniquePassword);
}
STDMETHODIMP
CLDAPUser::put_RequireUniquePassword(THIS_ VARIANT_BOOL fRequireUniquePassword)
{
PUT_PROPERTY_VARIANT_BOOL((IADsUser *)this, RequireUniquePassword);
}
BOOLEAN
_cdecl ServerCertCallback(
PLDAP Connection,
PCCERT_CONTEXT pServerCert
)
{
//
// After the secure connection is established, this function is called by
// LDAP. This gives the client an opportunity to verify the server cert.
// If, for some reason, the client doesn't approve it, it should return FALSE
// and the connection will be terminated. Else, return TRUE
//
fprintf( stderr, "Server cert callback has been called...\n" );
//
// Use some way to verify the server certificate.
//
return TRUE;
}
STDMETHODIMP
CLDAPUser::SetPassword(THIS_ BSTR bstrNewPassword)
{
HRESULT hr = E_FAIL;
BOOLEAN bUseLDAP = FALSE;
LPWSTR pszServer = NULL;
LPWSTR pszHostName = NULL;
DWORD dwLen = 0;
int err = 0;
BSTR bstrADsPath = NULL;
LPWSTR szServerSSL = NULL;
LPWSTR szDn = NULL;
DWORD dwPortSSL = 0;
PADSLDP pAdsLdpSSL = NULL;
IADsObjOptPrivate * pADsPrivObjectOptions = NULL;
PADSLDP pAdsLdp = NULL;
LDAPMessage *pMsgResult = NULL;
LDAPMessage *pMsgEntry = NULL;
LDAP *pLdapCurrent = NULL;
LPWSTR Attributes[] = {L"objectClass", NULL};
VARIANT varSamAccount;
DWORD dwServerPwdSupport = SERVER_STATUS_UNKNOWN;
LPWSTR pszHostDomainName = NULL;
SEC_WINNT_AUTH_IDENTITY AuthI;
BOOLEAN fPasswordSet = FALSE;
LPWSTR pszTempPwd = NULL;
ULONG ulFlags = 0;
VARIANT varGetInfoEx;
BOOL fCachePrimed = FALSE;
BOOL fImpersonating = FALSE;
HANDLE hUserToken = INVALID_HANDLE_VALUE;
//
// Init params we will need to free later.
//
AuthI.User = NULL;
AuthI.Domain = NULL;
AuthI.Password = NULL;
VariantInit(&varSamAccount);
VariantInit(&varGetInfoEx);
//
// Get the Ldap path of the user object
//
hr = _pADs->get_ADsPath( &bstrADsPath );
BAIL_ON_FAILURE(hr);
hr = BuildLDAPPathFromADsPath2(
bstrADsPath,
&szServerSSL,
&szDn,
&dwPortSSL
);
BAIL_ON_FAILURE(hr);
//
// Now do an LDAP Search with Referrals and get the handle to success
// connection. This is where we can find the server the referred object
// resides on
//
hr = _pADs->QueryInterface(
IID_IADsObjOptPrivate,
(void **)&pADsPrivObjectOptions
);
BAIL_ON_FAILURE(hr);
hr = pADsPrivObjectOptions->GetOption (
LDAP_SERVER,
(void*)&pszHostName
);
BAIL_ON_FAILURE(hr);
//
// additional lengh 3 is for '\0' and "\\\\"
//
dwLen = STRING_LENGTH(pszHostName) + 3;
pszServer = (LPWSTR) AllocADsMem( dwLen * sizeof(WCHAR) );
if (!pszServer) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
wcscpy(pszServer,L"\\\\");
wcscat(pszServer, pszHostName);
dwServerPwdSupport = ReadServerSupportsSSL(pszHostName);
if (dwServerPwdSupport ==
( SERVER_STATUS_UNKNOWN
| SERVER_DOES_NOT_SUPPORT_SSL
| SERVER_DOES_NOT_SUPPORT_NETUSER
| SERVER_DOES_NOT_SUPPORT_KERBEROS )
) {
//
// All flags are set, we will reset and rebuild cache
//
UpdateServerSSLSupportStatus(
pszHostName,
SERVER_STATUS_UNKNOWN
);
dwServerPwdSupport = SERVER_STATUS_UNKNOWN;
}
if (dwServerPwdSupport == SERVER_STATUS_UNKNOWN
|| !(dwServerPwdSupport & SERVER_DOES_NOT_SUPPORT_SSL)) {
//
// Try to establish SSL connection for this Password Operation
//
hr = LdapOpenObject(
pszHostName,
szDn,
&pAdsLdpSSL,
_Credentials,
636
);
if (SUCCEEDED(hr)) {
int retval;
SecPkgContext_ConnectionInfo sslattr;
retval = ldap_get_option( pAdsLdpSSL->LdapHandle, LDAP_OPT_SSL_INFO, &sslattr );
if (retval == LDAP_SUCCESS) {
//
// If Channel is secure enough, enable LDAP Password Change
//
if (sslattr.dwCipherStrength >= 128) {
bUseLDAP = TRUE;
}
}
}
//
// Update the SSL support if appropriate
//
if (dwServerPwdSupport == SERVER_STATUS_UNKNOWN
|| !bUseLDAP) {
//
// Set the server does not support ssl bit if necessary
//
UpdateServerSSLSupportStatus(
pszHostName,
bUseLDAP ?
dwServerPwdSupport :
dwServerPwdSupport |= SERVER_DOES_NOT_SUPPORT_SSL
);
}
}
if (bUseLDAP) {
//
// LDAP Password Set
//
PLDAPModW prgMod[2];
LDAPModW ModReplace;
struct berval* rgBerVal[2];
struct berval BerVal;
int ipwdLen;
prgMod[0] = &ModReplace;
prgMod[1] = NULL;
ModReplace.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
ModReplace.mod_type = L"unicodePwd";
ModReplace.mod_bvalues = rgBerVal;
rgBerVal[0] = &BerVal;
rgBerVal[1] = NULL;
//
// 2 extra for "" to put the password in.
//
if (bstrNewPassword) {
ipwdLen = (wcslen(bstrNewPassword) + 2) * sizeof(WCHAR);
}
else {
ipwdLen = 2 * sizeof(WCHAR);
}
//
// Add 1 for the \0.
//
pszTempPwd = (LPWSTR) AllocADsMem(ipwdLen + sizeof(WCHAR));
if (!pszTempPwd) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
wcscpy(pszTempPwd, L"\"");
if (bstrNewPassword) {
wcscat(pszTempPwd, bstrNewPassword);
}
wcscat(pszTempPwd, L"\"");
BerVal.bv_len = ipwdLen;
BerVal.bv_val = (char*)pszTempPwd;
hr = LdapModifyS(
pAdsLdpSSL,
szDn,
prgMod
);
BAIL_ON_FAILURE(hr);
//
// Set flag so that we do not try any other methods.
//
fPasswordSet = TRUE;
}
//
// Try kerberos setpassword if applicable
//
#if (!defined(WIN95))
//
// Only valid on Win2k
//
if (!fPasswordSet) {
//
// If we cached the server as not supporting Kerberos, most likely it
// was because we were not mutually authenticated. Do a quick check to
// see if that has changed, so that we can update our cached information
// if necessary.
//
if (dwServerPwdSupport & SERVER_DOES_NOT_SUPPORT_KERBEROS) {
hr = pADsPrivObjectOptions->GetOption (
LDAP_MUTUAL_AUTH_STATUS,
(void *) &ulFlags
);
BAIL_ON_FAILURE(hr);
if ((ulFlags & KERB_SUPPORT_FLAGS)) {
UpdateServerSSLSupportStatus(
pszHostName,
dwServerPwdSupport &= (~SERVER_DOES_NOT_SUPPORT_KERBEROS)
);
}
}
if (!(dwServerPwdSupport & SERVER_DOES_NOT_SUPPORT_KERBEROS)) {
//
// Kerberos set password
//
CredHandle secCredHandle = {0};
SECURITY_STATUS SecStatus;
DWORD dwStatus = 0;
LPWSTR pszSamAccountArr[] = {L"sAMAccountName"};
if (!fCachePrimed) {
hr = ADsBuildVarArrayStr( pszSamAccountArr, 1, &varGetInfoEx );
BAIL_ON_FAILURE(hr);
hr = _pADs->GetInfoEx(varGetInfoEx, 0);
BAIL_ON_FAILURE(hr);
fCachePrimed = TRUE;
}
hr = _pADs->Get(L"sAMAccountName", &varSamAccount);
BAIL_ON_FAILURE(hr);
//
// The AuthIdentity structure is ueful down the road.
// This routine will fail if we were not bound using
// kerberos to the server.
//
hr = GetAuthIdentityForCaller(
_Credentials,
_pADs,
&AuthI,
TRUE // enforce mutual auth.
);
if (FAILED(hr)) {
UpdateServerSSLSupportStatus(
pszHostName,
dwServerPwdSupport |= SERVER_DOES_NOT_SUPPORT_KERBEROS
);
}
else {
//
// Kerb really needs this handle.
//
hr = ConvertAuthIdentityToCredHandle(
AuthI,
&secCredHandle
);
if (FAILED(hr)) {
UpdateServerSSLSupportStatus(
pszHostName,
dwServerPwdSupport |= SERVER_DOES_NOT_SUPPORT_KERBEROS
);
}
if (SUCCEEDED(hr)) {
//
// Get the domain dns name for the user
//
hr = GetDomainDNSNameFromHost(
pszHostName,
AuthI,
_Credentials,
dwPortSSL,
&pszHostDomainName
);
if (SUCCEEDED(hr)) {
dwStatus = KerbSetPasswordUserEx(
pszHostDomainName,
V_BSTR(&varSamAccount),
bstrNewPassword,
&secCredHandle,
pszHostName
);
if (dwStatus) {
//
// We should have got this to come in here.
//
hr = HRESULT_FROM_WIN32(ERROR_LOGON_FAILURE);
}
else {
fPasswordSet = TRUE;
}
} // if domain dns name get succeeded.
FreeCredentialsHandleWrapper(&secCredHandle);
} // if GetCredentialsForCaller succeeded.
} // if we could get authidentity succesfully
} // if server supports kerberos
} // if password not set.
#endif
//
// At this point server status cannot be unknown, it
// will atleast have info about ssl support.
//
if (!fPasswordSet) {
if (!(dwServerPwdSupport & SERVER_DOES_NOT_SUPPORT_NETUSER)) {
//
// Password Set using NET APIs
//
NET_API_STATUS nasStatus;
DWORD dwParmErr = 0;
LPWSTR pszSamAccountArr[] = {L"sAMAccountName"};
//
// Get SamAccountName
//
VariantClear(&varSamAccount);
VariantClear(&varGetInfoEx);
if (!fCachePrimed) {
hr = ADsBuildVarArrayStr( pszSamAccountArr, 1, &varGetInfoEx );
BAIL_ON_FAILURE(hr);
hr = _pADs->GetInfoEx(varGetInfoEx, 0);
BAIL_ON_FAILURE(hr);
fCachePrimed = TRUE;
}
hr = _pADs->Get(L"sAMAccountName", &varSamAccount);
BAIL_ON_FAILURE(hr);
//
// Set the password
//
USER_INFO_1003 lpUserInfo1003 ;
lpUserInfo1003.usri1003_password = bstrNewPassword;
#ifndef Win95
//
// At this point if the user credentials are non NULL,
// we want to impersonate the user and then make this call.
// This will make sure the NetUserSetInfo call is made in the
// correct context.
//
if (!_Credentials.IsNullCredentials()) {
//
// Need to get the userName and password in the format
// usable by the logonUser call.
//
if ((AuthI.User == NULL)
&& (AuthI.Domain == NULL)
&& (AuthI.Password == NULL)
) {
//
// Get teh Auth identity struct populate if necessary.
//
hr = GetAuthIdentityForCaller(
_Credentials,
_pADs,
&AuthI,
FALSE
);
}
BAIL_ON_FAILURE(hr);
//
// Note that if this code is backported, then we might
// need to change LOGON32_PROVIDER_WINNT50 to
// LOGON32_PROVIDER_DEFAULT as NT4 and below will support
// only that option. Also note that Win2k and below, do not
// allow all accounts to impersonate.
//
if (LogonUser(
AuthI.User,
AuthI.Domain,
AuthI.Password,
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_WINNT50,
&hUserToken
)
) {
//
// Call succeeded so we should use this context.
//
if (ImpersonateLoggedOnUser(hUserToken)) {
fImpersonating = TRUE;
}
}
if (!fImpersonating) {
hr = HRESULT_FROM_WIN32(GetLastError());
}
BAIL_ON_FAILURE(hr);
} // if credentials are valid.
#endif
nasStatus = NetUserSetInfo(
pszServer,
V_BSTR(&varSamAccount),
1003,
(LPBYTE)&lpUserInfo1003,
&dwParmErr
);
#ifndef Win95
if (fImpersonating) {
if (RevertToSelf()) {
fImpersonating = FALSE;
}
else {
ADsAssert(!"Revert to self failed.");
BAIL_ON_FAILURE(hr = HRESULT_FROM_WIN32(GetLastError()));
}
}
#endif
if ( nasStatus == NERR_UserNotFound ) { // User not created yet
hr = E_ADS_OBJECT_UNBOUND;
BAIL_ON_FAILURE(hr);
}
hr = HRESULT_FROM_WIN32(nasStatus);
if (FAILED(hr) && (nasStatus == ERROR_LOGON_FAILURE)) {
//
// Was failure and ERROR_LOGON_FAILURE
//
UpdateServerSSLSupportStatus(
pszHostName,
dwServerPwdSupport |= SERVER_DOES_NOT_SUPPORT_NETUSER
);
//
// Need to free the variant as we will re-read in kerb
//
VariantClear(&varSamAccount);
}
else {
//
// password set succeed
//
fPasswordSet = TRUE;
}
}
} // if Password not set.
error:
if (bstrADsPath) {
ADsFreeString(bstrADsPath);
}
if (szServerSSL) {
FreeADsStr(szServerSSL);
}
if (szDn) {
FreeADsStr(szDn);
}
if (pAdsLdpSSL) {
LdapCloseObject(pAdsLdpSSL);
}
if (pADsPrivObjectOptions) {
pADsPrivObjectOptions->Release();
}
if (pMsgResult) {
LdapMsgFree(pMsgResult);
}
if (pszHostDomainName) {
FreeADsStr(pszHostDomainName);
}
if (AuthI.User) {
FreeADsStr(AuthI.User);
}
if (AuthI.Domain) {
FreeADsStr(AuthI.Domain);
}
if (AuthI.Password) {
FreeADsStr(AuthI.Password);
}
if (pszTempPwd) {
FreeADsMem(pszTempPwd);
}
if (pszHostName) {
FreeADsStr(pszHostName);
}
if (pszServer) {
FreeADsMem(pszServer);
}
#ifndef Win95
if (fImpersonating) {
//
// Try and call revert to self again
//
RevertToSelf();
}
#endif
if (hUserToken != INVALID_HANDLE_VALUE ) {
CloseHandle(hUserToken);
hUserToken = NULL;
}
VariantClear(&varSamAccount);
VariantClear(&varGetInfoEx);
RRETURN(hr);
}
STDMETHODIMP
CLDAPUser::ChangePassword(THIS_ BSTR bstrOldPassword, BSTR bstrNewPassword)
{
HRESULT hr = S_OK;
BOOLEAN bUseLDAP = FALSE;
LPWSTR pszServer = NULL;
LPWSTR pszHostName = NULL;
DWORD dwLen = 0;
int err = 0;
BSTR bstrADsPath = NULL;
LPWSTR szServerSSL = NULL;
LPWSTR szDn = NULL;
DWORD dwPortSSL = 0;
PADSLDP pAdsLdpSSL = NULL;
IADsObjOptPrivate * pADsPrivObjectOptions = NULL;
PADSLDP pAdsLdp = NULL;
LDAPMessage *pMsgResult = NULL;
LDAPMessage *pMsgEntry = NULL;
LDAP *pLdapCurrent = NULL;
LPWSTR Attributes[] = {L"objectClass", NULL};
VARIANT varSamAccount;
DWORD dwServerSSLSupport = 0;
LPWSTR pszNewPassword = NULL;
LPWSTR pszOldPassword = NULL;
VARIANT varGetInfoEx;
SEC_WINNT_AUTH_IDENTITY AuthI;
BOOL fImpersonating = FALSE;
HANDLE hUserToken = INVALID_HANDLE_VALUE;
VariantInit(&varSamAccount);
VariantInit(&varGetInfoEx);
memset(&AuthI, 0, sizeof(SEC_WINNT_AUTH_IDENTITY));
//
// Get the Ldap path of the user object
//
hr = _pADs->get_ADsPath( &bstrADsPath );
BAIL_ON_FAILURE(hr);
hr = BuildLDAPPathFromADsPath2(
bstrADsPath,
&szServerSSL,
&szDn,
&dwPortSSL
);
BAIL_ON_FAILURE(hr);
//
// Now do an LDAP Search with Referrals and get the handle to success
// connection. This is where we can find the server the referred object
// resides on
//
hr = _pADs->QueryInterface(
IID_IADsObjOptPrivate,
(void **)&pADsPrivObjectOptions
);
BAIL_ON_FAILURE(hr);
hr = pADsPrivObjectOptions->GetOption (
LDAP_SERVER,
(void *)&pszHostName
);
BAIL_ON_FAILURE(hr);
//
// additional length 3 is for '\0' and "\\\\"
//
dwLen = STRING_LENGTH(pszHostName) + 3;
pszServer = (LPWSTR) AllocADsMem( dwLen * sizeof(WCHAR) );
if (!pszServer) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
wcscpy(pszServer,L"\\\\");
wcscat(pszServer, pszHostName);
dwServerSSLSupport = ReadServerSupportsSSL(pszHostName);
if (dwServerSSLSupport == SERVER_STATUS_UNKNOWN
|| !(dwServerSSLSupport & SERVER_DOES_NOT_SUPPORT_SSL)) {
//
// Try to establish SSL connection for this Password Operation
//
hr = LdapOpenObject(
pszHostName,
szDn,
&pAdsLdpSSL,
_Credentials,
636
);
if (SUCCEEDED(hr)) {
int retval;
SecPkgContext_ConnectionInfo sslattr;
retval = ldap_get_option( pAdsLdpSSL->LdapHandle, LDAP_OPT_SSL_INFO, &sslattr );
if (retval == LDAP_SUCCESS) {
//
// If Channel is secure enough, enable LDAP Password Change
//
if (sslattr.dwCipherStrength >= 128) {
bUseLDAP = TRUE;
}
}
}
//
// Update the SSL support if appropriate
//
if (dwServerSSLSupport == SERVER_STATUS_UNKNOWN
|| !bUseLDAP) {
UpdateServerSSLSupportStatus(
pszHostName,
bUseLDAP ?
dwServerSSLSupport :
dwServerSSLSupport |= SERVER_DOES_NOT_SUPPORT_SSL
);
}
}
if (bUseLDAP) {
//
// LDAP Password Set
//
PLDAPModW prgMod[3];
LDAPModW ModDelete;
LDAPModW ModAdd;
int iOldPwdLen, iNewPwdLen;
struct berval* rgBerVal[2];
struct berval* rgBerVal2[2];
struct berval BerVal;
struct berval BerVal2;
prgMod[0] = &ModDelete;
prgMod[1] = &ModAdd;
prgMod[2] = NULL;
ModDelete.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES;
ModDelete.mod_type = L"unicodePwd";
ModDelete.mod_bvalues = rgBerVal;
rgBerVal[0] = &BerVal;
rgBerVal[1] = NULL;
//
// Put old pwd in quotes.
//
if (bstrOldPassword) {
iOldPwdLen = (wcslen(bstrOldPassword) + 2) * sizeof(WCHAR);
}
else {
iOldPwdLen = 2 * sizeof(WCHAR);
}
pszOldPassword = (LPWSTR) AllocADsMem((iOldPwdLen+1) * sizeof(WCHAR));
if (!pszOldPassword) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
wcscpy(pszOldPassword, L"\"");
if (bstrOldPassword) {
wcscat(pszOldPassword, bstrOldPassword);
}
wcscat(pszOldPassword, L"\"");
BerVal.bv_len = iOldPwdLen;
BerVal.bv_val = (char*)pszOldPassword;
ModAdd.mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES;
ModAdd.mod_type = L"unicodePwd";
ModAdd.mod_bvalues = rgBerVal2;
rgBerVal2[0] = &BerVal2;
rgBerVal2[1] = NULL;
//
// Put new password in ""
//
if (bstrNewPassword) {
iNewPwdLen = (wcslen(bstrNewPassword) + 2) * sizeof(WCHAR);
}
else {
iNewPwdLen = 2 * sizeof(WCHAR);
}
pszNewPassword = (LPWSTR) AllocADsMem(iNewPwdLen + sizeof(WCHAR));
if (!pszNewPassword) {
BAIL_ON_FAILURE(hr = E_FAIL);
}
wcscpy(pszNewPassword, L"\"");
if (bstrNewPassword) {
wcscat(pszNewPassword, bstrNewPassword);
}
wcscat(pszNewPassword, L"\"");
BerVal2.bv_len = iNewPwdLen;
BerVal2.bv_val = (char*)pszNewPassword;
hr = LdapModifyS(
pAdsLdpSSL,
szDn,
prgMod
);
BAIL_ON_FAILURE(hr);
}
else {
//
// Password Set using NET APIs
//
NET_API_STATUS nasStatus;
DWORD dwParmErr = 0;
LPWSTR pszSamAccountArr[] = {L"sAMAccountName"};
//
// Get SamAccountName
//
hr = ADsBuildVarArrayStr( pszSamAccountArr, 1, &varGetInfoEx );
BAIL_ON_FAILURE(hr);
hr = _pADs->GetInfoEx(varGetInfoEx, 0);
BAIL_ON_FAILURE(hr);
hr = _pADs->Get(L"sAMAccountName", &varSamAccount);
BAIL_ON_FAILURE(hr);
#ifndef Win95
//
// At this point if the user credentials are non NULL,
// we want to impersonate the user and then make this call.
// This will make sure the NetUserChangePassword call is made in the
// correct context.
//
if (!_Credentials.IsNullCredentials()) {
//
// Need to get the userName and password in the format
// usable by the logonUser call.
//
hr = GetAuthIdentityForCaller(
_Credentials,
_pADs,
&AuthI,
FALSE
);
if SUCCEEDED(hr) {
//
// Note that if this code is backported, then we might
// need to change LOGON32_PROVIDER_WINNT50 to
// LOGON32_PROVIDER_DEFAULT as NT4 and below will support
// only that option. Also note that Win2k and below, do not
// allow all accounts to impersonate.
//
if (LogonUser(
AuthI.User,
AuthI.Domain,
AuthI.Password,
LOGON32_LOGON_NEW_CREDENTIALS,
LOGON32_PROVIDER_DEFAULT,
&hUserToken
)
) {
//
// Call succeeded so we should use this context.
//
if (ImpersonateLoggedOnUser(hUserToken)) {
fImpersonating = TRUE;
}
}
} // if we could successfully get the auth ident structure.
//
// We will continue to make the ChangePassword call even if
// we could not impersonate successfully.
//
} // if credentials are valid.
#endif
//
// Do the actual change password
//
nasStatus = NetUserChangePassword(
pszServer,
V_BSTR(&varSamAccount),
bstrOldPassword,
bstrNewPassword
);
#ifndef Win95
if (fImpersonating) {
if (RevertToSelf()) {
fImpersonating = FALSE;
}
else {
ADsAssert(!"Revert to self failed.");
BAIL_ON_FAILURE(hr = HRESULT_FROM_WIN32(GetLastError()));
}
}
#endif
if ( nasStatus == NERR_UserNotFound ) // User not created yet
{
hr = E_ADS_OBJECT_UNBOUND;
BAIL_ON_FAILURE(hr);
}
hr = HRESULT_FROM_WIN32(nasStatus);
BAIL_ON_FAILURE(hr);
}
error:
if (bstrADsPath) {
ADsFreeString(bstrADsPath);
}
if (szServerSSL) {
FreeADsStr(szServerSSL);
}
if (szDn) {
FreeADsStr(szDn);
}
if (pAdsLdpSSL) {
LdapCloseObject(pAdsLdpSSL);
}
if (pADsPrivObjectOptions) {
pADsPrivObjectOptions->Release();
}
if (pMsgResult) {
LdapMsgFree(pMsgResult);
}
if (pszOldPassword) {
FreeADsMem(pszOldPassword);
}
if (pszNewPassword) {
FreeADsMem(pszNewPassword);
}
if (AuthI.User) {
FreeADsStr(AuthI.User);
}
if (AuthI.Domain) {
FreeADsStr(AuthI.Domain);
}
if (AuthI.Password) {
FreeADsStr(AuthI.Password);
}
if (pszHostName) {
FreeADsStr(pszHostName);
}
if (pszServer) {
FreeADsMem(pszServer);
}
#ifndef Win95
if (fImpersonating) {
//
// Try and call revert to self again
//
RevertToSelf();
}
#endif
if (hUserToken != INVALID_HANDLE_VALUE ) {
CloseHandle(hUserToken);
hUserToken = NULL;
}
VariantClear(&varSamAccount);
VariantClear(&varGetInfoEx);
RRETURN(hr);
}
//+---------------------------------------------------------------------------
//
// GetDomainDNSNameFromHost
//
// Given the domain dns name for a host, we need to get hold of the
// dns name for the domain.
//
// Arguments:
// [szHostName] - name of server.
// [Credentials] - Credentials to use for bind.
// [dwPort] - Port to connect to server on.
// [ppszHostName] - ptr to string for retval.
//
// Returns:
// S_OK - If operation succeeds.
// E_* - For other cases.
//
//----------------------------------------------------------------------------
HRESULT
GetDomainDNSNameFromHost(
LPWSTR szHostName,
SEC_WINNT_AUTH_IDENTITY& AuthI,
CCredentials& Credentials,
DWORD dwPort,
LPWSTR * ppszHostName
)
{
HRESULT hr = S_OK;
PADSLDP ld = NULL;
LPTSTR *aValuesNamingContext = NULL;
IADsNameTranslate *pNameTranslate = NULL;
BSTR bstrName = NULL;
int nCount = 0;
//
// Bind to the ROOTDSE of the server.
//
hr = LdapOpenObject(
szHostName,
NULL, // the DN.
&ld,
Credentials,
dwPort
);
BAIL_ON_FAILURE(hr);
//
// Now get the defaultNamingContext
//
hr = LdapReadAttributeFast(
ld,
NULL, // the DN.
LDAP_OPATT_DEFAULT_NAMING_CONTEXT_W,
&aValuesNamingContext,
&nCount
);
//
// Verify we actuall got back at least one value
//
if (SUCCEEDED(hr) && (nCount < 1)) {
hr = HRESULT_FROM_WIN32(ERROR_DS_NO_ATTRIBUTE_OR_VALUE);
}
BAIL_ON_FAILURE(hr);
//
// Create nametran object
//
hr = CoCreateInstance(
CLSID_NameTranslate,
NULL,
CLSCTX_ALL,
IID_IADsNameTranslate,
(void **) &pNameTranslate
);
BAIL_ON_FAILURE(hr);
//
// Init with defaultNamingContext and get transalte
//
hr = pNameTranslate->InitEx(
ADS_NAME_INITTYPE_SERVER,
szHostName,
AuthI.User,
AuthI.Domain,
AuthI.Password
);
BAIL_ON_FAILURE(hr);
hr = pNameTranslate->Set(
ADS_NAME_TYPE_1779,
aValuesNamingContext[0]
);
BAIL_ON_FAILURE(hr);
hr = pNameTranslate->Get(
ADS_NAME_TYPE_CANONICAL,
&bstrName
);
BAIL_ON_FAILURE(hr);
if (!bstrName) {
BAIL_ON_FAILURE(hr = E_FAIL);
}
*ppszHostName = AllocADsStr(bstrName);
if (!*ppszHostName) {
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
}
//
// Null terminate one place ahead so we can get rid of /
//
(*ppszHostName)[wcslen(bstrName)-1] = L'\0';
error :
if (ld) {
LdapCloseObject(ld);
}
if (pNameTranslate) {
pNameTranslate->Release();
}
if (bstrName) {
SysFreeString(bstrName);
}
if (aValuesNamingContext) {
LdapValueFree(aValuesNamingContext);
}
RRETURN(hr);
}