// ServMigr.cpp : Implementation of CServMigr
#include "stdafx.h"
#include "ScmMigr.h"
#include "ServMigr.h"
#include "ErrDct.hpp"
#include "ResStr.h"
#include "Common.hpp"
#include "PWGen.hpp"
#include "EaLen.hpp"
#include "TReg.hpp"
#include "TxtSid.h"
#include "ARExt_i.c"
#include "LsaUtils.h"
#include "crypt.hxx"
#include "GetDcName.h"

#include <lm.h>
#include <dsgetdc.h>
#include <ntdsapi.h>
#include <Sddl.h>

#include "folders.h"
using namespace nsFolders;

//#import "\bin\McsVarSetMin.tlb" no_namespace, named_guids
//#import "\bin\DBManager.tlb" no_namespace, named_guids
//#import "\bin\McsDctWorkerObjects.tlb" no_namespace, named_guids

#import "VarSet.tlb" no_namespace, named_guids rename("property", "aproperty")
//#import "DBMgr.tlb" no_namespace, named_guids //already #imported in ServMigr.h
#import "WorkObj.tlb" no_namespace, named_guids

TErrorDct         err;
StringLoader      gString;

#define BLOCK_SIZE 160
#define BUFFER_SIZE 400

#define SvcAcctStatus_NotMigratedYet			0
#define SvcAcctStatus_DoNotUpdate			   1
#define SvcAcctStatus_Updated				      2
#define SvcAcctStatus_UpdateFailed			   4
#define SvcAcctStatus_NeverAllowUpdate       8

// these defines are for GetWellKnownSid
#define ADMINISTRATORS     1
#define SYSTEM             7

/////////////////////////////////////////////////////////////////////////////
// CServMigr

STDMETHODIMP CServMigr::ProcessUndo(/*[in]*/ IUnknown * pSource, /*[in]*/ IUnknown * pTarget, /*[in]*/ IUnknown * pMainSettings, /*[in, out]*/ IUnknown ** pPropToSet, /*[in,out]*/ EAMAccountStats* pStats)
{
   return E_NOTIMPL;
}

STDMETHODIMP CServMigr::PreProcessObject(/*[in]*/ IUnknown * pSource, /*[in]*/ IUnknown * pTarget, /*[in]*/ IUnknown * pMainSettings, /*[in, out]*/ IUnknown ** pPropToSet, /*[in,out]*/ EAMAccountStats* pStats)
{
   return S_OK;
}

