/*****************************************************************/
/**               Microsoft Windows for Workgroups              **/
/**           Copyright (C) Microsoft Corp., 1991-1992          **/
/*****************************************************************/

/* PROFILES.CPP -- Code for user profile management.
 *
 * History:
 *  01/04/94    gregj   Created
 *	06/28/94	gregj	Use sync engine for desktop, programs reconciliation
 *	09/05/96	gregj	Snarfed from MPR for use by IE4 family logon.
 */

#include "mslocusr.h"
#include "msluglob.h"

#include "resource.h"

#include <npmsg.h>
#include <regentry.h>
#include <buffer.h>
#include <shellapi.h>

HMODULE g_hmodShell = NULL;
typedef int (*PFNSHFILEOPERATIONA)(LPSHFILEOPSTRUCTA lpFileOp);
PFNSHFILEOPERATIONA g_pfnSHFileOperationA = NULL;


HRESULT LoadShellEntrypoint(void)
{
    if (g_pfnSHFileOperationA != NULL)
        return S_OK;

    HRESULT hres;
    ENTERCRITICAL
    {
        if (g_hmodShell == NULL) {
            g_hmodShell = ::LoadLibrary("SHELL32.DLL");
        }
        if (g_hmodShell != NULL) {
            g_pfnSHFileOperationA = (PFNSHFILEOPERATIONA)::GetProcAddress(g_hmodShell, "SHFileOperationA");
        }
        if (g_pfnSHFileOperationA == NULL)
            hres = HRESULT_FROM_WIN32(::GetLastError());
        else
            hres = S_OK;
    }
    LEAVECRITICAL

    return hres;
}


void UnloadShellEntrypoint(void)
{
    ENTERCRITICAL
    {
        if (g_hmodShell != NULL) {
            ::FreeLibrary(g_hmodShell);
            g_hmodShell = NULL;
            g_pfnSHFileOperationA = NULL;
        }
    }
    LEAVECRITICAL
}


const DWORD attrLocalProfile = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY;

extern "C" {
extern LONG __stdcall RegRemapPreDefKey(HKEY hkeyNew, HKEY hkeyPredef);
};

#ifdef DEBUG

extern "C" {
BOOL fNoisyReg = FALSE;
};

#endif

