/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "secrng.h"
#include "secerr.h"
#ifdef XP_WIN
#include <windows.h>

#if defined(_WIN32_WCE)
#include <stdlib.h>	/* Win CE puts lots of stuff here. */
#include "prprf.h"	/* for PR_snprintf */
#else
#include <time.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <stdio.h>

#ifndef _WIN32
#define VTD_Device_ID   5
#define OP_OVERRIDE     _asm _emit 0x66
#include <dos.h>
#endif

#include "prio.h"
#include "prerror.h"

static PRInt32  filesToRead;
static DWORD    totalFileBytes;
static DWORD    maxFileBytes	= 250000;	/* 250 thousand */
static DWORD    dwNumFiles, dwReadEvery;

static BOOL
CurrentClockTickTime(LPDWORD lpdwHigh, LPDWORD lpdwLow)
{
#ifdef _WIN32
    LARGE_INTEGER   liCount;

    if (!QueryPerformanceCounter(&liCount))
        return FALSE;

    *lpdwHigh = liCount.u.HighPart;
    *lpdwLow = liCount.u.LowPart;
    return TRUE;

#else   /* is WIN16 */
    BOOL    bRetVal;
    FARPROC lpAPI;
    WORD    w1, w2, w3, w4;

    // Get direct access to the VTD and query the current clock tick time
    _asm {
        xor   di, di
        mov   es, di
        mov   ax, 1684h
        mov   bx, VTD_Device_ID
        int   2fh
        mov   ax, es
        or    ax, di
        jz    EnumerateFailed

        ; VTD API is available. First store the API address (the address actually
        ; contains an instruction that causes a fault, the fault handler then
        ; makes the ring transition and calls the API in the VxD)
        mov   word ptr lpAPI, di
        mov   word ptr lpAPI+2, es
        mov   ax, 100h      ; API function to VTD_Get_Real_Time
;       call  dword ptr [lpAPI]
        call  [lpAPI]

        ; Result is in EDX:EAX which we will get 16-bits at a time
        mov   w2, dx
        OP_OVERRIDE
        shr   dx,10h        ; really "shr edx, 16"
        mov   w1, dx

        mov   w4, ax
        OP_OVERRIDE
        shr   ax,10h        ; really "shr eax, 16"
        mov   w3, ax

        mov   bRetVal, 1    ; return TRUE
        jmp   EnumerateExit

      EnumerateFailed:
        mov   bRetVal, 0    ; return FALSE

      EnumerateExit:
    }

    *lpdwHigh = MAKELONG(w2, w1);
    *lpdwLow = MAKELONG(w4, w3);

    return bRetVal;
#endif  /* is WIN16 */
}