STDMETHODIMP 
   CServMigr::ProcessObject(
      /*[in]*/ IUnknown     * pSource, 
      /*[in]*/ IUnknown     * pTarget, 
      /*[in]*/ IUnknown     * pMainSettings, 
      /*[in,out]*/IUnknown ** ppPropsToSet,
      /*[in,out]*/ EAMAccountStats* pStats
   )
{
    HRESULT                    hr = S_OK;
    WCHAR                      domAccount[500];
    WCHAR                      domTgtAccount[500];
    IVarSetPtr                 pVarSet(pMainSettings);
    IIManageDBPtr              pDB;
    _bstr_t                    logfile;
    _bstr_t                    srcComputer;
    _bstr_t                    tgtComputer;
    IVarSetPtr                 pData(CLSID_VarSet);
    IUnknown                 * pUnk = NULL;
    DWORD                      rc = 0;
    _bstr_t                    sIntraForest;
    BOOL                       bIntraForest = FALSE;
    USER_INFO_2              * uInfo = NULL;

    try { 
        logfile = pVarSet->get(GET_BSTR(DCTVS_Options_Logfile));

        if ( logfile.length() )
        {
            err.LogOpen(logfile,1);
        }

        pDB = pVarSet->get(GET_BSTR(DCTVS_DBManager));

        if ( pDB != NULL )
        {
            // Check to see if this account is referenced in the service accounts table
            m_strSourceDomain = pVarSet->get(GET_BSTR(DCTVS_Options_SourceDomain));
            m_strSourceDomainFlat = pVarSet->get(GET_BSTR(DCTVS_Options_SourceDomainFlat));
            m_strTargetDomain = pVarSet->get(GET_BSTR(DCTVS_Options_TargetDomain));
            m_strTargetDomainFlat = pVarSet->get(GET_BSTR(DCTVS_Options_TargetDomainFlat));
            m_strSourceSam = pVarSet->get(GET_BSTR(DCTVS_CopiedAccount_SourceSam));
            m_strTargetSam = pVarSet->get(GET_BSTR(DCTVS_CopiedAccount_TargetSam));
            srcComputer = pVarSet->get(GET_BSTR(DCTVS_Options_SourceServer));
            tgtComputer = pVarSet->get(GET_BSTR(DCTVS_Options_TargetServer));
            sIntraForest = pVarSet->get(GET_BSTR(DCTVS_Options_IsIntraforest));

            if ( ! UStrICmp((WCHAR*)sIntraForest,GET_STRING(IDS_YES)) )
            {
                // for intra-forest migration we are moving, not copying, so we don't need to update the password
                // Actually, it turns out that ChangeServiceConfig will not let us update just the service account
                // and not the passord, so we'll have to go ahead and change the password for the service ac
                //bIntraForest = TRUE;
            }
            //if the SAM account name has a " character in it, it cannot be a service
            //account, and therefore we leave
            if (wcschr((WCHAR*)m_strSourceSam, L'\"')) {
                return S_OK;
            }

            swprintf(domAccount,L"%s\\%s",(WCHAR*)m_strSourceDomainFlat,(WCHAR*)m_strSourceSam);
            swprintf(domTgtAccount,L"%s\\%s",(WCHAR*)m_strTargetDomainFlat,(WCHAR*)m_strTargetSam);
        }
    }
    catch (_com_error& ce) {
        hr = ce.Error();
        return hr;
    }
    catch (... )
    {
        return E_FAIL;
    }

    try { 
        hr = pData->QueryInterface(IID_IUnknown,(void**)&pUnk);

        if ( SUCCEEDED(hr) )
        {
            hr = pDB->raw_GetServiceAccount(_bstr_t(domAccount),&pUnk);
        }
    }
    catch (_com_error& ce) {

        if (pUnk)
            pUnk->Release();

        hr = ce.Error();
        return hr;
    }    
    catch ( ... )
    {
        if (pUnk)
            pUnk->Release();

        return E_FAIL;
    }

    try {
        if ( SUCCEEDED(hr) )
        {
            pData = pUnk;
            pUnk->Release();
            pUnk=NULL;
            // remove the password must change flag, if set
            DWORD                   parmErr = 0;
            WCHAR                   password[LEN_Password];
            long                    entries = pData->get("ServiceAccountEntries");

            if ( (entries != 0) && !bIntraForest ) // if we're moving the account, don't mess with its properties
            {
                //
                // Open password log file if it has not been already opened.
                //

                if (!m_bTriedToOpenFile)
                {
                    m_bTriedToOpenFile = true;

                    _bstr_t strPasswordFile = pVarSet->get(GET_BSTR(DCTVS_AccountOptions_PasswordFile));

                    if (m_passwordLog.LogOpen(strPasswordFile) == FALSE)
                    {
                        err.MsgWrite(ErrI, DCT_MSG_SERVICES_WILL_NOT_BE_UPDATED);

                        if (pStats != NULL)
                        {
                            pStats->errors.users++;
                        }
                    }
                }

                rc = NetUserGetInfo(tgtComputer,m_strTargetSam,2,(LPBYTE*)&uInfo);

                if ( ! rc )
                {
                    // generate a new, strong, 14 character password for this account, 
                    // and set the password to not expire
                    rc = EaPasswordGenerate(3,3,3,3,6,14,password,DIM(password));

                    if (!rc)
                    {
                        //
                        // Only set password to not expire if able
                        // to write password to password file.
                        //

                        if (m_passwordLog.IsOpen())
                        {
                            uInfo->usri2_flags |= (UF_DONT_EXPIRE_PASSWD);
                        }

                        uInfo->usri2_password = password;
                        rc = NetUserSetInfo(tgtComputer,m_strTargetSam,2,(LPBYTE)uInfo,&parmErr);

                        if ( ! rc )
                        {
                            if (m_passwordLog.IsOpen())
                            {
                                err.MsgWrite(0,DCT_MSG_REMOVED_PWDCHANGE_FLAG_S,(WCHAR*)m_strTargetSam);
                            }
                            err.MsgWrite(0,DCT_MSG_PWGENERATED_S,(WCHAR*)m_strTargetSam);
                            // write the password to the password log file and mark this account, so that the 
                            // SetPassword extension will not reset the password again.
                            pVarSet->put(GET_BSTR(DCTVS_CopiedAccount_DoNotUpdatePassword),m_strSourceSam);

                            //
                            // If password log is open then write password to file
                            // otherwise set error code so that services are not updated.
                            //

                            if (m_passwordLog.IsOpen())
                            {
                                m_passwordLog.MsgWrite(L"%ls,%ls",(WCHAR*)m_strTargetSam,password);
                            }
                            else
                            {
                                rc = ERROR_OPEN_FAILED;
                            }
                        }
                        else
                        {
                            if (pStats != NULL)
                                pStats->errors.users++;
                            err.SysMsgWrite(ErrE,rc,DCT_MSG_REMOVED_PWDCHANGE_FLAG_FAILED_SD,(WCHAR*)m_strTargetSam,rc);
                        }

                        uInfo->usri2_password = NULL;
                    }

                    NetApiBufferFree(uInfo);
                    uInfo = NULL;
                }
            }
            if (entries != 0 )
            {
                try { 
                    if ( ! rc )
                    {
                        WCHAR             strSID[200] = L"";
                        BYTE              sid[200];
                        WCHAR             sdomain[LEN_Domain];
                        SID_NAME_USE      snu;
                        DWORD             lenSid = DIM(sid);
                        DWORD             lenDomain = DIM(sdomain);
                        DWORD             lenStrSid = DIM(strSID);

                        if ( LookupAccountName(tgtComputer,m_strTargetSam,sid,&lenSid,sdomain,&lenDomain,&snu) )
                        {
                            if ( GetTextualSid(sid,strSID,&lenStrSid) )
                            {
                                // for each reference to the service account, update the SCM
                                // for intra-forest migration, don't update the password
                                if ( bIntraForest )
                                    UpdateSCMs(pData,domTgtAccount,NULL,strSID,pDB, pStats); 
                                else
                                    UpdateSCMs(pData,domTgtAccount,password,strSID,pDB, pStats);
                            }
                            else
                            {
                                if (pStats != NULL)
                                    pStats->errors.users++;
                                err.SysMsgWrite(ErrE,GetLastError(),DCT_MSG_CANNOT_FIND_ACCOUNT_SSD,m_strTargetSam,tgtComputer,GetLastError());
                            }
                        }
                        else
                        {
                            if (pStats != NULL)
                                pStats->errors.users++;
                            err.SysMsgWrite(ErrE,GetLastError(),DCT_MSG_CANNOT_FIND_ACCOUNT_SSD,m_strTargetSam,tgtComputer,GetLastError());
                        }
                    }
                }
                catch (_com_error& ce) {

                    hr = ce.Error();
                    return hr;
                }  
                catch(...)
                {
                    return E_FAIL;
                }
            }
        }
        else
        {
            if (pStats != NULL)
                pStats->errors.users++;
            err.SysMsgWrite(ErrE,E_FAIL,DCT_MSG_DB_OBJECT_CREATE_FAILED_D,E_FAIL);
        }

        err.LogClose();
    }
    catch (_com_error& ce) {

        if (pUnk)
            pUnk->Release();

        if (uInfo)
            NetApiBufferFree(uInfo);

        hr = ce.Error();
        return hr;
    }  
    catch (... )
    {
        if (pUnk)
            pUnk->Release();

        if (uInfo)
            NetApiBufferFree(uInfo);

        return E_FAIL;
    }

    return S_OK;
}

