/* Copyright (c) 1992-2001, Microsoft Corporation, all rights reserved ** ** pcache.c ** Remote Access Phonebook - Win9x Password cache (PWL) decrypter ** Main routines ** ** Portions of this code have been ported from: ** Win9x\proj\net\user\src\WNET\PCACHE ** ** Whistler bug: 208318 Win9x Upg: Username and Password for DUN connectoid not ** migrated from Win9x to Whistler ** ** 06/24/92 gregj ** 03/06/01 Jeff Sigman */ #include "pch.h" // Pre-compiled #include "pcache.h" // Private pcache header #include // RSA RC4 MD5 library #include // RSA RC4 MD5 library //---------------------------------------------------------------------------- // Globals //---------------------------------------------------------------------------- CHAR g_szPWLUsername[ UNLEN + 1 ]; CHAR g_szPWLPassword[ PWLEN + 1 ]; HANDLE g_hFile = NULL; RC4_KEYSTRUCT g_ks; NEW_PLAINTEXT_HEADER g_hdrPlaintext; NEW_ENCRYPTED_HEADER g_hdrEncrypted; //----------------------------------------------------------------------------- // Routines //----------------------------------------------------------------------------- UINT HashName ( const CHAR* pbResource, WORD cbResource ) { return cbResource ? ( ( *pbResource ) % BUCKET_COUNT ) : 0; } VOID Encrypt ( CHAR *pbSource, WORD cbSource, CHAR *pbDest ) { if ( pbDest ) { memcpy ( pbDest, pbSource, cbSource ); pbSource = pbDest; } rc4 ( &g_ks, cbSource, pbSource ); return; } VOID Decrypt ( CHAR *pbSource, WORD cbSource ) { Encrypt ( pbSource, cbSource, NULL ); return; } VOID ENCRYPTER ( const CHAR* pszUsername, const CHAR* pszPassword, UINT iBucket, DWORD dwSalt ) { UCHAR md5_hash[ 16 ]; MD5_CTX ctxBucketNumber; MD5_CTX ctx; MD5Init ( &ctxBucketNumber ); MD5Update ( &ctxBucketNumber, (UCHAR* )&iBucket, sizeof( iBucket ) ); MD5Update ( &ctxBucketNumber, (UCHAR* )pszUsername, strlen ( pszUsername ) + 1 ); MD5Update ( &ctxBucketNumber, (UCHAR* )&dwSalt, sizeof( dwSalt ) ); MD5Final ( &ctxBucketNumber ); MD5Init ( &ctx ); MD5Update ( &ctx, (UCHAR* )pszPassword, strlen ( pszPassword ) + 1 ); MD5Update ( &ctx, (UCHAR* )ctxBucketNumber.digest, sizeof( ctxBucketNumber.digest ) ); MD5Final ( &ctx ); memcpy ( md5_hash, ctx.digest, sizeof( md5_hash ) ); memset ( (CHAR * )&ctx, '\0', sizeof( ctx )); memset ( (CHAR * )&ctxBucketNumber, '\0', sizeof( ctxBucketNumber ) ); rc4_key ( &g_ks, sizeof( md5_hash ), (UCHAR * )&md5_hash ); } DWORD ReadData ( WORD ibSeek, PVOID pbBuffer, WORD cbBuffer ) { DWORD dwErr = ERROR_SUCCESS; DWORD cbRead = 0; if ( ( SetFilePointer ( g_hFile, ibSeek, NULL, FILE_BEGIN ) == 0xffffffff ) || ( !ReadFile ( g_hFile, pbBuffer, cbBuffer, &cbRead, NULL ) ) ) { return GetLastError ( ); } return ( cbRead < cbBuffer ) ? IERR_CacheCorrupt : ERROR_SUCCESS; } VOID AssembleFindCacheName ( CHAR* pszWindir, CHAR* pszResult ) { CHAR szFind[ 6 ]; CHAR* Current = szFind; DWORD i; strncpy ( pszResult, pszWindir, MAX_PATH - strlen(pszResult) ); strncat ( pszResult, S_PWLDIR, MAX_PATH - strlen(pszResult) ); strncat ( pszResult, "\\", MAX_PATH - strlen(pszResult) ); for ( i = 0; (i < 5) && (i < strlen(g_szPWLUsername)); i++ ) { *(Current++) = g_szPWLUsername[ i ]; *Current = '\0'; } if ( Current != szFind ) { strncat ( pszResult, szFind, MAX_PATH - strlen(pszResult) ); } strncat ( pszResult, S_SRCHPWL, MAX_PATH - strlen(pszResult) ); } DWORD OpenCacheFile ( VOID ) { CHAR szFind[ MAX_PATH + 1 ]; CHAR szWindir[ MAX_PATH + 1 ]; CHAR szFilename[ MAX_PATH + 1 ]; DWORD dwErr; HANDLE hFile; do { if ( !GetWindowsDirectoryA (szWindir, sizeof(szWindir) ) ) { dwErr = ERROR_FILE_NOT_FOUND; break; } AssembleFindCacheName ( szWindir, szFind ); DEBUGMSGA ((S_DBG_RAS, "AssembleFindCacheName: %s", szFind)); dwErr = FindNewestFile ( szFind ); BREAK_ON_DWERR( dwErr ); strcpy ( szFilename, szWindir ); strcat ( szFilename, S_PWLDIR ); strcat ( szFilename, szFind ); DEBUGMSGA ((S_DBG_RAS, "FindNewestFile: %s", szFind)); hFile = CreateFileA ( szFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) { dwErr = GetLastError ( ); break; } DEBUGMSGA ((S_DBG_RAS, "CreateFileA: %s", szFilename)); g_hFile = hFile; } while ( FALSE ); return dwErr; } DWORD ReadAndDecrypt ( WORD ibSeek, PVOID pbBuffer, WORD cbBuffer ) { DWORD dwErr = ReadData ( ibSeek, pbBuffer, cbBuffer ); if ( dwErr ) { return dwErr; } Decrypt ( (CHAR* )pbBuffer, cbBuffer ); return ERROR_SUCCESS; } INT CompareCacheNames ( const CHAR* pbRes1, WORD cbRes1, const CHAR* pbRes2, WORD cbRes2 ) { INT nRet = memcmp ( pbRes1, pbRes2, min ( cbRes1, cbRes2 ) ); // DEBUGMSGA ((S_DBG_RAS, "CompareCacheNames")); // DEBUGMSGA ((S_DBG_RAS, "1 - %s", pbRes1)); // DEBUGMSGA ((S_DBG_RAS, "2 - %s", pbRes2)); if (nRet != 0) { return nRet; } return ( cbRes1 < cbRes2 ) ? -1 : ( ( cbRes1 == cbRes2 ) ? 0 : 1 ); } DWORD LoadEncryptedHeader ( VOID ) { const UINT cbFirst = FIELDOFFSET ( NEW_ENCRYPTED_HEADER, aibBuckets ); const UINT IBUCKET_HEADER = 0xffffffff; DWORD dwErr = ERROR_SUCCESS; do { ENCRYPTER ( g_szPWLUsername, g_szPWLPassword, IBUCKET_HEADER, g_hdrPlaintext.adwBucketSalts[ BUCKET_COUNT ] ); dwErr = ReadAndDecrypt ( (WORD )g_hdrPlaintext.cbHeader, &g_hdrEncrypted, (WORD )cbFirst ); BREAK_ON_DWERR( dwErr ); // All aibBuckets except the first and last are stored in the file // dwErr = ReadAndDecrypt ( (WORD )g_hdrPlaintext.cbHeader + cbFirst, (LPSTR )( &g_hdrEncrypted.aibBuckets[ 1 ] ), sizeof( g_hdrEncrypted.aibBuckets ) - ( sizeof( g_hdrEncrypted.aibBuckets[ 0 ] ) * 2) ); BREAK_ON_DWERR( dwErr ); // Generate the first and last aibBuckets values on the fly // g_hdrEncrypted.aibBuckets[ 0 ] = (USHORT )( g_hdrPlaintext.cbHeader + sizeof( NEW_ENCRYPTED_HEADER ) - sizeof( g_hdrEncrypted.aibBuckets[ 0 ] ) * 2 ); g_hdrEncrypted.aibBuckets[ BUCKET_COUNT ] = (USHORT )GetFileSize ( g_hFile, NULL ); } while ( FALSE ); return dwErr; } DWORD LoadPlaintextHeader ( VOID ) { DWORD dwErr = ReadData ( 0, &g_hdrPlaintext, sizeof( g_hdrPlaintext ) ); if ( dwErr ) { return dwErr; } if ( g_hdrPlaintext.ulSig != NEW_PLAINTEXT_SIGNATURE ) { return ERROR_SUCCESS; // no key blobs, for sure } // If there are any key blobs, read them all in a chunk (the remainder of // the header) Otherwise we've already got the whole thing // if ( g_hdrPlaintext.cbHeader > sizeof( g_hdrPlaintext ) ) { return ReadData ( sizeof( g_hdrPlaintext ), ((CHAR* )&g_hdrPlaintext) + sizeof( g_hdrPlaintext ), ((WORD )g_hdrPlaintext.cbHeader) - sizeof( g_hdrPlaintext ) ); } else { return ERROR_SUCCESS; } } DWORD LookupEntry ( const CHAR* pbResource, WORD cbResource, UCHAR nType, PASSWORD_CACHE_ENTRY** ppce ) { UINT iBucket = HashName ( pbResource, cbResource ); WORD ibEntry = g_hdrEncrypted.aibBuckets[ iBucket ]; // offs of 1st entry WORD cbEntry; DWORD dwErr; PASSWORD_CACHE_ENTRY* pce = NULL; ENCRYPTER ( g_szPWLUsername, g_szPWLPassword, iBucket, g_hdrPlaintext.adwBucketSalts[ iBucket ] ); dwErr = ReadAndDecrypt ( ibEntry, &cbEntry, sizeof( cbEntry ) ); if ( dwErr ) { return dwErr; } ibEntry += sizeof( cbEntry ); if ( !cbEntry ) { return IERR_CacheEntryNotFound; } pce = ( PASSWORD_CACHE_ENTRY* ) LocalAlloc ( LMEM_FIXED, MAX_ENTRY_SIZE + sizeof( cbEntry ) ); if ( pce == NULL ) { return ERROR_NOT_ENOUGH_MEMORY; } while ( !( cbEntry & PCE_END_MARKER ) ) { if ( cbEntry > MAX_ENTRY_SIZE ) { dwErr = IERR_CacheCorrupt; break; } dwErr = ReadAndDecrypt ( ibEntry, ((CHAR* )pce) + sizeof( cbEntry ), cbEntry ); BREAK_ON_DWERR( dwErr ); pce->cbEntry = cbEntry; // we read this earlier, set it manually // DEBUGMSGA ((S_DBG_RAS, "LookupEntry: Searching for %s", pbResource)); if (nType == pce->nType && !CompareCacheNames ( pbResource, cbResource, pce->abResource, pce->cbResource )) { DEBUGMSGA ((S_DBG_RAS, "LookupEntry: Match Found")); break; // dwErr == ERROR_SUCCESS } ibEntry += cbEntry; cbEntry = NEXT_PCE(pce)->cbEntry; // fetch next entry's length } if ( ( cbEntry & PCE_END_MARKER ) || dwErr != ERROR_SUCCESS ) { LocalFree ( pce ); pce = NULL; DEBUGMSGA ((S_DBG_RAS, "LookupEntry: Nothing Found")); return ( cbEntry & PCE_END_MARKER ) ? IERR_CacheEntryNotFound : dwErr; } *ppce = pce; return ERROR_SUCCESS; } DWORD ValidateEncryptedHeader ( VOID ) { MD5_CTX ctx; MD5Init ( &ctx ); MD5Update ( &ctx, (UCHAR* )g_szPWLUsername, strlen( g_szPWLUsername ) + 1 ); MD5Update ( &ctx, (UCHAR* )g_hdrEncrypted.abRandomPadding, sizeof( g_hdrEncrypted.abRandomPadding ) ); MD5Final ( &ctx ); if ( memcmp ( ctx.digest, g_hdrEncrypted.abAuthenticationHash, sizeof( ctx.digest ) ) ) { return IERR_IncorrectUsername; } return ERROR_SUCCESS; } DWORD FindPWLResource ( const CHAR* pbResource, WORD cbResource, CHAR* pbBuffer, WORD cbBuffer, UCHAR nType ) { DWORD dwErr = ERROR_SUCCESS; CACHE_ENTRY_INFO* pcei = (CACHE_ENTRY_INFO* )pbBuffer; PASSWORD_CACHE_ENTRY* pce = NULL; do { if ( cbBuffer < sizeof( CACHE_ENTRY_INFO ) ) { dwErr = ERROR_INSUFFICIENT_BUFFER; break; } dwErr = LookupEntry ( pbResource, cbResource, nType, &pce ); BREAK_ON_DWERR( dwErr ); pcei->cbResource = pce->cbResource; pcei->cbPassword = pce->cbPassword; pcei->iEntry = pce->iEntry; pcei->nType = pce->nType; pcei->dchResource = 0; pcei->dchPassword = 0; cbBuffer -= sizeof( CACHE_ENTRY_INFO ); if ( pce->cbResource > cbBuffer ) { dwErr = ERROR_MORE_DATA; break; } pcei->dchResource = sizeof( CACHE_ENTRY_INFO ); memcpy ( pbBuffer + pcei->dchResource, pce->abResource, pce->cbResource ); cbBuffer -= pce->cbResource; if ( pce->cbPassword > cbBuffer ) { dwErr = ERROR_MORE_DATA; break; } pcei->dchPassword = pcei->dchResource + pcei->cbResource; memcpy ( pbBuffer + pcei->dchPassword, pce->abResource + pce->cbResource, pce->cbPassword ); } while ( FALSE ); // // Clean up // if ( pce ) { LocalFree ( pce ); pce = NULL; } return dwErr; } DWORD FindNewestFile ( IN OUT CHAR* SourceName ) { CHAR szCurFile[ MAX_PATH + 1 ]; HANDLE SourceHandle; LARGE_INTEGER SourceFileTime, NextFileTime; WIN32_FIND_DATAA SourceFileData; SourceHandle = FindFirstFileA ( SourceName, &SourceFileData ); if ( INVALID_HANDLE_VALUE == SourceHandle ) { return ERROR_FILE_NOT_FOUND; } SourceFileTime.LowPart = SourceFileData.ftLastWriteTime.dwLowDateTime; SourceFileTime.HighPart = SourceFileData.ftLastWriteTime.dwHighDateTime; strcpy ( szCurFile, SourceFileData.cFileName ); do { if ( !FindNextFileA (SourceHandle, &SourceFileData) ) { break; } NextFileTime.LowPart = SourceFileData.ftLastWriteTime.dwLowDateTime; NextFileTime.HighPart = SourceFileData.ftLastWriteTime.dwHighDateTime; if ( NextFileTime.QuadPart > SourceFileTime.QuadPart ) { SourceFileTime.LowPart = NextFileTime.LowPart; SourceFileTime.HighPart = NextFileTime.HighPart; strcpy ( szCurFile, SourceFileData.cFileName ); } } while ( TRUE ); strcpy ( SourceName, "\\" ); strcat ( SourceName, szCurFile ); // // Clean up // FindClose ( SourceHandle ); return ERROR_SUCCESS; } VOID DeleteAllPwls ( VOID ) { CHAR szWindir[ MAX_PATH + 1 ]; PCSTR pszPath = NULL; DEBUGMSGA ((S_DBG_RAS, "DeleteAllPwls")); do { // // Whistler bug: 427175 427176 PREFIX // if ( !GetWindowsDirectoryA ( szWindir, MAX_PATH ) ) {break;} DEBUGMSGA ((S_DBG_RAS, "GetWindowsDirectoryA %s", szWindir )); pszPath = JoinPathsA (szWindir, S_PWLDIR); if (!pszPath) {break;} if (DeleteDirectoryContentsA (pszPath)) { if (RemoveDirectoryA (pszPath)) { DEBUGMSGA ((S_DBG_RAS, "DeleteAllPwls: Success!")); } ELSE_DEBUGMSGA ((S_DBG_RAS, "Could not delete the tree %s.", pszPath)); } ELSE_DEBUGMSGA ((S_DBG_RAS, "Could not delete the contents of %s.", pszPath)); } while ( FALSE ); // // Clean up // if (pszPath) { FreePathStringA (pszPath); } return; } // // Whistler bug: 417745 INTL:Win9x Upg: DBCS chars cause User,Domain, // Passwrds to not be migrated for DUN // BOOL StrCpyAFromWUsingAnsiEncoding( LPSTR pszDst, LPCWSTR pszSrc, DWORD dwDstChars ) { DWORD cb; cb = WideCharToMultiByte( CP_ACP, 0, pszSrc, -1, pszDst, dwDstChars, NULL, NULL ); if (cb == 0) { DEBUGMSGA ((S_DBG_RAS, "StrCpyAFromWUsingAnsiEncoding fail")); return TRUE; } // Success return FALSE; } // // Whistler bug: 417745 INTL:Win9x Upg: DBCS chars cause User,Domain, // Passwrds to not be migrated for DUN // BOOL StrCpyWFromAUsingAnsiEncoding( WCHAR* pszDst, LPCSTR pszSrc, DWORD dwDstChars ) { DWORD cb; *pszDst = L'\0'; cb = MultiByteToWideChar( CP_ACP, 0, pszSrc, -1, pszDst, dwDstChars ); if (cb == 0) { DEBUGMSGA ((S_DBG_RAS, "StrCpyWFromAUsingAnsiEncoding fail")); return TRUE; } // Success return FALSE; } VOID CopyAndTruncate ( LPSTR lpszDest, LPCSTR lpszSrc, UINT cbDest, BOOL flag ) { strncpy ( lpszDest, lpszSrc, cbDest - 1 ); // // strncpyf() won't null-terminate if src > dest // lpszDest[ cbDest - 1 ] = '\0'; if ( flag ) { CharUpperBuffA ( lpszDest, cbDest - 1 ); CharToOemA ( lpszDest, lpszDest ); } } DWORD OpenPWL ( CHAR* Username, CHAR* Password, BOOL flag ) { DWORD dwErr = ERROR_SUCCESS; do { CopyAndTruncate ( g_szPWLUsername, Username, sizeof( g_szPWLUsername ), flag ); CopyAndTruncate ( g_szPWLPassword, Password, sizeof( g_szPWLPassword ), flag ); ZeroMemory ( &g_hdrPlaintext, sizeof(g_hdrPlaintext) ); ZeroMemory ( &g_hdrEncrypted, sizeof(g_hdrEncrypted) ); dwErr = OpenCacheFile ( ); if ( dwErr ) { DEBUGMSGA ((S_DBG_RAS, "OpenCacheFile fail")); break; } dwErr = LoadPlaintextHeader ( ); if ( dwErr ) { DEBUGMSGA ((S_DBG_RAS, "LoadPlaintextHeader fail")); break; } if ( g_hdrPlaintext.ulSig == PLAINTEXT_SIGNATURE ) { DEBUGMSGA ((S_DBG_RAS, "PLAINTEXT_SIGNATURE fail")); dwErr = IERR_BadSig; break; } if ( g_hdrPlaintext.ulSig != NEW_PLAINTEXT_SIGNATURE ) { DEBUGMSGA ((S_DBG_RAS, "NEW_PLAINTEXT_SIGNATURE fail")); dwErr = IERR_BadSig; break; } dwErr = LoadEncryptedHeader ( ); if ( dwErr ) { DEBUGMSGA ((S_DBG_RAS, "LoadEncryptedHeader fail")); break; } dwErr = ValidateEncryptedHeader ( ); if ( dwErr ) { DEBUGMSGA ((S_DBG_RAS, "ValidateEncryptedHeader fail")); break; } } while ( FALSE ); return dwErr; } DWORD FindPWLString ( IN CHAR* EntryName, IN CHAR* ConnUser, IN OUT CHAR* Output ) { CHAR resource[ MAX_PATH * 2 ]; DWORD dwErr = ERROR_SUCCESS; DWORD cbCopied = 0; LPBYTE pcei = NULL; do { // Allocate a buffer for the cache entry info // if ( ( pcei = (LPBYTE )LocalAlloc ( LMEM_FIXED, sizeof( CACHE_ENTRY_INFO ) + ( RAS_MaxPortName + 1 ) + ( MAX_PATH + 1 ) ) ) == NULL ) { dwErr = ERROR_NOT_ENOUGH_MEMORY; break; } // // Whistler bug: 417745 INTL:Win9x Upg: DBCS chars cause User,Domain, // Passwrds to not be migrated for DUN // _snprintf(resource, sizeof(resource) - 1, S_RESOURCEMASK2, EntryName, ConnUser); DEBUGMSGA ((S_DBG_RAS, "FindPWLString: %s", resource)); dwErr = FindPWLResource ( resource, (WORD )strlen( resource ), pcei, sizeof( CACHE_ENTRY_INFO ) + ( RAS_MaxPortName + 1 ) + ( MAX_PATH + 1 ), PCE_MISC ); if ( dwErr ) { dwErr = ERROR_INVALID_PASSWORD; break; } cbCopied = min( MAX_PATH,((CACHE_ENTRY_INFO* )pcei)->cbPassword ); // Copy a non null-terminated string for password and terminate it with // a null character // if ( !cbCopied ) { dwErr = ERROR_INVALID_PASSWORD; break; } memcpy ( Output, pcei+(((CACHE_ENTRY_INFO*)pcei)->dchPassword), cbCopied ); Output[ cbCopied ] = '\0'; } while ( FALSE ); // Clean up // if ( pcei ) { ZeroMemory ( pcei, sizeof( CACHE_ENTRY_INFO ) + ( RAS_MaxPortName + 1 ) + ( MAX_PATH + 1 ) ); LocalFree ( pcei ); pcei = NULL; } return dwErr; } BOOL MigrateEntryCreds ( IN OUT PRAS_DIALPARAMS prdp, IN PCTSTR pszEntryName, IN PCTSTR pszUserName, IN PDWORD pdwFlag ) { CHAR szEntryName[RAS_MaxPortName + 1]; CHAR szUserName[UNLEN + 1]; CHAR szConnUser[UNLEN + 1]; CHAR szPassword[MAX_PATH * 2]; do { ZeroMemory ( szEntryName, sizeof(szEntryName) ); ZeroMemory ( szUserName, sizeof(szUserName) ); ZeroMemory ( szConnUser, sizeof(szConnUser) ); ZeroMemory ( szPassword, sizeof(szPassword) ); // // Whistler bug: 417745 INTL:Win9x Upg: DBCS chars cause User,Domain, // Passwrds to not be migrated for DUN // if ( StrCpyAFromWUsingAnsiEncoding ( szEntryName, pszEntryName, sizeof (szEntryName) ) || StrCpyAFromWUsingAnsiEncoding ( szUserName, pszUserName, sizeof (szUserName) ) || StrCpyAFromWUsingAnsiEncoding ( szConnUser, prdp->DP_UserName, sizeof (szConnUser) ) ) { DEBUGMSGA ((S_DBG_RAS, "MigrateEntryCreds: Init Conversion Fail" )); break; } if (OpenPWL ( szUserName, "", TRUE )) { DEBUGMSGA ((S_DBG_RAS, "MigrateEntryCreds: OpenPWL fail")); break; } if (FindPWLString ( szEntryName, szConnUser, szPassword )) { DEBUGMSGA ((S_DBG_RAS, "MigrateEntryCreds: FindPWLString fail")); break; } if (StrCpyWFromAUsingAnsiEncoding (prdp->DP_Password, szPassword, PWLEN )) { DEBUGMSGA ((S_DBG_RAS, "MigrateEntryCreds: Password Conversion Fail" )); break; } DEBUGMSGA ((S_DBG_RAS, "MigrateEntryCreds success")); *pdwFlag |= DLPARAMS_MASK_PASSWORD; } while ( FALSE ); // // Clean up // ZeroMemory( szPassword, sizeof( szPassword ) ); if ( g_hFile ) { CloseHandle ( g_hFile ); g_hFile = NULL; } if (*pdwFlag) { // Success *pdwFlag |= DLPARAMS_MASK_OLDSTYLE; return FALSE; } else { return TRUE; } }