size_t RNG_GetNoise(void *buf, size_t maxbuf)
{
    DWORD   dwHigh, dwLow, dwVal;
    int     n = 0;
    int     nBytes;

    if (maxbuf <= 0)
        return 0;

    CurrentClockTickTime(&dwHigh, &dwLow);

    // get the maximally changing bits first
    nBytes = sizeof(dwLow) > maxbuf ? maxbuf : sizeof(dwLow);
    memcpy((char *)buf, &dwLow, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

    nBytes = sizeof(dwHigh) > maxbuf ? maxbuf : sizeof(dwHigh);
    memcpy(((char *)buf) + n, &dwHigh, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

    // get the number of milliseconds that have elapsed since Windows started
    dwVal = GetTickCount();

    nBytes = sizeof(dwVal) > maxbuf ? maxbuf : sizeof(dwVal);
    memcpy(((char *)buf) + n, &dwVal, nBytes);
    n += nBytes;
    maxbuf -= nBytes;

    if (maxbuf <= 0)
        return n;

#if defined(_WIN32_WCE)
    {
    // get the number of milliseconds elapsed since Windows CE was started. 
    DWORD  tickCount = GetTickCount();
    nBytes = (sizeof tickCount) > maxbuf ? maxbuf : (sizeof tickCount);
    memcpy(((char *)buf) + n, &tickCount, nBytes);
    n += nBytes;
    }
#else
    {
    time_t  sTime;
    // get the time in seconds since midnight Jan 1, 1970
    time(&sTime);
    nBytes = sizeof(sTime) > maxbuf ? maxbuf : sizeof(sTime);
    memcpy(((char *)buf) + n, &sTime, nBytes);
    n += nBytes;
    }
#endif

    return n;
}

#if defined(_WIN32_WCE)
static BOOL
EnumSystemFilesWithNSPR(const char * dirName, 
			BOOL recursive,
                        PRInt32 (*func)(const char *))
{
    PRDir *      pDir;
    PRDirEntry * pEntry;
    BOOL         rv 		= FALSE;

    pDir = PR_OpenDir(dirName);
    if (!pDir)
    	return rv;
    while ((pEntry = PR_ReadDir(pDir, PR_SKIP_BOTH|PR_SKIP_HIDDEN)) != NULL) {
	PRStatus    status;
	PRInt32     count;
	PRInt32     stop;
	PRFileInfo  fileInfo;
	char        szFileName[_MAX_PATH];

        count = (PRInt32)PR_snprintf(szFileName, sizeof szFileName, "%s\\%s", 
	                             dirName, PR_DirName(pEntry));
	if (count < 1)
	    continue;
	status = PR_GetFileInfo(szFileName, &fileInfo);
	if (status != PR_SUCCESS)
	    continue;
	if (fileInfo.type == PR_FILE_FILE) {
	    stop = (*func)(szFileName);
	    rv = TRUE;
	    if (stop)
	        break;
	    continue;
    	}
	if (recursive && fileInfo.type == PR_FILE_DIRECTORY) {
	    rv |= EnumSystemFilesWithNSPR(szFileName, recursive, func);
	}
    }
    PR_CloseDir(pDir);
    return rv;
}
#endif

static BOOL
EnumSystemFiles(PRInt32 (*func)(const char *))
{
#if defined(_WIN32_WCE)
    BOOL rv = FALSE;
    rv |= EnumSystemFilesWithNSPR("\\Windows\\Temporary Internet Files", TRUE, func);
    rv |= EnumSystemFilesWithNSPR("\\Temp",    FALSE, func);
    rv |= EnumSystemFilesWithNSPR("\\Windows", FALSE, func);
    return rv;
#else
    int                 iStatus;
    char                szSysDir[_MAX_PATH];
    char                szFileName[_MAX_PATH];
#ifdef _WIN32
    WIN32_FIND_DATA     fdData;
    HANDLE              lFindHandle;
#else
    struct _find_t  fdData;
#endif

    if (!GetSystemDirectory(szSysDir, sizeof(szSysDir)))
        return FALSE;

    // tack *.* on the end so we actually look for files. this will
    // not overflow
    strcpy(szFileName, szSysDir);
    strcat(szFileName, "\\*.*");

#ifdef _WIN32
    lFindHandle = FindFirstFile(szFileName, &fdData);
    if (lFindHandle == INVALID_HANDLE_VALUE)
        return FALSE;
    do {
        // pass the full pathname to the callback
        sprintf(szFileName, "%s\\%s", szSysDir, fdData.cFileName);
        (*func)(szFileName);
        iStatus = FindNextFile(lFindHandle, &fdData);
    } while (iStatus != 0);
    FindClose(lFindHandle);
#else
    if (_dos_findfirst(szFileName, 
             _A_NORMAL | _A_RDONLY | _A_ARCH | _A_SUBDIR, &fdData) != 0)
        return FALSE;
    do {
        // pass the full pathname to the callback
        sprintf(szFileName, "%s\\%s", szSysDir, fdData.name);
        (*func)(szFileName);
        iStatus = _dos_findnext(&fdData);
    } while (iStatus == 0);
    _dos_findclose(&fdData);
#endif

    return TRUE;
#endif
}

static PRInt32
CountFiles(const char *file)
{
    dwNumFiles++;
    return 0;
}

static PRInt32
ReadFiles(const char *file)
{
    if ((dwNumFiles % dwReadEvery) == 0) {
	++filesToRead;
    }
    if (filesToRead) {
	DWORD    prevFileBytes = totalFileBytes;
        RNG_FileForRNG(file);
	if (prevFileBytes < totalFileBytes) {
	    --filesToRead;
	}
    }
    dwNumFiles++;
    return (totalFileBytes >= maxFileBytes);
}

static void
ReadSystemFiles()
{
    // first count the number of files
    dwNumFiles = 0;
    if (!EnumSystemFiles(CountFiles))
        return;

    RNG_RandomUpdate(&dwNumFiles, sizeof(dwNumFiles));

    // now read the first 10 readable files, then 10 or 11 files
    // spread throughout the system directory
    filesToRead = 10;
    if (dwNumFiles == 0)
        return;

    dwReadEvery = dwNumFiles / 10;
    if (dwReadEvery == 0)
        dwReadEvery = 1;  // less than 10 files

    dwNumFiles = 0;
    EnumSystemFiles(ReadFiles);
}

void RNG_SystemInfoForRNG(void)
{
    DWORD           dwVal;
    char            buffer[256];
    int             nBytes;
#ifdef _WIN32
    MEMORYSTATUS    sMem;
    HANDLE          hVal;
#if !defined(_WIN32_WCE)
    DWORD           dwSerialNum;
    DWORD           dwComponentLen;
    DWORD           dwSysFlags;
    char            volName[128];
    DWORD           dwSectors, dwBytes, dwFreeClusters, dwNumClusters;
#endif
#else
    int             iVal;
    HTASK           hTask;
    WORD            wDS, wCS;
    LPSTR           lpszEnv;
#endif

    nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
    RNG_RandomUpdate(buffer, nBytes);

#ifdef _WIN32
    sMem.dwLength = sizeof(sMem);
    GlobalMemoryStatus(&sMem);                // assorted memory stats
    RNG_RandomUpdate(&sMem, sizeof(sMem));
#if !defined(_WIN32_WCE)
    dwVal = GetLogicalDrives();
    RNG_RandomUpdate(&dwVal, sizeof(dwVal));  // bitfields in bits 0-25
#endif
#else
    dwVal = GetFreeSpace(0);
    RNG_RandomUpdate(&dwVal, sizeof(dwVal));

    _asm    mov wDS, ds;
    _asm    mov wCS, cs;
    RNG_RandomUpdate(&wDS, sizeof(wDS));
    RNG_RandomUpdate(&wCS, sizeof(wCS));
#endif

#ifdef _WIN32
#if !defined(_WIN32_WCE)
    dwVal = sizeof(buffer);
    if (GetComputerName(buffer, &dwVal))
        RNG_RandomUpdate(buffer, dwVal);
#endif
/* XXX This is code that got yanked because of NSPR20.  We should put it
 * back someday.
 */
#ifdef notdef
    {
    POINT ptVal;
    GetCursorPos(&ptVal);
    RNG_RandomUpdate(&ptVal, sizeof(ptVal));
    }

    dwVal = GetQueueStatus(QS_ALLINPUT);      // high and low significant
    RNG_RandomUpdate(&dwVal, sizeof(dwVal));

    {
    HWND hWnd;
    hWnd = GetClipboardOwner();               // 2 or 4 bytes
    RNG_RandomUpdate((void *)&hWnd, sizeof(hWnd));
    }

    {
    UUID sUuid;
    UuidCreate(&sUuid);                       // this will fail on machines with no ethernet
    RNG_RandomUpdate(&sUuid, sizeof(sUuid));  // boards. shove the bits in regardless
    }
#endif

    hVal = GetCurrentProcess();               // 4 or 8 byte pseudo handle (a
                                              // constant!) of current process
    RNG_RandomUpdate(&hVal, sizeof(hVal));

    dwVal = GetCurrentProcessId();            // process ID (4 bytes)
    RNG_RandomUpdate(&dwVal, sizeof(dwVal));

    dwVal = GetCurrentThreadId();             // thread ID (4 bytes)
    RNG_RandomUpdate(&dwVal, sizeof(dwVal));

#if !defined(_WIN32_WCE)
    volName[0] = '\0';
    buffer[0] = '\0';
    GetVolumeInformation(NULL,
                         volName,
                         sizeof(volName),
                         &dwSerialNum,
                         &dwComponentLen,
                         &dwSysFlags,
                         buffer,
                         sizeof(buffer));

    RNG_RandomUpdate(volName,         strlen(volName));
    RNG_RandomUpdate(&dwSerialNum,    sizeof(dwSerialNum));
    RNG_RandomUpdate(&dwComponentLen, sizeof(dwComponentLen));
    RNG_RandomUpdate(&dwSysFlags,     sizeof(dwSysFlags));
    RNG_RandomUpdate(buffer,          strlen(buffer));

    if (GetDiskFreeSpace(NULL, &dwSectors, &dwBytes, &dwFreeClusters, &dwNumClusters)) {
        RNG_RandomUpdate(&dwSectors,      sizeof(dwSectors));
        RNG_RandomUpdate(&dwBytes,        sizeof(dwBytes));
        RNG_RandomUpdate(&dwFreeClusters, sizeof(dwFreeClusters));
        RNG_RandomUpdate(&dwNumClusters,  sizeof(dwNumClusters));
    }
#endif
#else   /* is WIN16 */
    hTask = GetCurrentTask();
    RNG_RandomUpdate((void *)&hTask, sizeof(hTask));

    iVal = GetNumTasks();
    RNG_RandomUpdate(&iVal, sizeof(iVal));      // number of running tasks

    lpszEnv = GetDOSEnvironment();
    while (*lpszEnv != '\0') {
        RNG_RandomUpdate(lpszEnv, strlen(lpszEnv));

        lpszEnv += strlen(lpszEnv) + 1;
    }
#endif  /* is WIN16 */

    // now let's do some files
    ReadSystemFiles();

    nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
    RNG_RandomUpdate(buffer, nBytes);
}

#if defined(_WIN32_WCE)
void RNG_FileForRNG(const char *filename)
{
    PRFileDesc *    file;
    int             nBytes;
    PRFileInfo      infoBuf;
    unsigned char   buffer[1024];

    /* windows doesn't initialize all the bytes in the stat buf,
     * so initialize them all here to avoid UMRs.
     */
    memset(&infoBuf, 0, sizeof infoBuf);

    if (PR_GetFileInfo(filename, &infoBuf) < 0)
        return;

    RNG_RandomUpdate((unsigned char*)&infoBuf, sizeof(infoBuf));

    file = PR_Open(filename, PR_RDONLY, 0);
    if (file != NULL) {
        for (;;) {
            PRInt32 bytes = PR_Read(file, buffer, sizeof buffer);

            if (bytes <= 0)
                break;

            RNG_RandomUpdate(buffer, bytes);
            totalFileBytes += bytes;
            if (totalFileBytes > maxFileBytes)
                break;
        }

        PR_Close(file);
    }

    nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
    RNG_RandomUpdate(buffer, nBytes);
}

#else /* not WinCE */

void RNG_FileForRNG(const char *filename)
{
    FILE*           file;
    int             nBytes;
    struct stat     stat_buf;
    unsigned char   buffer[1024];

   /* static DWORD    totalFileBytes = 0; */

    /* windows doesn't initialize all the bytes in the stat buf,
     * so initialize them all here to avoid UMRs.
     */
    memset(&stat_buf, 0, sizeof stat_buf);

    if (stat((char *)filename, &stat_buf) < 0)
        return;

    RNG_RandomUpdate((unsigned char*)&stat_buf, sizeof(stat_buf));

    file = fopen((char *)filename, "r");
    if (file != NULL) {
        for (;;) {
            size_t  bytes = fread(buffer, 1, sizeof(buffer), file);

            if (bytes == 0)
                break;

            RNG_RandomUpdate(buffer, bytes);
            totalFileBytes += bytes;
            if (totalFileBytes > maxFileBytes)
                break;
        }

        fclose(file);
    }

    nBytes = RNG_GetNoise(buffer, 20);  // get up to 20 bytes
    RNG_RandomUpdate(buffer, nBytes);
}

#endif  /* not WinCE */

/*
 * CryptoAPI requires Windows NT 4.0 or Windows 95 OSR2 and later.
 * Until we drop support for Windows 95, we need to emulate some
 * definitions and declarations in <wincrypt.h> and look up the
 * functions in advapi32.dll at run time.
 */

#ifndef WIN64
typedef unsigned long HCRYPTPROV;
#endif

#define CRYPT_VERIFYCONTEXT 0xF0000000

#define PROV_RSA_FULL 1

typedef BOOL
(WINAPI *CryptAcquireContextAFn)(
    HCRYPTPROV *phProv,
    LPCSTR pszContainer,
    LPCSTR pszProvider,
    DWORD dwProvType,
    DWORD dwFlags);

typedef BOOL
(WINAPI *CryptReleaseContextFn)(
    HCRYPTPROV hProv,
    DWORD dwFlags);

typedef BOOL
(WINAPI *CryptGenRandomFn)(
    HCRYPTPROV hProv,
    DWORD dwLen,
    BYTE *pbBuffer);

/*
 * Windows XP and Windows Server 2003 and later have RtlGenRandom,
 * which must be looked up by the name SystemFunction036.
 */
typedef BOOLEAN
(APIENTRY *RtlGenRandomFn)(
    PVOID RandomBuffer,
    ULONG RandomBufferLength);

size_t RNG_SystemRNG(void *dest, size_t maxLen)
{
    HMODULE hModule;
    RtlGenRandomFn pRtlGenRandom;
    CryptAcquireContextAFn pCryptAcquireContextA;
    CryptReleaseContextFn pCryptReleaseContext;
    CryptGenRandomFn pCryptGenRandom;
    HCRYPTPROV hCryptProv;
    size_t bytes = 0;

    hModule = LoadLibrary("advapi32.dll");
    if (hModule == NULL) {
	PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
	return 0;
    }
    pRtlGenRandom = (RtlGenRandomFn)
	GetProcAddress(hModule, "SystemFunction036");
    if (pRtlGenRandom) {
	if (pRtlGenRandom(dest, maxLen)) {
	    bytes = maxLen;
	} else {
	    PORT_SetError(SEC_ERROR_NEED_RANDOM);  /* system RNG failed */
	}
	goto done;
    }
    pCryptAcquireContextA = (CryptAcquireContextAFn)
	GetProcAddress(hModule, "CryptAcquireContextA");
    pCryptReleaseContext = (CryptReleaseContextFn)
	GetProcAddress(hModule, "CryptReleaseContext");
    pCryptGenRandom = (CryptGenRandomFn)
	GetProcAddress(hModule, "CryptGenRandom");
    if (!pCryptAcquireContextA || !pCryptReleaseContext || !pCryptGenRandom) {
	PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
	goto done;
    }
    if (pCryptAcquireContextA(&hCryptProv, NULL, NULL,
	PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
	if (pCryptGenRandom(hCryptProv, maxLen, dest)) {
	    bytes = maxLen;
	}
	pCryptReleaseContext(hCryptProv, 0);
    }
    if (bytes == 0) {
	PORT_SetError(SEC_ERROR_NEED_RANDOM);  /* system RNG failed */
    }
done:
    FreeLibrary(hModule);
    return bytes;
}

#endif  /* is XP_WIN */