STDMETHODIMP CServMigr::get_sDesc(/*[out, retval]*/ BSTR *pVal)
{
   (*pVal) = SysAllocString(L"Updates SCM entries for services using migrated accounts.");
   return S_OK;
}

STDMETHODIMP CServMigr::put_sDesc(/*[in]*/ BSTR newVal)
{
   return E_NOTIMPL;
}

STDMETHODIMP CServMigr::get_sName(/*[out, retval]*/ BSTR *pVal)
{
   (*pVal) = SysAllocString(L"Generic Service Account Migration");
   return S_OK;
}

STDMETHODIMP CServMigr::put_sName(/*[in]*/ BSTR newVal)
{
   return E_NOTIMPL;
}

DWORD 
   CServMigr::DoUpdate(
      WCHAR const          * account,
      WCHAR const          * password,
      WCHAR const          * strSid,
      WCHAR const          * computer,
      WCHAR const          * service,
      BOOL                   bNeedToGrantLOS,
	  EAMAccountStats        *pStats
   )
{
    DWORD                     rc = 0;
    WCHAR   const           * ppassword = password;


    // if password is empty, set it to NULL
    if ( ppassword && ppassword[0] == 0 )  
    {
        ppassword = NULL;
    }
    else if ( !UStrCmp(password,L"NULL") )
    {
        ppassword = NULL;
    }
    // only try to update entries that we need to be updating
    // try to connect to the SCM on this machine

    SC_HANDLE          pScm = OpenSCManager(computer, NULL, SC_MANAGER_ALL_ACCESS );
    if ( pScm )
    {
        // grant the logon as a service right to the target account

        if ( bNeedToGrantLOS )
        {
            LSA_HANDLE hPolicy;
            NTSTATUS ntsStatus = OpenPolicy(
                const_cast<LPWSTR>(computer),
                POLICY_CREATE_ACCOUNT|POLICY_LOOKUP_NAMES,
                &hPolicy
            );
            rc = LsaNtStatusToWinError(ntsStatus);

            if (rc == ERROR_SUCCESS)
            {
                LSA_UNICODE_STRING lsausUserRights;
                InitLsaString(&lsausUserRights, _T("SeServiceLogonRight"));
                PSID pSid = SidFromString(strSid);

                if (pSid)
                {
                    ntsStatus = LsaAddAccountRights(hPolicy, pSid, &lsausUserRights, 1L);
                    rc = LsaNtStatusToWinError(ntsStatus);
                    FreeSid(pSid);
                }
                else
                {
                    rc = ERROR_OUTOFMEMORY;
                }

                LsaClose(hPolicy);
            }

            if ( rc )
            {
                if (pStats != NULL)
                    pStats->errors.users++;
                err.SysMsgWrite(ErrE,rc,DCT_MSG_LOS_GRANT_FAILED_SSD,
                    account,(WCHAR*)computer,rc);
            }
            else
            {
                err.MsgWrite(0,DCT_MSG_LOS_GRANTED_SS,
                    account,(WCHAR*)computer);
            }
        }

        SC_HANDLE         pService = OpenService(pScm,service,SERVICE_ALL_ACCESS);

        if ( pService )
        {
            int nCnt = 0;

            /* make sure the same user still starts this service */
            //get the source account names
            BOOL bSameAccount = TRUE;
            _bstr_t sSrcDom, sSrcSAM, sSrcUPN; 
            _bstr_t sSrcAccount = L"";
            _bstr_t sSrcAccountUPN = L"";

            //if not given src names (not migrating right now), get them
            if ((!m_strSourceDomainFlat) && (!m_strSourceSam))
            {
                //if got names, get UPN name also
                if (RetrieveOriginalAccount(sSrcDom, sSrcSAM))
                {
                    sSrcUPN = GetUPNName(sSrcSAM);
                    sSrcAccount = sSrcDom + _bstr_t(L"\\") + sSrcSAM;
                }
            }
            else //els if given src names (migrate this object now), use those names
            {
                sSrcDom = m_strSourceDomainFlat;
                sSrcSAM = m_strSourceSam;
                sSrcUPN = GetUPNName(sSrcSAM);
                sSrcAccount = sSrcDom + _bstr_t(L"\\") + sSrcSAM;
            }

            //if got names to check, check them
            if ((sSrcAccount.length()) || (sSrcUPN.length()))
            {
                BYTE                    buf[3000];
                QUERY_SERVICE_CONFIG  * pConfig = (QUERY_SERVICE_CONFIG *)buf; 
                DWORD                   lenNeeded = 0;
                // get the information about this service
                if (QueryServiceConfig(pService, pConfig, sizeof buf, &lenNeeded))
                {
                    //if not the same account, check UPN name or set to FALSE
                    if ((sSrcAccount.length()) && (UStrICmp(pConfig->lpServiceStartName,sSrcAccount)))
                    {
                        //if UPN name, try it
                        if (sSrcUPN.length())
                        {
                            //if not match either, set flag to FALSE;
                            if (UStrICmp(pConfig->lpServiceStartName,sSrcUPN))
                                bSameAccount = FALSE;
                        }
                        else  //else, not a match
                            bSameAccount = FALSE;
                    }
                }
            }//if got names

            //if same account, update the SCM
            if (bSameAccount)
            {
                // update the account and password for the service
                while ( !ChangeServiceConfig(pService,
                    SERVICE_NO_CHANGE, // dwServiceType
                    SERVICE_NO_CHANGE, // dwStartType
                    SERVICE_NO_CHANGE, // dwErrorControl
                    NULL,              // lpBinaryPathName
                    NULL,              // lpLoadOrderGroup
                    NULL,              // lpdwTagId
                    NULL,              // lpDependencies
                    account, // lpServiceStartName
                    ppassword,   // lpPassword
                    NULL) && nCnt < 5)       // lpDisplayName
                {
                    nCnt++;
                    Sleep(500);
                }
                if ( nCnt < 5 )
                {
                    err.MsgWrite(0,DCT_MSG_UPDATED_SCM_ENTRY_SS,(WCHAR*)computer,(WCHAR*)service);
                }
                else
                {
                    rc = GetLastError();
                }
            }//end if still same account
            else //else if not same user, put message in log and return error
            {
                err.MsgWrite(0,DCT_MSG_UPDATE_SCM_ENTRY_UNMATCHED_SSD,(WCHAR*)computer,(WCHAR*)service,(WCHAR*)sSrcAccount);
                rc = DCT_MSG_UPDATE_SCM_ENTRY_UNMATCHED_SSD;
            }

            CloseServiceHandle(pService);
        }
        CloseServiceHandle(pScm);
    }
    else
    {
        rc = GetLastError();
    }
    return rc;
}