LONG MyRegLoadKey(HKEY hKey, LPCSTR lpszSubKey, LPCSTR lpszFile)
{
#ifdef DEBUG
	if (fNoisyReg) {
		char buf[300];
		::wsprintf(buf, "MyRegLoadKey(\"%s\", \"%s\")\r\n", lpszSubKey, lpszFile);
		::OutputDebugString(buf);
	}
#endif

	/* Since the registry doesn't support long filenames, get the short
	 * alias for the path.  If that succeeds, we use that path, otherwise
	 * we just use the original one and hope it works.
	 */
	CHAR szShortPath[MAX_PATH+1];
	if (GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
		lpszFile = szShortPath;

	return ::RegLoadKey(hKey, lpszSubKey, lpszFile);
}


#ifdef DEBUG
LONG MyRegUnLoadKey(HKEY hKey, LPCSTR lpszSubKey)
{
	if (fNoisyReg) {
		char buf[300];
		::wsprintf(buf, "MyRegUnLoadKey(\"%s\")\r\n", lpszSubKey);
		::OutputDebugString(buf);
	}
	return ::RegUnLoadKey(hKey, lpszSubKey);
}
#endif


LONG MyRegSaveKey(HKEY hKey, LPCSTR lpszFile, LPSECURITY_ATTRIBUTES lpsa)
{
#ifdef DEBUG
	if (fNoisyReg) {
		char buf[300];
		::wsprintf(buf, "MyRegSaveKey(\"%s\")\r\n", lpszFile);
		::OutputDebugString(buf);
	}
#endif

	/* Since the registry doesn't support long filenames, get the short
	 * alias for the path.  If that succeeds, we use that path, otherwise
	 * we just use the original one and hope it works.
	 *
	 * GetShortPathName only works if the file exists, so we have to
	 * create a dummy copy first.
	 */

	HANDLE hTemp = ::CreateFile(lpszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
								FILE_ATTRIBUTE_NORMAL, NULL);
	if (hTemp == INVALID_HANDLE_VALUE)
		return ::GetLastError();
	::CloseHandle(hTemp);

	CHAR szShortPath[MAX_PATH+1];
	if (::GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
		lpszFile = szShortPath;

	return ::RegSaveKey(hKey, lpszFile, lpsa);
}

#ifndef DEBUG
#define MyRegUnLoadKey	RegUnLoadKey
#endif

LONG OpenLogonKey(HKEY *phKey)
{
	return ::RegOpenKey(HKEY_LOCAL_MACHINE, szLogonKey, phKey);
}


void AddBackslash(LPSTR lpPath)
{
	LPCSTR lpBackslash = ::strrchrf(lpPath, '\\');
	if (lpBackslash == NULL || *(lpBackslash+1) != '\0')
		::strcatf(lpPath, "\\");
}


void AddBackslash(NLS_STR& nlsPath)
{
	ISTR istrBackslash(nlsPath);
	if (!nlsPath.strrchr(&istrBackslash, '\\') ||
		*nlsPath.QueryPch(++istrBackslash) != '\0')
		nlsPath += '\\';
}


void GetDirFromPath(NLS_STR& nlsTempDir, LPCSTR pszPath)
{
	nlsTempDir = pszPath;

	ISTR istrBackslash(nlsTempDir);
	if (nlsTempDir.strrchr(&istrBackslash, '\\'))
		nlsTempDir.DelSubStr(istrBackslash);
}


BOOL FileExists(LPCSTR pszPath)
{
	DWORD dwAttrs = ::GetFileAttributes(pszPath);

	if (dwAttrs != 0xffffffff && !(dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
		return TRUE;
	else
		return FALSE;
}


BOOL DirExists(LPCSTR pszPath)
{
	if (*pszPath == '\0')
		return FALSE;

	DWORD dwAttrs = ::GetFileAttributes(pszPath);

	if (dwAttrs != 0xffffffff && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
		return TRUE;
	else
		return FALSE;
}


/* CreateDirectoryPath attempts to create the specified directory;  if the
 * create attempt fails, it tries to create each element of the path in case
 * any intermediate directories also don't exist.
 */
BOOL CreateDirectoryPath(LPCSTR pszPath)
{
    BOOL fRet = ::CreateDirectory(pszPath, NULL);

    if (fRet || (::GetLastError() != ERROR_PATH_NOT_FOUND))
        return fRet;

    NLS_STR nlsTemp(pszPath);
    if (nlsTemp.QueryError() != ERROR_SUCCESS)
        return FALSE;

    LPSTR pszTemp = nlsTemp.Party();
    LPSTR pszNext = pszTemp;

    /* If it's a drive-based path (which it should be), skip the drive
     * and first backslash -- we don't need to attempt to create the
     * root directory.
     */
    if (::strchrf(pszTemp, ':') != NULL) {
        pszNext = ::strchrf(pszTemp, '\\');
        if (pszNext != NULL)
            pszNext++;
    }

    /* Now walk through the path creating one directory at a time. */

    for (;;) {
        pszNext = ::strchrf(pszNext, '\\');
        if (pszNext != NULL) {
            *pszNext = '\0';
        }
        else {
            break;          /* no more intermediate directories to create */
        }

        /* Create the intermediate directory.  No error checking because we're
         * not extremely performance-critical, and we can get errors if the
         * directory already exists, etc.  With security and other things,
         * the set of benign error codes we'd have to check for could be
         * large.
         */
        fRet = ::CreateDirectory(pszTemp, NULL);

        *pszNext = '\\';
        pszNext++;
        if (!*pszNext)      /* ended with trailing slash? */
            return fRet;    /* return last result */
    }

    /* We should have created all the intermediate directories by now.
     * Create the final path.
     */

    return ::CreateDirectory(pszPath, NULL);
}


UINT SafeCopy(LPCSTR pszSrc, LPCSTR pszDest, DWORD dwAttrs)
{
	NLS_STR nlsTempDir(MAX_PATH);
	NLS_STR nlsTempFile(MAX_PATH);
	if (!nlsTempDir || !nlsTempFile)
		return ERROR_NOT_ENOUGH_MEMORY;

	GetDirFromPath(nlsTempDir, pszDest);

	if (!::GetTempFileName(nlsTempDir.QueryPch(), ::szProfilePrefix, 0,
						   nlsTempFile.Party()))
		return ::GetLastError();

	nlsTempFile.DonePartying();

	if (!::CopyFile(pszSrc, nlsTempFile.QueryPch(), FALSE)) {
		UINT err = ::GetLastError();
		::DeleteFile(nlsTempFile.QueryPch());
		return err;
	}

	::SetFileAttributes(pszDest, FILE_ATTRIBUTE_NORMAL);

	::DeleteFile(pszDest);

	// At this point, the temp file has the same attributes as the original
	// (usually read-only, hidden, system).  Some servers, such as NetWare
	// servers, won't allow us to rename a read-only file.  So we have to
	// take the attributes off, rename the file, then put back whatever the
	// caller wants.
	::SetFileAttributes(nlsTempFile.QueryPch(), FILE_ATTRIBUTE_NORMAL);

	if (!::MoveFile(nlsTempFile.QueryPch(), pszDest))
		return ::GetLastError();

	::SetFileAttributes(pszDest, dwAttrs);

	return ERROR_SUCCESS;
}


#ifdef LOAD_PROFILES

void SetProfileTime(LPCSTR pszLocalPath, LPCSTR pszCentralPath)
{
	HANDLE hFile = ::CreateFile(pszCentralPath,
								GENERIC_READ | GENERIC_WRITE,
								FILE_SHARE_READ, NULL,
								OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile != INVALID_HANDLE_VALUE) {
		FILETIME ft;

		::GetFileTime(hFile, NULL, NULL, &ft);
		::CloseHandle(hFile);

		DWORD dwAttrs = ::GetFileAttributes(pszLocalPath);
		if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
			::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
		}
		hFile = ::CreateFile(pszLocalPath, GENERIC_READ | GENERIC_WRITE,
							 FILE_SHARE_READ, NULL, OPEN_EXISTING,
							 FILE_ATTRIBUTE_NORMAL, NULL);
		if (hFile != INVALID_HANDLE_VALUE) {
			::SetFileTime(hFile, NULL, NULL, &ft);
			::CloseHandle(hFile);
		}
		if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
			::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
		}
	}
}


UINT DefaultReconcile(LPCSTR pszCentralPath, LPCSTR pszLocalPath, DWORD dwFlags)
{
	UINT err;

	if (dwFlags & RP_LOGON) {
		if (dwFlags & RP_INIFILE)
			return SafeCopy(pszCentralPath, pszLocalPath, FILE_ATTRIBUTE_NORMAL);

		HANDLE hFile = ::CreateFile(pszCentralPath, GENERIC_READ, FILE_SHARE_READ,
									NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		FILETIME ftCentral;
		if (hFile != INVALID_HANDLE_VALUE) {
			::GetFileTime(hFile, NULL, NULL, &ftCentral);
			::CloseHandle(hFile);
		}
		else {
			ftCentral.dwLowDateTime = 0;	/* can't open, pretend it's really old */
			ftCentral.dwHighDateTime = 0;
		}

		hFile = ::CreateFile(pszLocalPath, GENERIC_READ, FILE_SHARE_READ,
							 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		FILETIME ftLocal;
		if (hFile != INVALID_HANDLE_VALUE) {
			::GetFileTime(hFile, NULL, NULL, &ftLocal);
			::CloseHandle(hFile);
		}
		else {
			ftLocal.dwLowDateTime = 0;	/* can't open, pretend it's really old */
			ftLocal.dwHighDateTime = 0;
		}

		LPCSTR pszSrc, pszDest;

		/*
		 * Find out which file is newer, and make that the source
		 * for the copy.
		 */

		LONG lCompare = ::CompareFileTime(&ftCentral, &ftLocal);
		if (!lCompare) {
			::dwProfileFlags |= PROF_CENTRALWINS;
			return WN_SUCCESS;		/* timestamps match, no copy to do */
		}
		else if (lCompare > 0) {
			pszSrc = pszCentralPath;
			pszDest = pszLocalPath;
			::dwProfileFlags |= PROF_CENTRALWINS;
		}
		else {
			pszSrc = pszLocalPath;
			pszDest = pszCentralPath;
			::dwProfileFlags &= ~PROF_CENTRALWINS;
		}

		err = SafeCopy(pszSrc, pszDest,
					   pszDest == pszCentralPath ? FILE_ATTRIBUTE_NORMAL
					   : attrLocalProfile);
	}
	else {
		err = SafeCopy(pszLocalPath, pszCentralPath, FILE_ATTRIBUTE_NORMAL);
		if (err == WN_SUCCESS) {	/* copied back successfully */

#ifdef EXTENDED_PROFILES	/* chicago doesn't special-case resident profiles */
			if (dwFlags & PROF_RESIDENT) {
				DeleteProfile(pszLocalPath);	/* delete temp file */
			}
#endif

			SetProfileTime(pszLocalPath, pszCentralPath);
		}
	}

	return err;
}


#endif	/* LOAD_PROFILES */


void GetLocalProfileDirectory(NLS_STR& nlsPath)
{
	::GetWindowsDirectory(nlsPath.Party(), nlsPath.QueryAllocSize());
	nlsPath.DonePartying();

	AddBackslash(nlsPath);

	nlsPath.strcat(::szProfilesDirectory);

	::CreateDirectory(nlsPath.QueryPch(), NULL);
}


HRESULT GiveUserDefaultProfile(LPCSTR lpszPath)
{
	HKEY hkeyDefaultUser;
	LONG err = ::RegOpenKey(HKEY_USERS, ::szDefaultUserName, &hkeyDefaultUser);
	if (err == ERROR_SUCCESS) {
		err = ::MyRegSaveKey(hkeyDefaultUser, lpszPath, NULL);
		::RegCloseKey(hkeyDefaultUser);
	}
	return HRESULT_FROM_WIN32(err);
}


void ComputeLocalProfileName(LPCSTR pszUsername, NLS_STR *pnlsLocalProfile)
{
	GetLocalProfileDirectory(*pnlsLocalProfile);

	UINT cbPath = pnlsLocalProfile->strlen();
	LPSTR lpPath = pnlsLocalProfile->Party();
	LPSTR lpFilename = lpPath + cbPath;

	*(lpFilename++) = '\\';
	::strcpyf(lpFilename, pszUsername);		/* start with whole username */

	LPSTR lpFNStart = lpFilename;

	UINT iFile = 0;
	while (!::CreateDirectory(lpPath, NULL)) {
		if (!DirExists(lpPath))
			break;

		/* Couldn't use whole username, start with 5 bytes of username + numbers. */
		if (iFile == 0) {
			::strncpyf(lpFilename, pszUsername, 5);	/* copy at most 5 bytes of username */
			*(lpFilename+5) = '\0';					/* force null term, just in case */
			lpFilename += ::strlenf(lpFilename);
		}
		else if (iFile >= 4095) {	/* max number expressible in 3 hex digits */
			lpFilename = lpFNStart;	/* start using big numbers with no uname prefix */
			if ((int)iFile < 0)	/* if we run out of numbers, abort */
				break;
		}

		::wsprintf(lpFilename, "%03lx", iFile);

		iFile++;
	}

	pnlsLocalProfile->DonePartying();
}


HRESULT CopyProfile(LPCSTR pszSrcPath, LPCSTR pszDestPath)
{
	UINT err = SafeCopy(pszSrcPath, pszDestPath, attrLocalProfile);

	return HRESULT_FROM_WIN32(err);
}


BOOL UseUserProfiles(void)
{
	HKEY hkeyLogon;

	LONG err = OpenLogonKey(&hkeyLogon);
	if (err == ERROR_SUCCESS) {
		DWORD fUseProfiles = 0;
		DWORD cbData = sizeof(fUseProfiles);
		err = ::RegQueryValueEx(hkeyLogon, (LPSTR)::szUseProfiles, NULL, NULL,
								(LPBYTE)&fUseProfiles, &cbData);
		::RegCloseKey(hkeyLogon);
		return (err == ERROR_SUCCESS) && fUseProfiles;
	}

	return FALSE;
}


void EnableProfiles(void)
{
	HKEY hkeyLogon;

	LONG err = OpenLogonKey(&hkeyLogon);
	if (err == ERROR_SUCCESS) {
		DWORD fUseProfiles = 1;
		::RegSetValueEx(hkeyLogon, (LPSTR)::szUseProfiles, 0, REG_DWORD,
						(LPBYTE)&fUseProfiles, sizeof(fUseProfiles));
		::RegCloseKey(hkeyLogon);
	}
}


struct SYNCSTATE
{
    HKEY hkeyProfile;
    NLS_STR *pnlsProfilePath;
    NLS_STR *pnlsOtherProfilePath;
    HKEY hkeyPrimary;
};


/*
 * PrefixMatch determines whether a given path is equal to or a descendant
 * of a given base path.
 */
BOOL PrefixMatch(LPCSTR pszPath, LPCSTR pszBasePath)
{
	UINT cchBasePath = ::strlenf(pszBasePath);
	if (!::strnicmpf(pszPath, pszBasePath, cchBasePath)) {
		/* make sure that the base path matches the whole last component */
		if ((pszPath[cchBasePath] == '\\' || pszPath[cchBasePath] == '\0'))
			return TRUE;
		/* check to see if the base path is a root path;  if so, match */
		LPCSTR pszBackslash = ::strrchrf(pszBasePath, '\\');
		if (pszBackslash != NULL && *(pszBackslash+1) == '\0')
			return TRUE;
		else
			return FALSE;
	}
	else
		return FALSE;
}


#if 0
void ReportReconcileError(SYNCSTATE *pSyncState, TWINRESULT tr, PRECITEM pri,
						  PRECNODE prnSrc, PRECNODE prnDest, BOOL fSrcCentral)
{
	/* If we're copying the file the "wrong" way, swap our idea of the
	 * source and destination.  For the purposes of other profile code,
	 * source and destination refer to the entire profile copy direction.
	 * For this particular error message, they refer to the direction
	 * that this particular file was being copied.
	 */
	if (prnSrc->rnaction == RNA_COPY_TO_ME) {
		PRECNODE prnTemp = prnSrc;
		prnSrc = prnDest;
		prnDest = prnTemp;
		fSrcCentral = !fSrcCentral;
	}

	/* Set the error status on this key to be the destination of the copy,
	 * which is the copy that's now out of date because of the error and
	 * needs to be guarded from harm next time.
	 */
	pSyncState->uiRecError |= fSrcCentral ? RECERROR_LOCAL : RECERROR_CENTRAL;

	pSyncState->dwFlags |= SYNCSTATE_ERROR;

	if (pSyncState->dwFlags & SYNCSTATE_ERRORMSG)
		return;			/* error already reported */

	pSyncState->dwFlags |= SYNCSTATE_ERRORMSG;

	RegEntry re(::szReconcileRoot, pSyncState->hkeyProfile);
	if (re.GetError() == ERROR_SUCCESS && !re.GetNumber(::szDisplayProfileErrors, TRUE))
		return;		/* user doesn't want to see this error message */

	PCSTR pszFile;
	UINT uiMainMsg;

	switch (tr) {
	case TR_DEST_OPEN_FAILED:
	case TR_DEST_WRITE_FAILED:
		uiMainMsg = IERR_ProfRecWriteDest;
		pszFile = prnDest->pcszFolder;
		break;
	case TR_SRC_OPEN_FAILED:
	case TR_SRC_READ_FAILED:
		uiMainMsg = IERR_ProfRecOpenSrc;
		pszFile = prnSrc->pcszFolder;
		break;
	default:
		uiMainMsg = IERR_ProfRecCopy;
		pszFile = pri->pcszName;
		break;
	}

	if (DisplayGenericError(NULL, uiMainMsg, tr, pszFile, ::szNULL,
							MB_YESNO | MB_ICONEXCLAMATION, IDS_TRMsgBase) == IDNO) {
		re.SetValue(::szDisplayProfileErrors, (ULONG)FALSE);
	}
}


#ifdef DEBUG
char szOutbuf[200];
#endif


/*
 * MyReconcile is a wrapper around ReconcileItem.  It needs to detect merge
 * type operations and transform them into copies in the appropriate direction,
 * and recognize when the sync engine wants to replace a file that the user
 * really wants deleted.
 */
void MyReconcile(PRECITEM pri, SYNCSTATE *pSyncState)
{
	if (pri->riaction == RIA_NOTHING)
		return;

	/* Because we don't have a persistent briefcase, we can't recognize when
	 * the user has deleted an item;  the briefcase will want to replace it
	 * with the other version, which is not what the user wants.  So we use
	 * the direction of the profile's copy, and if the sync engine wants to
	 * copy a file from the "destination" of the profile's copy to the "source"
	 * because the "source" doesn't exist, we recognize that as the source
	 * having been deleted and synchronize manually by deleting the dest.
	 *
	 * prnSrc points to the recnode for the item that's coming from the same
	 * side of the transaction that the more recent profile was on;  prnDest
	 * points to the recnode for the other side.
	 *
	 * The test is complicated because we first have to figure out which of
	 * the two directories (nlsDir1, the local dir; or nlsDir2, the central
	 * dir) is the source and which the destination.  Then we have to figure
	 * out which of the two RECNODEs we got matches which directory.
	 */
	PRECNODE prnSrc;
	PRECNODE prnDest;
	LPCSTR pszSrcBasePath;
	BOOL fSrcCentral;
	if (pSyncState->IsMandatory() || (pSyncState->dwFlags & PROF_CENTRALWINS)) {
		pszSrcBasePath = pSyncState->nlsDir2.QueryPch();
		fSrcCentral = TRUE;
	}
	else {
		pszSrcBasePath = pSyncState->nlsDir1.QueryPch();
		fSrcCentral = FALSE;
	}

	if (PrefixMatch(pri->prnFirst->pcszFolder, pszSrcBasePath)) {
		prnSrc = pri->prnFirst;
		prnDest = prnSrc->prnNext;
	}
	else {
		prnDest = pri->prnFirst;
		prnSrc = prnDest->prnNext;
	}

	/*
	 * If files of the same name exist in both places, the sync engine thinks
     * they need to be merged (since we have no persistent briefcase database,
     * it doesn't know that they were originally the same).  The sync engine
     * sets the file stamp of a copied destination file to the file stamp of
     * the source file after copying.  If the file stamps of two files to be
     * merged are the same, we assume that the files are already up-to-date,
     * and we take no reconciliation action.  If the file stamps of two files
     * to be merged are different, we really just want a copy, so we figure out
     * which one is supposed to be definitive and transform the RECITEM and
     * RECNODEs to indicate a copy instead of a merge.
	 *
	 * The definitive copy is the source for mandatory or logoff cases,
	 * otherwise it's the newer file.
	 */
	if (pri->riaction == RIA_MERGE || pri->riaction == RIA_BROKEN_MERGE) {
		BOOL fCopyFromSrc;
        COMPARISONRESULT cr;

		if (pSyncState->IsMandatory())
			fCopyFromSrc = TRUE;
        else {
	        fCopyFromSrc = ! pSyncState->IsLogon();  

            if (pSyncState->CompareFileStamps(&prnSrc->fsCurrent, &prnDest->fsCurrent, &cr) == TR_SUCCESS) {
                if (cr == CR_EQUAL) {
#ifdef MAXDEBUG
			       ::OutputDebugString("Matching file stamps, no action taken\r\n");  
#endif
                   return;
                }
                else if (cr==CR_FIRST_LARGER)       
			       fCopyFromSrc = TRUE;
            }
        }

#ifdef MAXDEBUG
		if (fCopyFromSrc)
			::OutputDebugString("Broken merge, copying from src\r\n");
		else
			::OutputDebugString("Broken merge, copying from dest\r\n");
#endif

		prnSrc->rnaction = fCopyFromSrc ? RNA_COPY_FROM_ME : RNA_COPY_TO_ME;
		prnDest->rnaction = fCopyFromSrc ? RNA_COPY_TO_ME : RNA_COPY_FROM_ME;
		pri->riaction = RIA_COPY;
	}

	/*
	 * If the preferred source file doesn't exist, the sync engine is trying
	 * to create a file to make the two trees the same, when the user/admin
	 * really wanted to delete it (the sync engine doesn't like deleting
	 * files).  So we detect that case here and delete the "destination"
	 * to make the two trees match that way.
	 *
	 * If the last reconciliation had an error, we don't do the deletion
	 * if the site of the error is the current source (i.e., if we're
	 * about to delete the file we couldn't copy before).  Instead we'll
	 * try the operation that the sync engine wants, since that'll be the
	 * copy that failed before.
	 */
	if (prnSrc->rnstate == RNS_DOES_NOT_EXIST &&
		prnSrc->rnaction == RNA_COPY_TO_ME &&
		!((pSyncState->uiRecError & RECERROR_CENTRAL) && fSrcCentral) &&
		!((pSyncState->uiRecError & RECERROR_LOCAL) && !fSrcCentral)) {
		if (IS_EMPTY_STRING(pri->pcszName)) {
			::RemoveDirectory(prnDest->pcszFolder);
		}
		else {
			NLS_STR nlsTemp(prnDest->pcszFolder);
			AddBackslash(nlsTemp);
			nlsTemp.strcat(pri->pcszName);
			if (!nlsTemp.QueryError()) {
#ifdef MAXDEBUG
				if (pSyncState->IsMandatory())
					::OutputDebugString("Mandatory copy wrong way\r\n");

				wsprintf(::szOutbuf, "Deleting 'destination' file %s\r\n", nlsTemp.QueryPch());
				::OutputDebugString(::szOutbuf);
#endif
				::DeleteFile(nlsTemp.QueryPch());
			}
		}
		return;
	}

#ifdef MAXDEBUG
	::OutputDebugString("Calling ReconcileItem.\r\n");
#endif

	TWINRESULT tr;
	if ((tr=pSyncState->ReconcileItem(pri, NULL, 0, 0, NULL, NULL)) != TR_SUCCESS) {
		ReportReconcileError(pSyncState, tr, pri, prnSrc, prnDest, fSrcCentral);
#ifdef MAXDEBUG
		::wsprintf(::szOutbuf, "Error %d from ReconcileItem.\r\n", tr);
		::OutputDebugString(::szOutbuf);
#endif
	}
	else if (!IS_EMPTY_STRING(pri->pcszName))
		pSyncState->dwFlags |= SYNCSTATE_SOMESUCCESS;
}


/*
 * MakePathAbsolute examines a path to see whether it is absolute or relative.
 * If it is relative, it is prepended with the given base path.
 *
 * If the fMustBeRelative parameter is TRUE, then an error is returned if the
 * path was (a) absolute and (b) not a subdirectory of the old profile directory.
 */
BOOL MakePathAbsolute(NLS_STR& nlsDir, LPCSTR lpszBasePath,
					  NLS_STR& nlsOldProfileDir, BOOL fMustBeRelative)
{
	/* If the path starts with a special keyword, replace it. */

	if (*nlsDir.QueryPch() == '*') {
		return ReplaceCommonPath(nlsDir);
	}

	/* If the path is absolute and is relative to whatever the old profile
	 * directory was, transform it to a relative path.  We will then make
	 * it absolute again, using the new base path.
	 */
	if (PrefixMatch(nlsDir, nlsOldProfileDir)) {
		UINT cchDir = nlsDir.strlen();
		LPSTR lpStart = nlsDir.Party();
		::memmovef(lpStart, lpStart + nlsOldProfileDir.strlen(), cchDir - nlsOldProfileDir.strlen() + 1);
		nlsDir.DonePartying();
	}
	else if (::strchrf(nlsDir.QueryPch(), ':') != NULL || *nlsDir.QueryPch() == '\\')
		return !fMustBeRelative;

	if (*lpszBasePath == '\0') {
		nlsDir = lpszBasePath;
		return TRUE;
	}

	NLS_STR nlsBasePath(lpszBasePath);
	if (nlsBasePath.QueryError())
		return FALSE;
	AddBackslash(nlsBasePath);

	ISTR istrStart(nlsDir);
	nlsDir.InsertStr(nlsBasePath, istrStart);
	return !nlsDir.QueryError();
}
#endif  /**** 0 ****/


/*
 * ReplaceCommonPath takes a relative path beginning with a special keyword
 * and replaces the keyword with the corresponding real path.  Currently the
 * keyword supported is:
 *
 * *windir - replaced with the Windows (user) directory
 */
BOOL ReplaceCommonPath(NLS_STR& nlsDir)
{
	NLS_STR *pnlsTemp;
	ISTR istrStart(nlsDir);
	ISTR istrEnd(nlsDir);

	nlsDir.strchr(&istrEnd, '\\');
	pnlsTemp = nlsDir.QuerySubStr(istrStart, istrEnd);
	if (pnlsTemp == NULL)
		return FALSE;				/* out of memory, can't do anything */

	BOOL fSuccess = TRUE;
	if (!::stricmpf(pnlsTemp->QueryPch(), ::szWindirAlias)) {
		UINT cbBuffer = pnlsTemp->QueryAllocSize();
		LPSTR lpBuffer = pnlsTemp->Party();
		UINT cchWindir = ::GetWindowsDirectory(lpBuffer, cbBuffer);
		if (cchWindir >= cbBuffer)
			*lpBuffer = '\0';
		pnlsTemp->DonePartying();
		if (cchWindir >= cbBuffer) {
			pnlsTemp->realloc(cchWindir+1);
			if (!pnlsTemp->QueryError()) {
				::GetWindowsDirectory(pnlsTemp->Party(), cchWindir+1);
				pnlsTemp->DonePartying();
			}
			else
				fSuccess = FALSE;
		}
		if (fSuccess) {
			nlsDir.ReplSubStr(*pnlsTemp, istrStart, istrEnd);
			fSuccess = !nlsDir.QueryError();
		}
	}
	delete pnlsTemp;
	return fSuccess;
}


/*
 * GetSetRegistryPath goes to the registry key and value specified by
 * the current reconciliations's RegKey and RegValue settings, and
 * retrieves or sets a path there.
 */
void GetSetRegistryPath(HKEY hkeyProfile, RegEntry& re, NLS_STR *pnlsPath, BOOL fSet)
{
	NLS_STR nlsKey;

	re.GetValue(::szReconcileRegKey, &nlsKey);
	if (nlsKey.strlen() > 0) {
		NLS_STR nlsValue;
		re.GetValue(::szReconcileRegValue, &nlsValue);
		RegEntry re2(nlsKey, hkeyProfile);
		if (fSet) {
			re2.SetValue(nlsValue, pnlsPath->QueryPch());
            if (!nlsKey.stricmp("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders")) {
                nlsKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
                RegEntry reShell(nlsKey, hkeyProfile);
                reShell.SetValue(nlsValue, pnlsPath->QueryPch());
            }
        }
		else
			re2.GetValue(nlsValue, pnlsPath);
	}
}


/* CopyFolder calls the shell's copy engine to copy files.  The source is a
 * double-null-terminated list;  the destination is a folder.
 */
void CopyFolder(LPBYTE pbSource, LPCSTR pszDest)
{
    CHAR szDest[MAX_PATH];

    ::strcpyf(szDest, pszDest);
    szDest[::strlenf(szDest) + 1] = '\0';

    SHFILEOPSTRUCT fos;

    fos.hwnd = NULL;
    fos.wFunc = FO_COPY;
    fos.pFrom = (LPCSTR)pbSource;
    fos.pTo = szDest;
    fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
    fos.fAnyOperationsAborted = FALSE;
    fos.hNameMappings = NULL;
    fos.lpszProgressTitle = NULL;

    g_pfnSHFileOperationA(&fos);
}


/*
 * ReconcileKey performs reconciliation for a particular key in the
 * ProfileReconciliation branch of the registry.  It reads the config
 * parameters for the reconciliation, sets up an appropriate twin in
 * the temporary briefcase, and performs the reconciliation.
 */
BOOL ReconcileKey(HKEY hkeySection, LPCSTR lpszSubKey, SYNCSTATE *pSyncState)
{
#ifdef DEBUG
	DWORD dwStart = ::GetTickCount();
#endif

	BOOL fShouldDelete = FALSE;

	RegEntry re(lpszSubKey, hkeySection);
	if (re.GetError() == ERROR_SUCCESS) {
        BUFFER bufSrcStrings(MAX_PATH);
        NLS_STR nlsSrcPath(MAX_PATH);
        NLS_STR nlsDestPath(MAX_PATH);
        NLS_STR nlsName(MAX_PATH);
        if (bufSrcStrings.QueryPtr() != NULL &&
            nlsSrcPath.QueryError() == ERROR_SUCCESS &&
            nlsDestPath.QueryError() == ERROR_SUCCESS &&
            nlsName.QueryError() == ERROR_SUCCESS) {

            /* Get the source path to copy.  Usually it's in the profile,
             * left over from the profile we cloned.  If not, we take the
             * default local name from the ProfileReconciliation key.  If
             * the path already in the registry is not relative to the cloned
             * profile directory, then it's probably set by system policies
             * or something, and we shouldn't touch it.
             */
            if (pSyncState->pnlsOtherProfilePath != NULL) {
        		GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsSrcPath, FALSE);
                if (nlsSrcPath.strlen() && 
                    !PrefixMatch(nlsSrcPath.QueryPch(), pSyncState->pnlsOtherProfilePath->QueryPch())) {
                    return FALSE;   /* not profile-relative, nothing to do */
                }
            }
            if (!nlsSrcPath.strlen()) {
				re.GetValue(::szDefaultDir, &nlsSrcPath);
            	if (*nlsSrcPath.QueryPch() == '*') {
            		ReplaceCommonPath(nlsSrcPath);
            	}
            }

            /* Get the set of files to copy.  Like NT and unlike win95, we
             * want to clone the entire contents, not necessarily just the
             * files listed (for example, the desktop -- we want all the
             * files and subfolders, not just links).  So, unless the string
             * is empty, which means don't copy any content, just set the reg
             * path, we change any pattern containing wildcards to *.*.
             */
            re.GetValue(::szReconcileName, &nlsName);
            if (nlsName.strlen()) {
                if (::strchrf(nlsName.QueryPch(), '*') != NULL ||
                    ::strchrf(nlsName.QueryPch(), '?') != NULL) {
                    nlsName = "*.*";
                }
            }

            /* Get the destination path.  This is generated from the new
             * profile directory and the LocalFile entry in the registry.
             *
             * Should always do this, even if we're not going to call the
             * copy engine, because we're going to write this path out to
             * the registry.
             */
            re.GetValue(::szLocalFile, &nlsDestPath);
            ISTR istr(nlsDestPath);
            nlsDestPath.InsertStr(*(pSyncState->pnlsProfilePath), istr);

            /* Always create the destination path, even if we don't copy
             * any files into it because the source directory doesn't exist.
             */
            CreateDirectoryPath(nlsDestPath.QueryPch());

            /* Make sure the source directory exists so we won't get useless
             * error messages from the shell copy engine.
             */
            DWORD dwAttr = GetFileAttributes(nlsSrcPath.QueryPch());
            if (dwAttr != 0xffffffff && (dwAttr & FILE_ATTRIBUTE_DIRECTORY) &&
                nlsName.strlen()) {

                AddBackslash(nlsSrcPath);

                /* Build up the double-null-terminated list of file specs to copy. */

                UINT cbUsed = 0;

		    	LPSTR lpName = nlsName.Party();
			    do {
				    LPSTR lpNext = ::strchrf(lpName, ',');
    				if (lpNext != NULL) {
	    				*(lpNext++) = '\0';
		    		}

                    UINT cbNeeded = nlsSrcPath.strlen() + ::strlenf(lpName) + 1;
                    if (bufSrcStrings.QuerySize() - cbUsed < cbNeeded) {
                        if (!bufSrcStrings.Resize(bufSrcStrings.QuerySize() + MAX_PATH))
                            return FALSE;
                    }
                    LPSTR lpDest = ((LPSTR)bufSrcStrings.QueryPtr()) + cbUsed;
                    ::strcpyf(lpDest, nlsSrcPath.QueryPch());
                    lpDest += nlsSrcPath.strlen();
                    ::strcpyf(lpDest, lpName);
                    cbUsed += cbNeeded;

    				lpName = lpNext;
	    		} while (lpName != NULL);

                *((LPSTR)bufSrcStrings.QueryPtr() + cbUsed) = '\0';    /* double null terminate */
	    		nlsName.DonePartying();

                CopyFolder((LPBYTE)bufSrcStrings.QueryPtr(), nlsDestPath.QueryPch());
            }

    		/*
		     * Set a registry key to point to the new local path to this directory.
	    	 */
    		GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsDestPath, TRUE);
		}
    }

#ifdef MAXDEBUG
	::wsprintf(::szOutbuf, "ReconcileKey duration %d ms.\r\n", ::GetTickCount() - dwStart);
	::OutputDebugString(::szOutbuf);
#endif

	return fShouldDelete;
}


/*
 * GetMaxSubkeyLength just calls RegQueryInfoKey to get the length of the
 * longest named subkey of the given key.  The return value is the size
 * of buffer needed to hold the longest key name, including the null
 * terminator.
 */
DWORD GetMaxSubkeyLength(HKEY hKey)
{
	DWORD cchClass = 0;
	DWORD cSubKeys;
	DWORD cchMaxSubkey;
	DWORD cchMaxClass;
	DWORD cValues;
	DWORD cchMaxValueName;
	DWORD cbMaxValueData;
	DWORD cbSecurityDescriptor;
	FILETIME ftLastWriteTime;

	RegQueryInfoKey(hKey, NULL, &cchClass, NULL, &cSubKeys, &cchMaxSubkey,
					&cchMaxClass, &cValues, &cchMaxValueName, &cbMaxValueData,
					&cbSecurityDescriptor, &ftLastWriteTime);
	return cchMaxSubkey + 1;
}


/*
 * ReconcileSection walks through the ProfileReconciliation key and performs
 * reconciliation for each subkey.  One-time keys are deleted after they are
 * processed.
 */
void ReconcileSection(HKEY hkeyRoot, SYNCSTATE *pSyncState)
{
	NLS_STR nlsKeyName(GetMaxSubkeyLength(hkeyRoot));
	if (!nlsKeyName.QueryError()) {
		DWORD iKey = 0;

		for (;;) {
			DWORD cchKey = nlsKeyName.QueryAllocSize();

			UINT err = ::RegEnumKey(hkeyRoot, iKey, nlsKeyName.Party(), cchKey);
			if (err != ERROR_SUCCESS)
				break;

			nlsKeyName.DonePartying();
			if (ReconcileKey(hkeyRoot, nlsKeyName, pSyncState)) {
				::RegDeleteKey(hkeyRoot, nlsKeyName.QueryPch());
			}
			else
				iKey++;
		}
	}
}


/*
 * ReconcileFiles is called just after the user's profile and policies are
 * loaded at logon, and just before the profile is unloaded at logoff.  It
 * performs all file type reconciliation for the user's profile, excluding
 * the profile itself, of course.
 *
 * nlsOtherProfilePath is the path to the profile which is being cloned,
 * or an empty string if the default profile is being cloned.
 */
HRESULT ReconcileFiles(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
                    NLS_STR& nlsOtherProfilePath)
{
    HRESULT hres = LoadShellEntrypoint();
    if (FAILED(hres))
        return hres;

    if (nlsOtherProfilePath.strlen())
    {
    	ISTR istrBackslash(nlsOtherProfilePath);
	    if (nlsOtherProfilePath.strrchr(&istrBackslash, '\\')) {
            ++istrBackslash;
		    nlsOtherProfilePath.DelSubStr(istrBackslash);
        }
    }

	RegEntry re(::szReconcileRoot, hkeyProfile);
	if (re.GetError() == ERROR_SUCCESS) {
        SYNCSTATE s;
        s.hkeyProfile = hkeyProfile;
        s.pnlsProfilePath = &nlsProfilePath;
        s.pnlsOtherProfilePath = (nlsOtherProfilePath.strlen() != 0) ? &nlsOtherProfilePath : NULL;
        s.hkeyPrimary = NULL;

		RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
		RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
		if (rePrimary.GetError() == ERROR_SUCCESS) {
			ReconcileSection(rePrimary.GetKey(), &s);

			if (reSecondary.GetError() == ERROR_SUCCESS) {
                s.hkeyPrimary = rePrimary.GetKey();
				ReconcileSection(reSecondary.GetKey(), &s);
			}
		}
	}

	return ERROR_SUCCESS;
}


HRESULT DefaultReconcileKey(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
                            LPCSTR pszKeyName, BOOL fSecondary)
{
    HRESULT hres = LoadShellEntrypoint();
    if (FAILED(hres))
        return hres;

	RegEntry re(::szReconcileRoot, hkeyProfile);
	if (re.GetError() == ERROR_SUCCESS) {
        SYNCSTATE s;
        s.hkeyProfile = hkeyProfile;
        s.pnlsProfilePath = &nlsProfilePath;
        s.pnlsOtherProfilePath = NULL;
        s.hkeyPrimary = NULL;

		RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
		if (rePrimary.GetError() == ERROR_SUCCESS) {
            if (fSecondary) {
        		RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
                s.hkeyPrimary = rePrimary.GetKey();
    			ReconcileKey(reSecondary.GetKey(), pszKeyName, &s);
            }
            else
    			ReconcileKey(rePrimary.GetKey(), pszKeyName, &s);
		}
	}

	return ERROR_SUCCESS;
}


HRESULT DeleteProfileFiles(LPCSTR pszPath)
{
    HRESULT hres = LoadShellEntrypoint();
    if (FAILED(hres))
        return hres;

    SHFILEOPSTRUCT fos;
    TCHAR szFrom[MAX_PATH];

    lstrcpy(szFrom, pszPath);

    /* Before we build the complete source filespec, check to see if the
     * directory exists.  In the case of lesser-used folders such as
     * "Application Data", the default may not have ever been created.
     * In that case, we have no contents to copy.
     */
    DWORD dwAttr = GetFileAttributes(szFrom);
    if (dwAttr == 0xffffffff || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
        return S_OK;

    AddBackslash(szFrom);
    lstrcat(szFrom, TEXT("*.*"));
    szFrom[lstrlen(szFrom)+1] = '\0';   /* double null terminate from string */

    fos.hwnd = NULL;
    fos.wFunc = FO_DELETE;
    fos.pFrom = szFrom;
    fos.pTo = NULL;
    fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
    fos.fAnyOperationsAborted = FALSE;
    fos.hNameMappings = NULL;
    fos.lpszProgressTitle = NULL;

    g_pfnSHFileOperationA(&fos);

    ::RemoveDirectory(pszPath);

	return NOERROR;
}


HRESULT DeleteProfile(LPCSTR pszName)
{
	RegEntry re(::szProfileList, HKEY_LOCAL_MACHINE);

	HRESULT hres;

	if (re.GetError() == ERROR_SUCCESS) {
		{	/* extra scope for next RegEntry */
			RegEntry reUser(pszName, re.GetKey());
			if (reUser.GetError() == ERROR_SUCCESS) {
				NLS_STR nlsPath(MAX_PATH);
				if (nlsPath.QueryError() == ERROR_SUCCESS) {
					reUser.GetValue(::szProfileImagePath, &nlsPath);
					if (reUser.GetError() == ERROR_SUCCESS) {
						hres = DeleteProfileFiles(nlsPath.QueryPch());
					}
					else
						hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);

				}
				else
					hres = HRESULT_FROM_WIN32(nlsPath.QueryError());
			}
			else
				hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
		}
		if (SUCCEEDED(hres)) {
		    ::RegDeleteKey(re.GetKey(), pszName);
            NLS_STR nlsOEMName(pszName);
			if (nlsOEMName.QueryError() == ERROR_SUCCESS) {
			    nlsOEMName.strupr();
			    nlsOEMName.ToOEM();
    			::DeletePasswordCache(nlsOEMName.QueryPch());
            }
		}
	}
	else
		hres = E_UNEXPECTED;

	return hres;
}