BOOL 
   CServMigr::UpdateSCMs(
      IUnknown             * pVS,
      WCHAR const          * account, 
      WCHAR const          * password, 
      WCHAR const          * strSid,
      IIManageDB           * pDB,
	  EAMAccountStats      * pStats
   )
{
   BOOL                      bGotThemAll = TRUE;
   IVarSetPtr                pVarSet = pVS;
   LONG                      nCount = 0;
   WCHAR                     key[LEN_Path];            
   _bstr_t                   computer;
   _bstr_t                   service;
   long                      status;
   DWORD                     rc = 0;
   BOOL                      bFirst = TRUE;
   WCHAR                     prevComputer[LEN_Path] = L"";
   try  {
      nCount = pVarSet->get("ServiceAccountEntries");
      
      for ( long i = 0 ; i < nCount ; i++ )
      {
         
         swprintf(key,L"Computer.%ld",i);
         computer = pVarSet->get(key);
         swprintf(key,L"Service.%ld",i);
         service = pVarSet->get(key);
         swprintf(key,L"ServiceAccountStatus.%ld",i);
         status = pVarSet->get(key);
         
         if ( status == SvcAcctStatus_NotMigratedYet || status == SvcAcctStatus_UpdateFailed )
         {
            if ( UStrICmp(prevComputer,(WCHAR*)computer) )
            {
               bFirst = TRUE; // reset the 'first' flag when the computer changes
            }
            try {
               rc = DoUpdate(account,password,strSid,computer,service,bFirst/*only grant SeServiceLogonRight once per account*/,
				             pStats);
               bFirst = FALSE;
               safecopy(prevComputer,(WCHAR*)computer);
            }
            catch (...)
            {
                // Do we need to trigger the counter increment here?
                // if (pStats != NULL)
                //    pStats->errors.users++;
               err.DbgMsgWrite(ErrE,L"Exception!");
               err.DbgMsgWrite(0,L"Updating %ls on %ls",(WCHAR*)service,(WCHAR*)computer);
               err.DbgMsgWrite(0,L"Account=%ls, SID=%ls",(WCHAR*)account,(WCHAR*)strSid);
               rc = E_FAIL;
            }
            if (! rc )
            {
               // the update was successful
               pDB->raw_SetServiceAcctEntryStatus(computer,service,_bstr_t(account),SvcAcctStatus_Updated); 
            }
            else
            {
               // couldn't connect to this one -- we will need to save this account's password
               // in our encrypted storage
               pDB->raw_SetServiceAcctEntryStatus(computer,service,NULL,SvcAcctStatus_UpdateFailed);
               bGotThemAll = FALSE;
               SaveEncryptedPassword(computer,service,account,password);
               //if the current service account didn't match, we need not log an error 
               if (rc != DCT_MSG_UPDATE_SCM_ENTRY_UNMATCHED_SSD)
                {
                    err.SysMsgWrite(ErrE,rc,DCT_MSG_UPDATE_SCM_ENTRY_FAILED_SSD,(WCHAR*)computer,(WCHAR*)service,rc);
                    pStats->errors.users++;
                }
            }
         }
		    //else if skipping, still log in file so we can update later
	     else if (status == SvcAcctStatus_DoNotUpdate)
            SaveEncryptedPassword(computer,service,account,password);
      }
   }
   catch ( ... )
   {
    // Do we need to trigger the counter increment here?
    // if (pStats != NULL)
    //    pStats->errors.users++;
      err.DbgMsgWrite(ErrE,L"Exception!");
   }
   return bGotThemAll;
}

HRESULT 
   CServMigr::SaveEncryptedPassword(
      WCHAR          const * server,
      WCHAR          const * service,
      WCHAR          const * account,
      WCHAR          const * password
   )
{
	HRESULT hr = S_OK;
	TNodeListEnum e;
	TEntryNode* pNode;

	// if entry exists...

	for (pNode = (TEntryNode*)e.OpenFirst(&m_List); pNode; pNode = (TEntryNode*)e.Next())
	{
		if (_wcsicmp(pNode->GetComputer(), server) == 0)
		{
			if (_wcsicmp(pNode->GetService(), service) == 0)
			{
				if (_wcsicmp(pNode->GetAccount(), account) == 0)
				{
					// update password
					try {
						pNode->SetPassword(password);
					}
					catch (_com_error& ce) {
						hr = ce.Error();
						return hr;
					}
					break;
				}
			}
		}
	}

	// else...

	if (pNode == NULL)
	{
		// insert new entry

		try {
			pNode = new TEntryNode(server, service, account, password);
		}
		catch (_com_error& ce) {

			hr = ce.Error();
			return hr;
		}

		if (pNode)
		{
			m_List.InsertBottom(pNode);
		}
		else
		{
			hr = E_OUTOFMEMORY;
		}
	}

	return hr;
}


//////////////////////////////////////////////////////////////////////////////////////
///
/// TEntryList implementation of secure storage for service account passwords 
///
///
//////////////////////////////////////////////////////////////////////////////////////

DWORD 
   TEntryList::LoadFromFile(WCHAR const * filename)
{
    DWORD                     rc = 0;

    FILE                    * hSource = NULL;

    HCRYPTPROV                hProv = 0;
    HCRYPTKEY                 hKey = 0;

    BYTE                      pbBuffer[BLOCK_SIZE];
    WCHAR                     strData[BLOCK_SIZE * 5] = { 0 };
    DWORD                     dwCount;
    int                       eof = 0;
    WCHAR                     fullpath[LEN_Path];

    BYTE *pbKeyBlob = NULL;
    DWORD dwBlobLen;

    // Get our install directory from the registry, and then append the filename
    HKEY           hRegKey;
    DWORD          type;
    DWORD          lenValue = (sizeof fullpath);

    rc = RegOpenKey(HKEY_LOCAL_MACHINE,REGKEY_ADMT,&hRegKey);
    if ( ! rc )
    {

        rc = RegQueryValueEx(hRegKey,L"Directory",0,&type,(LPBYTE)fullpath,&lenValue);
        if (! rc )
        {
            UStrCpy(fullpath+UStrLen(fullpath),filename);
        }
        RegCloseKey(hRegKey);
    }

    if (rc != ERROR_SUCCESS)
    {
        goto done;
    }


    // Open the source file.
    if((hSource = _wfopen(fullpath,L"rb"))==NULL) 
    {
        rc = GetLastError();
        goto done;
    }

    // acquire handle to key container which must exist
    if ((hProv = AcquireContext(true)) == 0)
    {
        rc = GetLastError();
        goto done;
    }

    // Read the key blob length from the source file and allocate it to memory.
    fread(&dwBlobLen, sizeof(DWORD), 1, hSource);
    if(ferror(hSource) || feof(hSource)) 
    {
        rc = GetLastError();
        goto done;
    }

    if((pbKeyBlob = (BYTE*)malloc(dwBlobLen)) == NULL) 
    {
        rc = GetLastError();
        goto done;
    }

    // Read the key blob from the source file.
    fread(pbKeyBlob, 1, dwBlobLen, hSource);
    if(ferror(hSource) || feof(hSource)) 
    {
        rc = GetLastError();
        goto done;
    }

    // Import the key blob into the CSP.
    if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKey)) 
    {
        rc = GetLastError();
        goto done;
    }

    // Decrypt the source file and load the list
    do {
        // Read up to BLOCK_SIZE bytes from source file.
        dwCount = fread(pbBuffer, 1, BLOCK_SIZE, hSource);
        if(ferror(hSource)) 
        {
            rc = GetLastError();
            goto done;
        }
        eof=feof(hSource);

        // Decrypt the data.
        if(!CryptDecrypt(hKey, 0, eof, 0, pbBuffer, &dwCount)) 
        {
            rc = GetLastError();
            goto done;
        }
        // Read any complete entries from the buffer
        // first, add the buffer contents to any leftover information we had read from before
        WCHAR               * curr = strData;
        long                  len = UStrLen(strData);
        WCHAR               * nl = NULL;
        WCHAR                 computer[LEN_Computer];
        WCHAR                 service[LEN_Service];
        WCHAR                 account[LEN_Account];
        WCHAR                 password[LEN_Password];

        wcsncpy(strData + len,(WCHAR*)pbBuffer, dwCount / sizeof(WCHAR));
        strData[len + (dwCount / sizeof(WCHAR))] = 0;
        do {

            nl = wcschr(curr,L'\n');
            if ( nl )
            {
                *nl = 0;
                if ( swscanf(curr,L" %[^\t]\t%[^\t]\t%[^\t]\t%[^\t]\n",computer,service,account,password) )
                {
                    TEntryNode * pNode = NULL;
                    try {
                        pNode = new TEntryNode(computer,service,account,password);
                    }
                    catch (_com_error& ce) {

                        rc = ERROR_NOT_ENOUGH_MEMORY;
                        goto done;
                    }

                    InsertBottom(pNode);
                }
                else 
                {
                    rc = E_FAIL;
                    break;
                }
                // go on to the next entry
                curr = nl + 1;
            }

        } while ( nl );
        // there may be a partial record left in the buffer
        // if so, save it for the next read
        if ( (*curr) != 0 )
        {
            memmove(strData,curr,( 1 + UStrLen(curr) ) * (sizeof WCHAR));
        }
        else
        {
            strData[0] = L'\0';
        }


    } while(!feof(hSource));


done:

    // Clean up
    if(pbKeyBlob) 
        free(pbKeyBlob);


    if(hKey != 0) 
        CryptDestroyKey(hKey);


    if(hProv != 0) 
        CryptReleaseContext(hProv, 0);


    if(hSource != NULL) 
        fclose(hSource);

    return rc;
}

DWORD 
   TEntryList::SaveToFile(WCHAR const * filename)
{
    DWORD                     rc = 0;
    BYTE                      pbBuffer[BUFFER_SIZE];
    DWORD                     dwCount;
    HANDLE                    hDest = INVALID_HANDLE_VALUE;
    BYTE                    * pbKeyBlob = NULL;
    DWORD                     dwBlobLen;
    HCRYPTPROV                hProv = 0;
    HCRYPTKEY                 hKey = 0;
    HCRYPTKEY                 hXchgKey = 0;
    TEntryNode              * pNode;
    TNodeListEnum             e;
    WCHAR                     fullpath[LEN_Path];
    DWORD                     dwBlockSize;
    DWORD                     cbBlockSize = sizeof(dwBlockSize);
    DWORD                     dwPaddedCount;
    DWORD                     cbWritten;

    // Open the destination file.
    HKEY           hRegKey;
    DWORD          type;
    DWORD          lenValue = (sizeof fullpath);

    rc = RegOpenKey(HKEY_LOCAL_MACHINE,REGKEY_ADMT,&hRegKey);
    if ( ! rc )
    {

        rc = RegQueryValueEx(hRegKey,L"Directory",0,&type,(LPBYTE)fullpath,&lenValue);
        if (! rc )
        {
            UStrCpy(fullpath+UStrLen(fullpath),filename);
        }
        RegCloseKey(hRegKey);
    }

    if (rc != ERROR_SUCCESS)
    {
        goto done;
    }

    //
    // Delete previous data file if it exists. This obviates the need to change the
    // security descriptor on the file as CreateFile does not apply the security
    // descriptor if the file is opened but only when created.
    //

    if (!DeleteFile(fullpath))
    {
        rc = GetLastError();

        if (rc == ERROR_FILE_NOT_FOUND)
        {
            rc = ERROR_SUCCESS;
        }
        else
        {
            goto done;
        }
    }

    //
    // Create security descriptor with administrators as owner and permissions on file
    // so that only administrators and system have full access to the file.
    //

    PSECURITY_DESCRIPTOR psd = NULL;

    BOOL bConvert = ConvertStringSecurityDescriptorToSecurityDescriptor(
        _T("O:BAD:P(A;NP;FA;;;BA)(A;NP;FA;;;SY)"),
        SDDL_REVISION_1,
        &psd,
        NULL
    );

    if (!bConvert)
    {
        rc = GetLastError();
        goto done;
    }

    //
    // Create file.
    //

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = psd;
    sa.bInheritHandle = FALSE;

    hDest = CreateFile(
        fullpath,
        GENERIC_WRITE,
        0,
        &sa,
        CREATE_NEW,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    rc = GetLastError();

    if (psd)
    {
        LocalFree(psd);
    }

    if (hDest == INVALID_HANDLE_VALUE)
    {
        goto done;
    }

    // acquire handle to key container
    if ((hProv = AcquireContext(false)) == 0)
    {
        rc = GetLastError();
        goto done;
    }

    // Attempt to get handle to exchange key.
    if(!CryptGetUserKey(hProv,AT_KEYEXCHANGE,&hKey)) 
    {
        if(GetLastError()==NTE_NO_KEY) 
        {
            // Create key exchange key pair.
            if(!CryptGenKey(hProv,AT_KEYEXCHANGE,0,&hKey)) 
            {
                rc = GetLastError();
                goto done;
            } 
        } 
        else 
        {
            rc = GetLastError();
            goto done;
        }
    }
    CryptDestroyKey(hKey);
    CryptReleaseContext(hProv,0);

    // acquire handle to key container
    if ((hProv = AcquireContext(false)) == 0)
    {
        rc = GetLastError();
        goto done;
    }

    // Get a handle to key exchange key.
    if(!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hXchgKey)) 
    {
        rc = GetLastError();
        goto done;
    }

    // Create a random block cipher session key.
    if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) 
    {
        rc = GetLastError();
        goto done;
    }

    // Determine the size of the key blob and allocate memory.
    if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, NULL, &dwBlobLen)) 
    {
        rc = GetLastError();
        goto done;
    }

    if((pbKeyBlob = (BYTE*)malloc(dwBlobLen)) == NULL) 
    {
        rc = ERROR_NOT_ENOUGH_MEMORY;
        goto done;
    }

    // Export the key into a simple key blob.
    if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, 
        &dwBlobLen)) 
    {
        rc = GetLastError();
        free(pbKeyBlob);
        goto done;
    }

    // Write the size of the key blob to the destination file.

    if (!WriteFile(hDest, &dwBlobLen, sizeof(DWORD), &cbWritten, NULL))
    {
        rc = GetLastError();
        free(pbKeyBlob);
        goto done;
    }

    // Write the key blob to the destination file.

    if(!WriteFile(hDest, pbKeyBlob, dwBlobLen, &cbWritten, NULL)) 
    {
        rc = GetLastError();
        free(pbKeyBlob);
        goto done;
    }

    // Free memory.
    free(pbKeyBlob);

    // get key cipher's block length in bytes

    if (CryptGetKeyParam(hKey, KP_BLOCKLEN, (BYTE*)&dwBlockSize, &cbBlockSize, 0))
    {
        dwBlockSize /= 8;
    }
    else
    {
        rc = GetLastError();
        goto done;
    }

    // Encrypt the item list and write it to the destination file.

    for ( pNode = (TEntryNode*)e.OpenFirst(this); pNode ; pNode = (TEntryNode *)e.Next()  )
    {
        // copy an item into the buffer in the following format:
        // Computer\tService\tAccount\tPassword

        int cchWritten;
        const size_t BUFFER_SIZE_IN_WCHARS = sizeof(pbBuffer) / sizeof(wchar_t);
        wchar_t* pchLast = &(((wchar_t*)pbBuffer)[BUFFER_SIZE_IN_WCHARS - 1]);
        *pchLast = L'\0';

        const WCHAR * pszPwd = NULL;
        try {
            pszPwd = pNode->GetPassword();
        }
        catch (_com_error& ce) {
            rc = ERROR_DECRYPTION_FAILED;
            goto done;
        }
        
        
        if ( pszPwd && *pszPwd )

        {
            cchWritten = _snwprintf(
                (wchar_t*)pbBuffer,
                BUFFER_SIZE_IN_WCHARS,
                L"%s\t%s\t%s\t%s\n",
                pNode->GetComputer(),
                pNode->GetService(),
                pNode->GetAccount(),
                pszPwd
            );
        }
        else
        {
            cchWritten = _snwprintf(
                (wchar_t*)pbBuffer,
                BUFFER_SIZE_IN_WCHARS,
                L"%s\t%s\t%s\t%s\n",
                pNode->GetComputer(),
                pNode->GetService(),
                pNode->GetAccount(),
                L"NULL"
            );
        }

        pNode->ReleasePassword();
        pszPwd = NULL;


        if ((cchWritten < 0) || (*pchLast != L'\0'))
        {
            rc = ERROR_INSUFFICIENT_BUFFER;
            goto done;
        }

        dwCount = UStrLen((WCHAR*)pbBuffer) * (sizeof WCHAR) ;

        // the buffer must be a multiple of the key cipher's block length
        // NOTE: this algorithm assumes block length is multiple of sizeof(WCHAR)

        if (dwBlockSize > 0)
        {
            // calculate next multiple greater than count
            dwPaddedCount = ((dwCount + dwBlockSize - 1) / dwBlockSize) * dwBlockSize;

            // pad buffer with space characters

            WCHAR* pch = (WCHAR*)(pbBuffer + dwCount);

            for (; dwCount < dwPaddedCount; dwCount += sizeof(WCHAR))
            {
                *pch++ = L' ';
            }
        }

        // Encrypt the data.
        if(!CryptEncrypt(hKey, 0, (pNode->Next() == NULL) , 0, pbBuffer, &dwCount,
            BUFFER_SIZE))
        {
            rc = GetLastError();
            goto done;
        }

        // Write the data to the destination file.

        if(!WriteFile(hDest, pbBuffer, dwCount, &cbWritten, NULL)) 
        {
            rc = GetLastError();
            goto done;
        }
    }

done:

    // Destroy the session key.
    if(hKey != 0) CryptDestroyKey(hKey);

    // Destroy the key exchange key.
    if(hXchgKey != 0) CryptDestroyKey(hXchgKey);

    // Release the provider handle.
    if(hProv != 0) CryptReleaseContext(hProv, 0);

    // Close destination file.
    if(hDest != INVALID_HANDLE_VALUE) CloseHandle(hDest);

    return rc;
}


// AcquireContext Method
//
// acquire handle to key container within cryptographic service provider (CSP)
//

HCRYPTPROV TEntryList::AcquireContext(bool bContainerMustExist)
{
	HCRYPTPROV hProv = 0;

	#define KEY_CONTAINER_NAME _T("A69904BC349C4CFEAAEAB038BAB8C3B1")

	if (bContainerMustExist)
	{
		// first try Microsoft Enhanced Cryptographic Provider

		if (!CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
		{
			if (GetLastError() == NTE_KEYSET_NOT_DEF)
			{
				// then try Microsoft Base Cryptographic Provider

				CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET);
			}
		}
	}
	else
	{
		// first try Microsoft Enhanced Cryptographic Provider

		if (!CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
		{
			DWORD dwError = GetLastError();

			if ((dwError == NTE_BAD_KEYSET) || (dwError == NTE_KEYSET_NOT_DEF))
			{
				// then try creating key container in enhanced provider

				if (!CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET|CRYPT_NEWKEYSET))
				{
					dwError = GetLastError();

					if (dwError == NTE_KEYSET_NOT_DEF)
					{
						// then try Microsoft Base Cryptographic Provider

						if (!CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
						{
							dwError = GetLastError();

							if ((dwError == NTE_BAD_KEYSET) || (dwError == NTE_KEYSET_NOT_DEF))
							{
								// finally try creating key container in base provider

								CryptAcquireContext(&hProv, KEY_CONTAINER_NAME, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET|CRYPT_NEWKEYSET);
							}
						}
					}
				}
			}
		}
	}

	return hProv;
}


STDMETHODIMP CServMigr::TryUpdateSam(BSTR computer,BSTR service,BSTR account)
{
   HRESULT                   hr = S_OK;
   
   // Find the entry in the list, and perform the update
   TNodeListEnum             e;
   TEntryNode              * pNode;
   BOOL                      bFound = FALSE;

   for ( pNode = (TEntryNode*)e.OpenFirst(&m_List) ; pNode ; pNode = (TEntryNode*)e.Next() )
   {
      if (  !UStrICmp(computer,pNode->GetComputer())
         && !UStrICmp(service,pNode->GetService()) 
         && !UStrICmp(account,pNode->GetAccount())
         )
      {
         // found it!
         bFound = TRUE;
         const WCHAR * pszPwd = NULL;
         try {
              pszPwd = pNode->GetPassword();
         }
         catch (_com_error& ce) {
            hr = ce.Error();
            break;
         }
         
         BSTR bstrPwd = SysAllocString(pszPwd);
         if ((bstrPwd == NULL) && pszPwd && pszPwd[0])
         {
            hr = E_OUTOFMEMORY;
            pNode->ReleasePassword();
            break;
         }
         
         hr = TryUpdateSamWithPassword(computer,service,account,bstrPwd );

         pNode->ReleasePassword();

         SecureZeroMemory(bstrPwd, wcslen(bstrPwd)*sizeof(WCHAR));
         SysFreeString(bstrPwd);

         break;
      }
   }
   
   if ( ! bFound )
   {
      hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
   }
   return hr;
}

STDMETHODIMP CServMigr::TryUpdateSamWithPassword(BSTR computer,BSTR service,BSTR domAccount,BSTR password)
{
   DWORD                     rc = 0;
   WCHAR                     domain[LEN_Domain];
   _bstr_t                   dc;
   WCHAR                     account[LEN_Account];
   WCHAR                     domStr[LEN_Domain];
   BYTE                      sid[100];
   WCHAR                     strSid[200];
   WCHAR                   * pSlash = wcschr(domAccount,L'\\');
   SID_NAME_USE              snu;
   DWORD                     lenSid = DIM(sid);
   DWORD                     lenDomStr = DIM(domStr);
   DWORD                     lenStrSid = DIM(strSid);

   // split out the domain and account names
   if ( pSlash )
   {
//      UStrCpy(domain,domAccount,pSlash - domAccount + 1);
      UStrCpy(domain,domAccount,(int)(pSlash - domAccount + 1));
      UStrCpy(account,pSlash+1);
      
      GetAnyDcName5(domain, dc);

      // get the SID for the target account
      if ( LookupAccountName(dc,account,sid,&lenSid,domStr,&lenDomStr,&snu) )
      {
         GetTextualSid(sid,strSid,&lenStrSid);

         rc = DoUpdate(domAccount,password,strSid,computer,service,TRUE, NULL);
      }
      else 
      {
         rc = GetLastError();
      }
   }
   else
   {
      rc = ERROR_NOT_FOUND;
   }

   return HRESULT_FROM_WIN32(rc);
}


BOOL                                       // ret - TRUE if directory found
   CServMigr::GetDirectory(
      WCHAR                * filename      // out - string buffer to store directory name
   )
{
   DWORD                     rc = 0;
   BOOL                      bFound = FALSE;
   TRegKey                   key;


   rc = key.OpenRead(GET_STRING(IDS_HKLM_DomainAdmin_Key),HKEY_LOCAL_MACHINE);


   if ( ! rc )
   {

	   rc = key.ValueGetStr(L"Directory",filename,MAX_PATH);

	   if ( ! rc )
      {
         if ( *filename ) 
            bFound = TRUE;
      }
   }
   key.Close();

   return bFound;
}


/*********************************************************************
 *                                                                   *
 * Written by: Paul Thompson                                         *
 * Date: 28 MAY 2001                                                 *
 *                                                                   *
 *     This function is responsible for retrieving the original      *
 * service account of the given migrated service account.  We use the*
 * target service account name and domain to lookup its source       *
 * account name and domain from the Migrated Objects table.          *
 *     This function returns TRUE or FALSE and if TRUE, fills in the *
 * given BSTRs for soure domain and source SAM name.                 *
 *                                                                   *
 *********************************************************************/

//BEGIN RetrieveOriginalAccount
BOOL CServMigr::RetrieveOriginalAccount(_bstr_t &sSrcDom, _bstr_t &sSrcSAM)
{
    /* local constants */
    const long ONLY_ONE_MATCHED = 1;

    /* local variables */
    WCHAR			sTemp[MAX_PATH];
    BOOL				bSuccess = FALSE;
    IUnknown		  * pUnk = NULL;


    /* function body */

    try 
    { 
        IVarSetPtr		pVSMig(__uuidof(VarSet));
        IIManageDBPtr	pDb(__uuidof(IManageDB));
        //see if any target account fitting this SAM name and domain have been migrated
        pVSMig->QueryInterface(IID_IUnknown, (void**) &pUnk);
        HRESULT hrFind = pDb->raw_GetMigratedObjectsByTarget(m_strTargetDomain, m_strTargetSam, &pUnk);
        pUnk->Release();
        pUnk = NULL;
        //if migrated only one account to this name, then fill the return strings
        if (hrFind == S_OK)
        {
            //get objects number matching this description
            long nMatched = pVSMig->get(L"MigratedObjects");
            //if only one found, fill output strings
            if (nMatched == ONLY_ONE_MATCHED)
            {
                swprintf(sTemp,L"MigratedObjects.0.%s",GET_STRING(DB_SourceDomain));
                sSrcDom = pVSMig->get(sTemp);
                swprintf(sTemp,L"MigratedObjects.0.%s",GET_STRING(DB_SourceSamName));
                sSrcSAM = pVSMig->get(sTemp);
                bSuccess = TRUE;  //set success flag
            }//end if found only one
        }//end if found at least one
    }
    catch ( ... )
    {
        if (pUnk)
            pUnk->Release();

        bSuccess = false;
    }

    return bSuccess;
}
//END RetrieveOriginalAccount


/*********************************************************************
 *                                                                   *
 * Written by: Paul Thompson                                         *
 * Date: 28 MAY 2001                                                 *
 *                                                                   *
 *     This function is responsible for retrieving the UPN name of   *
 * the given account.  The given account should be in NT4 format     *
 * (Domain\Username).  The return will be the UPN or empty if not    *
 * retrieved.                                                        *
 *                                                                   *
 *********************************************************************/

//BEGIN GetUPNName
_bstr_t CServMigr::GetUPNName(_bstr_t sSrcSAM)
{
    /* local variables */
    HRESULT         hr;
    _bstr_t			sUPN = L"";
    HANDLE          hDs = NULL;

    /* function body */

    //bind to the source domain
    DWORD dwError = DsBind(NULL,m_strSourceDomain,&hDs);

    //now try to call DSCrackNames to get the UPN name
    if ((dwError == ERROR_SUCCESS) && hDs)
    {
        PDS_NAME_RESULT         pNamesOut = NULL;
        WCHAR                 * pNamesIn[1];

        _bstr_t sSrcAccount = m_strSourceDomainFlat + _bstr_t(L"\\") + m_strSourceSam;
        pNamesIn[0] = (WCHAR*)sSrcAccount;
        hr = DsCrackNames(hDs,DS_NAME_NO_FLAGS,DS_NT4_ACCOUNT_NAME,DS_USER_PRINCIPAL_NAME,1,pNamesIn,&pNamesOut);
        DsUnBind(&hDs);
        hDs = NULL;
        //if got UPN name, store it
        if ( !hr )
        {
            if ( pNamesOut->rItems[0].status == DS_NAME_NO_ERROR )
                sUPN = pNamesOut->rItems[0].pName;
            DsFreeNameResult(pNamesOut); //free the results
        }//end if cracked name
    }//end if bound

    return sUPN;
}
//END GetUPNName