//+--------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 1999
//
// File:        certsrv.cpp
//
// Contents:    Cert Server main & debug support
//
// History:     25-Jul-96       vich created
//
//---------------------------------------------------------------------------

#include <pch.cpp>

#pragma hdrstop

#include <locale.h>
#include <io.h>
#include <fcntl.h>
#include <safeboot.h>
#include <authzi.h>

#include "elog.h"
#include "certlog.h"
#include "certsrvd.h"
#include "resource.h"
#include "csresstr.h"

#define __dwFILE__	__dwFILE_CERTSRV_CERTSRV_CPP__

HKEY g_hkeyCABase = 0;


BOOL g_fCreateDB = FALSE;
BOOL g_fStartAsService = TRUE;
BOOL g_fStarted;
BOOL g_fStartInProgress;
DWORD g_ServiceThreadId;
HWND g_hwndMain;
WCHAR g_wszAppName[] = L"CertSrv";
HINSTANCE g_hInstApp;
DWORD g_dwDelay0;
DWORD g_dwDelay1;
DWORD g_dwDelay2;
BOOL g_fCryptSilent = FALSE;

HANDLE g_hServiceThread = NULL;
HANDLE g_hShutdownEvent = NULL;

CRITICAL_SECTION g_ShutdownCriticalSection;
BOOL g_fShutdownCritSec = FALSE;

BOOL g_fRefuseIncoming = FALSE;
LONG g_cCalls = 0;
LONG g_cCallsActive = 0;
BOOL g_fAdvancedServer = FALSE;

CAutoLPWSTR g_pwszDBFileHash;

SERVICE_TABLE_ENTRY steDispatchTable[] =
{
    { const_cast<WCHAR *>(g_wszCertSrvServiceName), ServiceMain },
    { NULL, NULL }
};


WCHAR const g_wszRegKeyClassesCLSID[] = L"SOFTWARE\\Classes\\CLSID";
WCHAR const g_wszRegKeyInprocServer32[] = L"InprocServer32";
WCHAR const g_wszRegValueThreadingModel[] = L"ThreadingModel";
WCHAR const g_wszRegKeyAppId[] = L"SOFTWARE\\Classes\\AppId";
WCHAR const g_wszRegRunAs[] = L"RunAs";
WCHAR const g_wszRegValueInteractiveUser[] = L"Interactive User";
WCHAR const g_wszRegLocalService[] = L"LocalService";

// do not change the order, add new audit resources at the end
//    g_pwszAllow,
//    g_pwszDeny,
//    g_pwszCAAdmin,
//    g_pwszOfficer,
//    g_pwszRead,
//    g_pwszEnroll,

LPCWSTR g_pwszAuditResources[6];

using namespace CertSrv;

HRESULT
OpenRegistryComKey(
    IN HKEY hKeyParent,
    IN CLSID const *pclsid,
    IN BOOL fWrite,
    OUT HKEY *phKey)
{
    HRESULT hr;
    WCHAR *pwsz = NULL;

    *phKey = NULL;
    hr = StringFromCLSID(*pclsid, &pwsz);
    _JumpIfError(hr, error, "StringFromCLSID");

    hr = RegOpenKeyEx(
		hKeyParent,
		pwsz,
		0,
		fWrite? KEY_ALL_ACCESS : KEY_READ,
		phKey);
    _JumpIfError(hr, error, "RegOpenKeyEx");

error:
    if (NULL != pwsz)
    {
	CoTaskMemFree(pwsz);
    }
    return(hr);
}


BOOL
IsMissingRegistryValue(
    IN HKEY hKey,
    IN WCHAR const *pwszRegValueName)
{
    HRESULT hr;
    DWORD dwLen;
    DWORD dwType;

    hr = RegQueryValueEx(hKey, pwszRegValueName, NULL, &dwType, NULL, &dwLen);
    if (S_OK != hr)
    {
	hr = myHError(hr);
    }
    _JumpIfError2(
	    hr,
	    error,
	    "RegQueryValueEx",
	    HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));

error:
    return(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr);
}


BOOL
IsMatchingRegistryValue(
    IN HKEY hKey,
    IN WCHAR const *pwszRegValueName,
    IN WCHAR const *pwszRegValueString)
{
    HRESULT hr;
    DWORD dwLen;
    DWORD dwType;
    BOOL fMatch = FALSE;
    WCHAR buf[MAX_PATH];

    dwLen = sizeof(buf);
    hr = RegQueryValueEx(
		    hKey,
		    pwszRegValueName,
		    NULL,
		    &dwType,
		    (BYTE *) buf,
		    &dwLen);
    _JumpIfErrorStr(hr, error, "RegQueryValueEx", pwszRegValueName);

    if (REG_SZ == dwType && 0 == lstrcmpi(buf, pwszRegValueString))
    {
	fMatch = TRUE;
    }

error:
    return(fMatch);
}


HRESULT
SetRegistryStringValue(
    IN HKEY hKey,
    IN WCHAR const *pwszRegValueName,
    IN WCHAR const *pwszRegValueString)
{
    HRESULT hr;

    hr = RegSetValueEx(
		    hKey,
		    pwszRegValueName,
		    0,
		    REG_SZ,
		    (const BYTE *) pwszRegValueString,
		    (wcslen(pwszRegValueString) + 1) * sizeof(WCHAR));
    return(hr);
}


HRESULT
CertSrvSetRegistryFileTimeValue(
    IN BOOL fConfigLevel,
    IN WCHAR const *pwszRegValueName,
    IN DWORD cpwszDelete,
    OPTIONAL IN WCHAR const * const *papwszRegValueNameDelete)
{
    HRESULT hr;
    HKEY hKey = NULL;
    HKEY hKey1 = NULL;
    WCHAR *pwszKey;
    FILETIME ftCurrent;
    DWORD i;

    GetSystemTimeAsFileTime(&ftCurrent);

    hr = RegOpenKeyEx(
		    HKEY_LOCAL_MACHINE,
		    wszREGKEYCONFIGPATH,
		    0,
		    KEY_ALL_ACCESS,
		    &hKey);
    _JumpIfError(hr, error, "RegOpenKeyEx");

    if (!fConfigLevel)
    {
	hKey1 = hKey;
	hKey = NULL;
	hr = RegOpenKeyEx(
			hKey1,
			g_wszSanitizedName,
			0,
			KEY_ALL_ACCESS,
			&hKey);
	_JumpIfError(hr, error, "RegOpenKeyEx");
    }

    hr = RegSetValueEx(
		    hKey,
		    pwszRegValueName,
		    0,
		    REG_BINARY,
		    (BYTE const *) &ftCurrent,
		    sizeof(ftCurrent));
    _JumpIfError(hr, error, "RegSetValueEx");

    for (i = 0; i < cpwszDelete; i++)
    {
	hr = RegDeleteValue(hKey, papwszRegValueNameDelete[i]);
	_PrintIfError2(hr, "RegDeleteValue", ERROR_FILE_NOT_FOUND);
    }
    hr = S_OK;

error:
    if (NULL != hKey1)
    {
	RegCloseKey(hKey1);
    }
    if (NULL != hKey)
    {
	RegCloseKey(hKey);
    }
    return(myHError(hr));
}


HRESULT
SetRegistryDcomConfig(
    IN BOOL fConsoleActive)
{
    HRESULT hr;
    HKEY hKeyAppId = NULL;
    HKEY hKeyAdmin = NULL;
    HKEY hKeyRequest = NULL;
    DWORD cChanged = 0;

    hr = RegOpenKeyEx(
		    HKEY_LOCAL_MACHINE,
		    g_wszRegKeyAppId,
		    0,
		    KEY_ALL_ACCESS,
		    &hKeyAppId);
    _JumpIfError(hr, error, "RegOpenKeyEx");

    hr = OpenRegistryComKey(hKeyAppId, &CLSID_CCertAdminD, TRUE, &hKeyAdmin);
    _JumpIfError(hr, error, "OpenRegistryComKey");

    hr = OpenRegistryComKey(hKeyAppId, &CLSID_CCertRequestD, TRUE, &hKeyRequest);
    _JumpIfError(hr, error, "OpenRegistryComKey");

    if (fConsoleActive)
    {
	// Running in console mode:
	// Delete both LocalService registry values
	// Create both RunAs = InteractiveUser registry values
	
	if (!IsMissingRegistryValue(hKeyAdmin, g_wszRegLocalService))
	{
	    cChanged++;
	    hr = RegDeleteValue(hKeyAdmin, g_wszRegLocalService);
	    _JumpIfError(hr, error, "RegDeleteValue");
	}
	if (!IsMissingRegistryValue(hKeyRequest, g_wszRegLocalService))
	{
	    cChanged++;
	    hr = RegDeleteValue(hKeyRequest, g_wszRegLocalService);
	    _JumpIfError(hr, error, "RegDeleteValue");
	}

	if (!IsMatchingRegistryValue(
				hKeyAdmin,
				g_wszRegRunAs,
				g_wszRegValueInteractiveUser))
	{
	    cChanged++;
	    hr = SetRegistryStringValue(
			    hKeyAdmin,
			    g_wszRegRunAs,
			    g_wszRegValueInteractiveUser);
	    _JumpIfError(hr, error, "SetRegistryStringValue");
	}
	if (!IsMatchingRegistryValue(
				hKeyRequest,
				g_wszRegRunAs,
				g_wszRegValueInteractiveUser))
	{
	    cChanged++;
	    hr = SetRegistryStringValue(
			    hKeyRequest,
			    g_wszRegRunAs,
			    g_wszRegValueInteractiveUser);
	    _JumpIfError(hr, error, "SetRegistryStringValue");
	}
	if (0 != cChanged)
	{
	    DBGPRINT((
		DBG_SS_CERTSRV,
		"SetRegistryDcomConfig(%u): setting %ws=%ws\n",
		cChanged,
		g_wszRegRunAs,
		g_wszRegValueInteractiveUser));
	}
    }
    else
    {
	// Running as a service:
	// Delete both RunAs registry values
	// Create both LocalService = CertSvc registry values
	
	if (!IsMissingRegistryValue(hKeyAdmin, g_wszRegRunAs))
	{
	    cChanged++;
	    hr = RegDeleteValue(hKeyAdmin, g_wszRegRunAs);
	    _JumpIfError(hr, error, "RegDeleteValue");
	}
	if (!IsMissingRegistryValue(hKeyRequest, g_wszRegRunAs))
	{
	    cChanged++;
	    hr = RegDeleteValue(hKeyRequest, g_wszRegRunAs);
	    _JumpIfError(hr, error, "RegDeleteValue");
	}

	if (!IsMatchingRegistryValue(
				hKeyAdmin,
				g_wszRegLocalService,
				g_wszCertSrvServiceName))
	{
	    cChanged++;
	    hr = SetRegistryStringValue(
			    hKeyAdmin,
			    g_wszRegLocalService,
			    g_wszCertSrvServiceName);
	    _JumpIfError(hr, error, "SetRegistryStringValue");
	}
	if (!IsMatchingRegistryValue(
				hKeyRequest,
				g_wszRegLocalService,
				g_wszCertSrvServiceName))
	{
	    cChanged++;
	    hr = SetRegistryStringValue(
			    hKeyRequest,
			    g_wszRegLocalService,
			    g_wszCertSrvServiceName);
	    _JumpIfError(hr, error, "SetRegistryStringValue");
	}
	if (0 != cChanged)
	{
	    DBGPRINT((
		DBG_SS_CERTSRV,
		"SetRegistryDcomConfig(%u): setting %ws=%ws\n",
		cChanged,
		g_wszRegLocalService,
		g_wszCertSrvServiceName));
	}
    }

error:
    if (NULL != hKeyRequest)
    {
	RegCloseKey(hKeyRequest);
    }
    if (NULL != hKeyAdmin)
    {
	RegCloseKey(hKeyAdmin);
    }
    if (NULL != hKeyAppId)
    {
	RegCloseKey(hKeyAppId);
    }
    return(myHError(hr));
}


DWORD
GetRegistryDwordValue(
    IN WCHAR const *pwszRegValueName)
{
    HRESULT hr;
    HKEY hKeyConfig = NULL;
    DWORD dwVal;
    DWORD dwType;
    DWORD dwLen;

    dwVal = 0;

    hr = RegOpenKeyEx(
		    HKEY_LOCAL_MACHINE,
		    g_wszRegKeyConfigPath,
		    0,
		    KEY_READ,
		    &hKeyConfig);
    _JumpIfError(hr, error, "RegOpenKeyEx");

    dwLen = sizeof(dwVal);
    hr = RegQueryValueEx(
			hKeyConfig,
			pwszRegValueName,
			NULL,
			&dwType,
			(BYTE *) &dwVal,
			&dwLen);

    if (S_OK != hr || REG_DWORD != dwType || sizeof(dwVal) != dwLen)
    {
	dwVal = 0;
	goto error;
    }

error:
    if (NULL != hKeyConfig)
    {
	RegCloseKey(hKeyConfig);
    }
    return(dwVal);
}

HRESULT 
CertSrvResetRegistryWatch(
    IN OUT HANDLE *phRegistryModified)
{
    HRESULT hr;
    
    CSASSERT(NULL != phRegistryModified);

    //////////////////////////////////////
    // Initialization of registry events

    if (NULL == g_hkeyCABase)
    {
        DWORD dwDisposition;
        LPWSTR pszCAPath;
        
        pszCAPath = (LPWSTR) LocalAlloc(
            LMEM_FIXED,
            (WSZARRAYSIZE(wszREGKEYCONFIGPATH_BS) +
            wcslen(g_wszSanitizedName) +
            1) * sizeof(WCHAR));
	if (NULL == pszCAPath)
	{
	    hr = E_OUTOFMEMORY;
	    _JumpError(hr, error, "LocalAlloc");
	}
        
        wcscpy(pszCAPath, wszREGKEYCONFIGPATH_BS);
        wcscat(pszCAPath, g_wszSanitizedName);
        
        hr = RegCreateKeyEx(
            HKEY_LOCAL_MACHINE,
            pszCAPath,
            0,                  // reserved
            NULL,               // class
            0,                  // options
            KEY_ALL_ACCESS,     // sec desired
            NULL,               // sec attr
            &g_hkeyCABase,      // phk
            &dwDisposition);
        LocalFree(pszCAPath); pszCAPath = NULL;
        _JumpIfError(hr, error, "RegCreateKeyEx base key");
    }
    if (NULL == *phRegistryModified)
    {
        *phRegistryModified = CreateEvent(
				    NULL,
				    TRUE,	// manual reset
				    FALSE,	// initial state
				    L"Registry Modification Event");
        if (NULL == *phRegistryModified)
        {
            hr = myHLastError();
            _JumpError(hr, error, "CreateEvent registry watch");
        }
    }
    else
    {
        // reset registry event
        ResetEvent( *phRegistryModified ); 
    }

    // register our registry lookout trigger
    hr = RegNotifyChangeKeyValue(
            g_hkeyCABase,
            FALSE,
            REG_NOTIFY_CHANGE_LAST_SET,
            *phRegistryModified,
            TRUE);
    _JumpIfError(hr, error, "RegNotifyChangeKeyValue on base key");

error:
    return(myHError(hr));
}


HRESULT 
CertSrvRegistryModificationEvent(
    IN FILETIME const *pftWait,
    IN OUT DWORD *pdwTimeOut)
{
    HRESULT hr;
    DWORD dwVal;
    BOOL fDisabledNew;
    FILETIME ftCurrent;
    BOOL fSetEvent = FALSE;
    DWORD dwMSTimeOut;

    // see if Base CRL publish enabled state has changed

    hr = myGetCertRegDWValue(
			g_wszSanitizedName,
			NULL,
			NULL,
			wszREGCRLPERIODCOUNT,
			&dwVal);
    if (S_OK == hr)
    {
	fDisabledNew = 0 == dwVal;      
	if (fDisabledNew != g_fCRLPublishDisabled)
	{
            fSetEvent = TRUE;
	}
    }

    // see if Delta CRL publish enabled state has changed

    hr = myGetCertRegDWValue(
			g_wszSanitizedName,
			NULL,
			NULL,
			wszREGCRLDELTAPERIODCOUNT,
			&dwVal);
    if (S_OK == hr)
    {
	fDisabledNew = 0 == dwVal;      
	if (fDisabledNew != g_fDeltaCRLPublishDisabled)
	{
            fSetEvent = TRUE;
        }
    }

    GetSystemTimeAsFileTime(&ftCurrent);

    CRLComputeTimeOut(pftWait, &ftCurrent, &dwMSTimeOut);
    if (dwMSTimeOut >= *pdwTimeOut)
    {
	dwMSTimeOut = *pdwTimeOut;
	fSetEvent = TRUE;
    }
    *pdwTimeOut -= dwMSTimeOut;

    if (fSetEvent)
    {
	SetEvent(g_hCRLManualPublishEvent);	// pulse to get up-to-date
    }
    return(hr);
}


#if DBG_CERTSRV
WCHAR const *
certsrvGetCurrentTimeWsz()
{
    HRESULT hr;
    FILETIME ft;
    WCHAR *pwszTime = NULL;
    static WCHAR s_wszTime[128];
    
    GetSystemTimeAsFileTime(&ft);
    hr = myGMTFileTimeToWszLocalTime(&ft, TRUE, &pwszTime);
    _PrintIfError(hr, "myGMTFileTimeToWszLocalTime");
    s_wszTime[0] = L'\0';
    if (NULL != pwszTime)
    {
	wcsncpy(s_wszTime, pwszTime, ARRAYSIZE(s_wszTime));
	s_wszTime[ARRAYSIZE(s_wszTime) - 1] = L'\0';
	LocalFree(pwszTime);
    }
    return(s_wszTime);
}
#endif


HRESULT
CertSrvBlockThreadUntilStop()
{
    HRESULT hr;
    HANDLE hRegistryModified = NULL;
    DWORD dwTimeOut;

    // check CRL publish, get next timeout interval

    hr = CRLPubWakeupEvent(&dwTimeOut);
    _PrintIfError(hr, "CRLPubWakeupEvent");

    hr = CertSrvResetRegistryWatch(&hRegistryModified);
    _PrintIfError(hr, "CertSrvResetRegistryWatch");

    while (TRUE)
    {
	FILETIME ftWait;
        DWORD dw;
        HANDLE hmultiObjects[] = {
	    hRegistryModified,
	    g_hServiceStoppingEvent,
	    g_hCRLManualPublishEvent
	};

#if DBG_CERTSRV
	{
	    LLFILETIME llft;
	    WCHAR *pwszTimePeriod = NULL;

	    llft.ll = dwTimeOut;
	    llft.ll *= (CVT_BASE / 1000);	// convert msecs to 100ns
	    llft.ll = -llft.ll;
	    
	    hr = myFileTimePeriodToWszTimePeriod(
				    &llft.ft,
				    TRUE,	// fExact
				    &pwszTimePeriod);
	    _PrintIfError(hr, "myFileTimePeriodToWszTimePeriod");

	    DBGPRINT((
		DBG_SS_CERTSRV,
		"WaitForMultipleObjects(%u ms) %ws @%ws\n",
		dwTimeOut,
		pwszTimePeriod,
		certsrvGetCurrentTimeWsz()));
	    if (NULL != pwszTimePeriod)
	    {
		LocalFree(pwszTimePeriod);
	    }
	}
#endif

	GetSystemTimeAsFileTime(&ftWait);
        dw = WaitForMultipleObjects(
			    ARRAYSIZE(hmultiObjects),
			    hmultiObjects,
			    FALSE,      // any object will cause bailout
			    dwTimeOut);

	DBGPRINT((
	    DBG_SS_CERTSRV,
	    "WaitForMultipleObjects(%u ms)->%x, %ws\n",
	    dwTimeOut,
	    dw,
	    certsrvGetCurrentTimeWsz()));

        if (WAIT_FAILED == dw)
        {
            hr = GetLastError();
            _JumpError(hr, error, "WaitForMultipleObjects worker");
        }

        if (dw == WAIT_TIMEOUT)     // CRL
        {
            hr = CRLPubWakeupEvent(&dwTimeOut);
            _PrintIfError(hr, "Error during CRLPubWakeupEvent");

            DBGPRINT((DBG_SS_CERTSRVI, "CRLPub: TimeOut %u ms\n", dwTimeOut));
        }
        else if (dw == WAIT_OBJECT_0)   // Registry modification
        {
            // In either case, determine if CRL needs to be published

            hr = CertSrvRegistryModificationEvent(&ftWait, &dwTimeOut);
            _PrintIfError(hr, "Error during CertSrvRegistryModificationEvent");

            // in registry case, reset registry trigger

            DBGPRINT((
		DBG_SS_CERTSRVI,
		"CRLPub: Registry change trigger, TimeOut=%u ms\n",
		dwTimeOut));

            hr = CertSrvResetRegistryWatch(&hRegistryModified);
            _PrintIfError(hr, "Error during CertSrvResetRegistryWatch");
        }
        else if (dw == WAIT_OBJECT_0 + 1)
        {
            // found "service done" event

            DBGPRINT((DBG_SS_CERTSRV, "Service is pending stop request\n"));
            break;  // exit wait loop
        }
        else if (dw == WAIT_OBJECT_0 + 2)
        {
            // found "g_hCRLManualPublishEvent" event: recalc timeout

            hr = CRLPubWakeupEvent(&dwTimeOut);
            _PrintIfError(hr, "Error during CRLPubWakeupEvent");

            DBGPRINT((
		DBG_SS_CERTSRVI,
		"CRLPub: Manual publish recalc, TimeOut=%u ms\n",
		dwTimeOut));
        }
        else
        {
            CSASSERT(!"unexpected wait return");
            hr = E_UNEXPECTED;
            _JumpError(hr, error, "WaitForMultipleObjects");
        }
    }
    hr = S_OK;

error:
    CloseHandle(hRegistryModified);
    return hr;
}


// returns TRUE if we shutdown correctly

BOOL
CertSrvStopServer(
    IN BOOL fConsoleActive)
{
    HRESULT hr;
    BOOL fCoInit = FALSE;
    BOOL fShutDown = FALSE;

    if (!g_fStartInProgress)		// ignore while starting the server
    {
	fShutDown = TRUE;

        DBGPRINT((
            DBG_SS_CERTSRV,
            "CertSrvStopServer(fConsoleActive=%u, tid=%d)\n",
            fConsoleActive,
            GetCurrentThreadId()));

        SetEvent(g_hServiceStoppingEvent);

        if (g_hkeyCABase)
        {
            RegCloseKey(g_hkeyCABase);
            g_hkeyCABase = NULL;
        }

        hr = CoInitializeEx(NULL, GetCertsrvComThreadingModel());
        if (S_OK != hr && S_FALSE != hr)
        {
            _JumpError(hr, error, "CoInitializeEx");
        }
        fCoInit = TRUE;

        // don't allow new callers in

        hr = RPCTeardown();
        _PrintIfError(hr, "RPCTeardown");
        CertStopClassFactories();

        CoreTerminate();

        if (g_fStarted)
        {
            if (CERTLOG_TERSE <= g_dwLogLevel)
            {
                LogEventString(
                    EVENTLOG_INFORMATION_TYPE,
                    MSG_I_SERVER_STOPPED,
                    g_wszCommonName);
            }
            CONSOLEPRINT0((
                DBG_SS_CERTSRV,
                "Certification Authority Service Stopped\n"));

        {
            //only perform Hash if the auditing is enabled
            if(AUDIT_FILTER_STARTSTOP & g_dwAuditFilter)
            {
                CertSrv::CAuditEvent event(
                    SE_AUDITID_CERTSRV_SERVICESTOP, 
                    g_dwAuditFilter);

                hr = ComputeMAC(g_wszDatabase, &g_pwszDBFileHash);
                _JumpIfErrorStr(hr, error, "ComputeMAC", g_wszDatabase);

                hr = event.AddData(g_pwszDBFileHash); // %1 database hash
                g_pwszDBFileHash.Cleanup();
                _JumpIfError(hr, error, "CAuditEvent::AddData");

                //
                // ... add code for retrieving key usage count from CSP
                //
                hr = event.AddData((DWORD)0); // %2 key usage count
                _JumpIfError(hr, error, "CAuditEvent::AddData");

                hr = event.Report();
                _JumpIfError(hr, error, "CAuditEvent::Report");
            }
        }
        }
        g_fStarted = FALSE;

        AuthzFreeResourceManager(g_AuthzCertSrvRM);
        g_AuthzCertSrvRM = NULL;
        g_CASD.Uninitialize();
        g_OfficerRightsSD.Uninitialize();

        // set "completely stopped" event
        if (!fConsoleActive)
	{
            SetEvent(g_hServiceStoppedEvent);
	}
    }

error:
    if (fCoInit)
    {
	CoUninitialize();
    }
    return(fShutDown);
}


// Control-C handler

BOOL
StopServer(
    IN DWORD dwCtrlType)
{
    HRESULT hr;

    // if successful shutdown
    if (SendMessage(g_hwndMain, WM_STOPSERVER, 0, 0))
    {
        if (!PostMessage(g_hwndMain, WM_SYNC_CLOSING_THREADS, S_OK, 0))
	{
	    hr = myHLastError();
	    _PrintError(hr, "PostMessage");
	}
    	SetConsoleCtrlHandler(StopServer, FALSE);
    }
    return(TRUE);
}


HRESULT
CertSrvEnterServer(
    OUT DWORD *pState)
{
    HRESULT hr;
    BOOL fEntered = FALSE;
    
    *pState = 0;	// Caller need not exit server
    if (!g_fShutdownCritSec)
    {
	hr = HRESULT_FROM_WIN32(ERROR_DLL_INIT_FAILED);
        _JumpError(hr, error, "InitializeCriticalSection");
    }
    EnterCriticalSection(&g_ShutdownCriticalSection);
    fEntered = TRUE;

    hr = CertSrvTestServerState();
    _JumpIfError(hr, error, "CertSrvTestServerState");

    g_cCalls++;
    g_cCallsActive++;
    *pState = 1;	// Caller must exit server
    hr = S_OK;

error:
    if (fEntered)
    {
        LeaveCriticalSection(&g_ShutdownCriticalSection);
    }
    return(hr);
}


HRESULT
CertSrvTestServerState()
{
    HRESULT hr;
    
    if (g_fRefuseIncoming)
    {
	hr = HRESULT_FROM_WIN32(ERROR_SHUTDOWN_IN_PROGRESS);
	_JumpError(hr, error, "g_fRefuseIncoming");
    }
    hr = S_OK;

error:
    return(hr);
}


HRESULT
CertSrvLockServer(
    IN OUT DWORD *pState)
{
    HRESULT hr;
    BOOL fEntered = FALSE;

    // Eliminate this thread from the active thread count
    
    CertSrvExitServer(*pState);
    *pState = 0;	// Caller no longer needs to exit server

    if (!g_fShutdownCritSec)
    {
	hr = HRESULT_FROM_WIN32(ERROR_DLL_INIT_FAILED);
        _JumpError(hr, error, "InitializeCriticalSection");
    }
    EnterCriticalSection(&g_ShutdownCriticalSection);
    fEntered = TRUE;

    g_fRefuseIncoming = TRUE;
    DBShutDown(TRUE);
    DBGPRINT((DBG_SS_CERTSRV, "LockServer(thread count = %u)\n", g_cCallsActive));
    while (0 < g_cCallsActive)
    {
	LeaveCriticalSection(&g_ShutdownCriticalSection);

	// Wait 15 seconds plus 2 seconds for each active call.

        hr = WaitForSingleObject(
			    g_hShutdownEvent,
			    (15 + 2 * g_cCallsActive) * 1000);
	EnterCriticalSection(&g_ShutdownCriticalSection);

	_PrintIfError(hr, "WaitForSingleObject");
	if ((HRESULT) WAIT_OBJECT_0 == hr)
	{
	    DBGPRINT((DBG_SS_CERTSRV, "LockServer(last thread exit event)\n"));
	}
	else if ((HRESULT) WAIT_TIMEOUT == hr)
	{
	    DBGPRINT((DBG_SS_CERTSRV, "LockServer(timeout)\n"));
	    break;
        }
	else if ((HRESULT) WAIT_ABANDONED == hr)
	{
	    DBGPRINT((DBG_SS_CERTSRV, "LockServer(wait abandoned)\n"));
        }
	DBGPRINT((DBG_SS_CERTSRV, "LockServer(thread count = %u)\n", g_cCallsActive));
    }
    DBGPRINT((DBG_SS_CERTSRV, "LockServer(done: thread count = %u)\n", g_cCallsActive));
    hr = S_OK;

error:
    if (fEntered)
    {
        LeaveCriticalSection(&g_ShutdownCriticalSection);
    }
    return(hr);
}


VOID
CertSrvExitServer(
    IN DWORD State)
{
    HRESULT hr;
    BOOL fEntered = FALSE;

    if (!g_fShutdownCritSec)
    {
	hr = HRESULT_FROM_WIN32(ERROR_DLL_INIT_FAILED);
        _JumpError(hr, error, "InitializeCriticalSection");
    }
    EnterCriticalSection(&g_ShutdownCriticalSection);
    fEntered = TRUE;

    if (State)
    {
	CSASSERT(0 < g_cCallsActive);
	if (0 == --g_cCallsActive && g_fRefuseIncoming)
	{
	    DBGPRINT((DBG_SS_CERTSRV, "ExitServer(set last thread exit event)\n"));
            SetEvent(g_hShutdownEvent);
	}
    }

error:
    if (fEntered)
    {
        LeaveCriticalSection(&g_ShutdownCriticalSection);
    }
}


// Test for alignment faults in the C runtimes.
// If the bug hasn't been fixed yet, log an event during cert server startup.

VOID
certsrvLogAlignmentFaultStatus()
{
    HRESULT hr;
    HRESULT hr2;
    ULONG_PTR ExceptionAddress;
    WCHAR awcAddress[2 + 2 * cwcDWORDSPRINTF];
    WCHAR const *apwsz[2];
    WORD cpwsz;
    WCHAR awchr[cwcHRESULTSTRING];
    WCHAR const *pwszStringErr = NULL;
    
    apwsz[1] = NULL;
    __try
    {
	fwprintf(stdout, L".");	  // may fault if I/O buffer is odd aligned
	fprintf(stdout, ".");
	fwprintf(stdout, L".\n"); // may fault if I/O buffer is odd aligned
	hr = S_OK;
    }
    __except(
	    ExceptionAddress = (ULONG_PTR) (GetExceptionInformation())->ExceptionRecord->ExceptionAddress,
	    hr = myHEXCEPTIONCODE(),
	    EXCEPTION_EXECUTE_HANDLER)
    {
	_PrintError(hr, "certsrvLogAlignmentFaultStatus: Exception");
    }
    if (S_OK != hr)
    {
	ALIGNIOB(stdout);	// align the stdio buffer
	wprintf(L"STDIO exception: 0x%x\n", hr);

	wsprintf(awcAddress, L"0x%p", ExceptionAddress);
	CSASSERT(wcslen(awcAddress) < ARRAYSIZE(awcAddress));

	apwsz[0] = awcAddress;
	pwszStringErr = myGetErrorMessageText(hr, TRUE);
	apwsz[1] = pwszStringErr;
	if (NULL == pwszStringErr)
	{
	    apwsz[1] = myHResultToString(awchr, hr);
	}
	cpwsz = ARRAYSIZE(apwsz);

	hr2 = LogEvent(
		    EVENTLOG_WARNING_TYPE,
		    MSG_E_STARTUP_EXCEPTION,
		    cpwsz,
		    apwsz);
	_JumpIfError(hr2, error, "LogEvent");
    }

error:
    if (NULL != pwszStringErr)
    {
	LocalFree(const_cast<WCHAR *>(pwszStringErr));
    }
}


#define MSTOSEC(ms)	(((ms) + 1000 - 1)/1000)
FNLOGEXCEPTION certsrvLogException;

HRESULT
certsrvStartServer(
    IN BOOL fConsoleActive)
{
    HRESULT hr;
    DWORD TimeStart;
    WCHAR awc[ARRAYSIZE(SAFEBOOT_DSREPAIR_STR_W)];
    DWORD cwc;
    DWORD dwEventType = EVENTLOG_ERROR_TYPE;
    DWORD dwIdEvent = 0;

    g_fStartInProgress = TRUE;
    DBGPRINT((
        DBG_SS_CERTSRV,
        "StartServer(tid=%d, fConsoleActive=%u)\n",
        GetCurrentThreadId(),
        fConsoleActive));
    TimeStart = GetTickCount();

    if (fConsoleActive)
    {
        g_fStartAsService = FALSE;
        SetConsoleCtrlHandler(StopServer, TRUE);
    }

    if (!FIsServer())
    {
        // don't allow startup on non-server SKU

        hr = HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION);
        _JumpError(hr, error, "FIsServer"); 
    }

    cwc = GetEnvironmentVariable(L"SAFEBOOT_OPTION", awc, ARRAYSIZE(awc));
    if (0 != cwc &&
	ARRAYSIZE(awc) > cwc &&
	0 == lstrcmpi(awc, SAFEBOOT_DSREPAIR_STR_W))
    {
        // log an error to the event log and stop immediately
	dwEventType = EVENTLOG_INFORMATION_TYPE;
	dwIdEvent = MSG_SAFEBOOT_DETECTED;

	hr = HRESULT_FROM_WIN32(ERROR_RETRY);
	_JumpError(hr, error, "Not starting service: booted in DSRepair mode");
    }
    
    g_fAdvancedServer = FIsAdvancedServer();

    if (!AuthzInitializeResourceManager(
            0,
            CallbackAccessCheck,
            NULL,
            NULL,
            L"CertSrv",
            &g_AuthzCertSrvRM))
    {
        hr = myHLastError();
        _PrintError(hr, "AuthzInitializeResourceManager");
        if (E_INVALIDARG != hr || (2 > g_fAdvancedServer && IsWhistler()))
        {
            goto error;
        }
    }

    hr = CoreInit();
    if (S_OK != hr)
    {
	dwIdEvent = MAXDWORD;	// Error event already logged
	_JumpError(hr, error, "CoreInit");
    }
    certsrvLogAlignmentFaultStatus();
    myLogExceptionInit(certsrvLogException);

    hr = RPCInit();
    if (S_OK != hr)
    {
	dwIdEvent = MSG_E_RPC_INIT;
        _JumpError(hr, error, "RPCInit");
    }

    hr = SetRegistryDcomConfig(fConsoleActive);
    if (S_OK != hr)
    {
	dwIdEvent = MSG_E_REGISTRY_DCOM;
        _JumpError(hr, error, "SetRegistryDcomConfig");
    }

    hr = CertStartClassFactories();
    if (S_OK != hr)
    {
	dwIdEvent = CO_E_WRONG_SERVER_IDENTITY == hr?
		    MSG_E_SERVER_IDENTITY : MSG_E_CLASS_FACTORIES;
        _JumpError(hr, error, "CertStartClassFactories");
    }

    {
        //only perform Hash if the auditing is enabled
        if(AUDIT_FILTER_STARTSTOP & g_dwAuditFilter)
        {
            CertSrv::CAuditEvent event(
			            SE_AUDITID_CERTSRV_SERVICESTART,
			            g_dwAuditFilter);

            hr = event.AddData(g_pwszDBFileHash); // %1 database hash
            g_pwszDBFileHash.Cleanup();
            _JumpIfError(hr, error, "CAuditEvent::AddData");
    
            //
            // ... add code for retrieving key usage count from CSP
            //
            hr = event.AddData((DWORD)0); // %2 key usage count
            _JumpIfError(hr, error, "CAuditEvent::AddData");

            hr = event.Report();
            _JumpIfError(hr, error, "CAuditEvent::Report");
        }
    }

    {
        CertSrv::CAuditEvent event(
			        SE_AUDITID_CERTSRV_ROLESEPARATIONSTATE,
			        g_dwAuditFilter);

        hr = event.AddData(CAuditEvent::RoleSeparationIsEnabled()); // %1 is role separation enabled?
        _JumpIfError(hr, error, "CAuditEvent::AddData");
    
        hr = event.Report();
        _JumpIfError(hr, error, "CAuditEvent::Report");
    }


    if (CERTLOG_TERSE <= g_dwLogLevel)
    {
        LogEventString(
		EVENTLOG_INFORMATION_TYPE,
		MSG_I_SERVER_STARTED,
		g_wszCommonName);
    }
    
    CONSOLEPRINT1((
        DBG_SS_CERTSRV,
        "Certification Authority Service Ready (%us) ...\n",
        MSTOSEC(GetTickCount() - TimeStart)));
    g_fStarted = TRUE;
    CSASSERT(S_OK == hr);

error:
    if (S_OK != hr)
    {
	if (MAXDWORD != dwIdEvent)
	{
	    if (0 == dwIdEvent)
	    {
		dwIdEvent = MSG_E_GENERIC_STARTUP_FAILRE;
	    }
	    LogEventStringHResult(
			dwEventType,
			dwIdEvent,
			g_wszCommonName,
			EVENTLOG_INFORMATION_TYPE == dwEventType? S_OK : hr);
	}

        CertSrvStopServer(fConsoleActive);
        
        // returning error here results in repost to scm
    }

    g_fStartInProgress = FALSE;
    return(hr);
}


VOID
certsrvLogException(
    IN HRESULT hrEvent,
    IN EXCEPTION_POINTERS const *pep,
    OPTIONAL IN char const *pszFileName,
    IN DWORD dwFile,
    IN DWORD dwLine)
{
    HRESULT hr;
    WCHAR awcFile[2 + 3 * cwcDWORDSPRINTF];
    WCHAR awcFlags[3 + cwcDWORDSPRINTF];
    WCHAR awcAddress[2 + 2 * cwcDWORDSPRINTF];
    WCHAR const *apwsz[4];
    WORD cpwsz;
    WCHAR awchr[cwcHRESULTSTRING];
    WCHAR const *pwszStringErr = NULL;

    wsprintf(awcFile, L"%u.%u.%u", dwFile, dwLine, MSG_E_EXCEPTION);
    CSASSERT(wcslen(awcFile) < ARRAYSIZE(awcFile));

    wsprintf(awcFlags, L"0x%08x", pep->ExceptionRecord->ExceptionFlags);
    CSASSERT(wcslen(awcFlags) < ARRAYSIZE(awcFlags));

    wsprintf(awcAddress, L"0x%p", pep->ExceptionRecord->ExceptionAddress);
    CSASSERT(wcslen(awcAddress) < ARRAYSIZE(awcAddress));

    apwsz[0] = awcFile;
    apwsz[1] = awcAddress;
    apwsz[2] = awcFlags;
    pwszStringErr = myGetErrorMessageText(hrEvent, TRUE);
    apwsz[3] = pwszStringErr;
    if (NULL == pwszStringErr)
    {
	apwsz[3] = myHResultToString(awchr, hrEvent);
    }
    cpwsz = ARRAYSIZE(apwsz);

    hr = LogEvent(EVENTLOG_ERROR_TYPE, MSG_E_EXCEPTION, cpwsz, apwsz);
    _JumpIfError(hr, error, "LogEvent");

error:
    if (NULL != pwszStringErr)
    {
	LocalFree(const_cast<WCHAR *>(pwszStringErr));
    }
}


DWORD
CertSrvStartServerThread(
    IN VOID *pvArg)
{
    HRESULT hr = S_OK;
    DWORD Flags = (DWORD) (ULONG_PTR) pvArg;
    BOOL b;
    ULONG_PTR ulp;

    // Anatomy of startup code
    // if g_fStartAsService, just registers this new thread as the main
    // thread and blocks until the ServiceMain fxn returns.
                        
    // We're in a non-rpc thread; check if we need to create VRoots.  I would
    // have liked to have moved this into CoreInit, but we're limited in where
    // we can do this (can't be calling into RPC during RPC call).
    //
    // If the SetupStatus SETUP_ATTEMPT_VROOT_CREATE registry flag is clear,
    // this call is a nop.  A separate thread is created to access the IIS
    // metabase.  If it hangs, it will be nuked -- after the specified timeout.
    // This call returns immediately, so the only detectable error is likely
    // to be a thread creation problem.

    // if we're doing anything other than starting the service controller,
    // check to see if the vroots need to be created.

    if (0 == (Flags & CSST_STARTSERVICECONTROLLER))
    {
	WCHAR *pwszPath = NULL;
	DWORD cb = sizeof(ENUM_CATYPES);
	DWORD dwType;
	ENUM_CATYPES CAType = ENUM_UNKNOWN_CA;
	HKEY hkey = NULL;

	hr = myRegOpenRelativeKey(
				NULL,
				L"ca",
				RORKF_CREATESUBKEYS,
				&pwszPath,
				NULL,           // ppwszName
				&hkey);
	_PrintIfError(hr, "myRegOpenRelativeKey");
	if (S_OK == hr)
	{
	    DBGPRINT((DBG_SS_CERTLIBI, "%ws\n", pwszPath));
	    cb = sizeof(CAType);
	    hr = RegQueryValueEx(
		 hkey,
		 wszREGCATYPE,
		 NULL,
		 &dwType,
		 (BYTE *) &CAType,
		 &cb);
	    _PrintIfErrorStr(hr, "RegQueryValueEx", wszREGCATYPE);
	}
	if (pwszPath)
	    LocalFree(pwszPath);
	if (hkey)
	   RegCloseKey(hkey);

	hr = myModifyVirtualRootsAndFileShares(
		    VFF_CREATEVROOTS |		// Create VRoots
			VFF_CREATEFILESHARES |	// Create File Shares
			VFF_CHECKREGFLAGFIRST |	// Skip if reg flag clear
			VFF_CLEARREGFLAGFIRST,	// Clear flag before attempt
		    CAType,
		    TRUE,           // asynch call -- don't block
		    VFCSEC_TIMEOUT, // wait this long before giving up
		    NULL,
		    NULL);
	if (S_OK != hr)
	{
	    LogEventHResult(
		    EVENTLOG_INFORMATION_TYPE,
		    MSG_E_IIS_INTEGRATION_ERROR,
		    hr);
	}
    }

    // StartServiceCtrlDispatcher should hang until certsrv terminates

    if ((CSST_STARTSERVICECONTROLLER & Flags) &&
        !StartServiceCtrlDispatcher(steDispatchTable))
    {
        hr = myHLastError();
        if (HRESULT_FROM_WIN32(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) != hr)
        {
            _JumpError(hr, error, "StartServiceCtrlDispatcher");
        }
        CONSOLEPRINT0((
            DBG_SS_CERTSRV,
            "CertSrv: Failed to connect to service controller -- running in standalone mode\n"));

        Flags &= ~CSST_STARTSERVICECONTROLLER;
        Flags |= CSST_CONSOLE;
    }

   
    if (0 == (CSST_STARTSERVICECONTROLLER & Flags))
    {
        DBGPRINT((
            DBG_SS_CERTSRVI,
            "SendMessageTimeout(tid=%d, hwnd=0x%x, msg=0x%x)\n",
            GetCurrentThreadId(),
            g_hwndMain,
            WM_STARTSERVER));

        b = SendMessageTimeout(
			g_hwndMain,
			WM_STARTSERVER,
			(CSST_CONSOLE & Flags)? TRUE : FALSE, // fConsoleActive
			0,
			SMTO_BLOCK,
			MAXLONG,
			&ulp) != 0;
        if (!b)
        {
            hr = myHLastError();
            _JumpError(hr, error, "SendMessageTimeout");
        }
        else if (ulp != S_OK)
        {
            hr = (HRESULT) ulp;
            _JumpError(hr, error, "SendMessageTimeout");
        }
    }

    if (Flags & CSST_CONSOLE)
    {   
        // we're running as console, and so don't have a CRL publishing thread. 
        // Use this one since no one cares if it returns

        // if svc, we do this in the caller of this function
        CertSrvBlockThreadUntilStop();
    }

error:

    // on return, this thread dies
    return(hr);
}


VOID
Usage(
    IN BOOL fUsageInternal)
{
    WCHAR awcUsage[2048];

    if (LoadString(NULL, IDS_USAGE, awcUsage, ARRAYSIZE(awcUsage)))
    {
	CONSOLEPRINT1((MAXDWORD, "%ws", awcUsage));
    }
    if (fUsageInternal)
    {
	if (LoadString(NULL, IDS_USAGE_FULL, awcUsage, ARRAYSIZE(awcUsage)))
	{
	    CONSOLEPRINT1((MAXDWORD, "%ws", awcUsage));
	}
#if DBG_COMTEST
	if (LoadString(NULL, IDS_USAGE_COMTEST, awcUsage, ARRAYSIZE(awcUsage)))
	{
	    CONSOLEPRINT1((MAXDWORD, "%ws", awcUsage));
	}
#endif
    }
}


int
ArgvParseCommandLine(
    IN int argc,
    IN WCHAR *argv[])
{
    HRESULT hr;

    myVerifyResourceStrings(g_hInstApp);

    hr = E_INVALIDARG;
    while (1 < argc && (L'-' == argv[1][0] || L'/' == argv[1][0]))
    {
	WCHAR *pwsz = argv[1];
	BOOL fUsage = FALSE;
	BOOL fUsageInternal = FALSE;

	while (*++pwsz != L'\0')
	{
	    switch (*pwsz)
	    {
#if DBG_COMTEST
		case L'C':
		case L'c':
		    fComTest = TRUE;
		    break;
#endif

		case L'N':
		case L'n':
		    g_fCreateDB = TRUE;
		    break;

		case L'Z':
		case L'z':
		    g_fStartAsService = FALSE;
		    break;

		case L'S':
		case L's':
		    g_fCryptSilent = TRUE;
		    break;

		case L'?':
		case L'u':
		    fUsage = TRUE;
		    if (0 == lstrcmp(pwsz, L"uSAGE"))
		    {
			fUsageInternal = TRUE;
		    }
		    // FALLTHROUGH

		default:
		    Usage(fUsageInternal);
		    if (fUsage)
		    {
			goto error;
		    }
		    _JumpError(hr, error, "bad command line option");
	    }
	}
	argc--;
	argv++;
    }
    if (argc != 1)
    {
	Usage(FALSE);
	_JumpError(hr, error, "extra args");
    }
    hr = S_OK;

error:
    return(hr);
}


typedef int (FNARGVMAIN)(
    IN int argc,
    IN WCHAR *argv[]);


//+------------------------------------------------------------------------
// FUNCTION:	CertArgvMainDispatch
//
// NOTES:	Takes a WCHAR * command line and chews it up into argc/argv
//		form so it can be passed on to a traditional C-style main.
//-------------------------------------------------------------------------

int
CertArgvMainDispatch(
    IN FNARGVMAIN *pfnMain,
    IN WCHAR *pwszAppName,
    IN WCHAR const *pwszCmdLine)
{
    WCHAR buf[MAX_PATH];
    WCHAR *apwszArg[20];
    int cArg = 0;
    LPWSTR p = buf;
    WCHAR wcEnd;

    apwszArg[cArg++] = pwszAppName;
    while (*pwszCmdLine != L'\0')
    {
        while (*pwszCmdLine == L' ')
        {
            pwszCmdLine++;
        }
        if (*pwszCmdLine != L'\0')
        {
            wcEnd = L' ';
            if (*pwszCmdLine == L'"')
            {
                wcEnd = *pwszCmdLine++;
            }
            apwszArg[cArg++] = p;
            while (*pwszCmdLine != L'\0' && *pwszCmdLine != wcEnd)
            {
                *p++ = *pwszCmdLine++;
            }
            *p++ = L'\0';
            if (*pwszCmdLine != L'\0')
            {
                pwszCmdLine++;	// skip blank or quote character
            }
        }
    }
    apwszArg[cArg] = NULL;

    return((*pfnMain)(cArg, apwszArg));
}


//+------------------------------------------------------------------------
// FUNCTION:	MainWndProc(...)
//-------------------------------------------------------------------------

LRESULT APIENTRY
MainWndProc(
    IN HWND hWnd,
    IN UINT msg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    WCHAR *pwszCmdLine;
    HRESULT hr;
    LPARAM lRet = 0;
    
    DBGPRINT((
        DBG_SS_CERTSRVI,
        "MainWndProc(tid=%d) msg=0x%x, wp=0x%x, lp=0x%x\n",
        GetCurrentThreadId(),
        msg,
        wParam,
        lParam));
    
    switch (msg)
    {
    case WM_CREATE:
    case WM_SIZE:
        break;
        
    case WM_DESTROY:
        if (!g_fStartAsService)
            PostQuitMessage(S_OK);
        break;
        

    case WM_ENDSESSION:
        // only stop on a real shutdown,
        // never look at this msg if running as svc
        if (g_fStartAsService || (0 == wParam) || (0 != lParam))
        {
            break;
        }
        // fall through

    case WM_STOPSERVER:
        lRet = CertSrvStopServer(!g_fStartAsService);

        break;
        
    case WM_SYNC_CLOSING_THREADS:
        hr = (HRESULT) lParam;
        
        // sync: wait for SCM to return control to exiting CertSrvStartServerThread
        if (WAIT_OBJECT_0 != WaitForSingleObject(g_hServiceThread, 10 * 1000))
        {
            hr = WAIT_TIMEOUT;
        }
        PostQuitMessage(hr);
        break;
        
    case WM_STARTSERVER:
        hr = CoInitializeEx(NULL, GetCertsrvComThreadingModel());
        if (S_FALSE == hr)
        {
            hr = S_OK;
        }
        if (S_OK != hr)
        {
            LogEventString(
                EVENTLOG_ERROR_TYPE,
                MSG_E_OLE_INIT_FAILED,
                NULL);
            _PrintError(hr, "CoInitializeEx");
        }
        else
        {
            hr = certsrvStartServer((BOOL) wParam);
            _PrintIfError(hr, "certsrvStartServer");
        }

        if (S_OK != hr)
        {
            if ((BOOL) wParam)	// fConsoleActive
	    {
                 PostQuitMessage(hr);
	    }
            lRet = hr;		// set this so caller knows we failed
        }
        break;
        
    case WM_SUSPENDSERVER:
        break;
        
    case WM_RESTARTSERVER:
        break;

    default:
        lRet = DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return(lRet);
}

/*

Complete anatomy of certificate server startup/shutdown

WinMain():
|
|g_hSvcThread = CreateThread(CertSrvStartServerThread(SVC_CONTROLLER))
|                     |
|[MessageLoop         \
| processing           CertSrvStartServerThread(SVC_CONTROLLER):
| until                |StartSvcCtrlDispatcher(ServiceMain)
| WM_QUIT]             ||ServiceMain:
|                      ||RegisterSvcCtrlHandler(ServiceControlHandler())
|                      ||hStartThread = CreateThread(CertSrvStartServerThread(0))
|                      ||                    |
|                      ||                    \ 
|                      ||                     CertSrvStartServerThread(0):
|                      ||                     |SendMessage(WM_STARTSERVER)
|                      ||                     \return   // CertSrvStartServerThread(0)
|                      ||                      (Thread Terminates)
|                      ||WaitForSingleObject(hStartThread), pinging SCM
|                      ||CertSrvBlockThreadUntilStop()
|                      |||WaitForSingleObject(g_hSvcStoppingEvent) ***steady state***
|                      ||\return   // CertSrvBlockThreadUntilStop()
|                      ||WaitForSingleObject(g_hSvcStoppedEvent), pinging SCM
|                      ||PostMessage(WM_SYNC_CLOSING_THREADS)
|                      |\return    // StartSvcCtrlDispatcher(ServiceMain)
|                      \return     // CertSrvStartServerThread(SVC_CONTROLLER)
|                       (Thread Terminates)
| WM_QUIT:
\ return
  (Process Terminates)

ServiceControlHandler special functions:
SERVICE_CONTROL_STOP:
|PostMessage(WM_STOPSERVER)
\break

MessageLoop special functions: 

WM_SYNC_CLOSING_THREADS:
|WaitForSingleObject(g_hSvcThread)
|PostQuitMessage()  // WM_QUIT to msgloop
\break

WM_STOPSERVER:
|CertSrvStopServer():
|| Signal(g_hServiceStoppingEvent)
|| Signal(g_hServiceStoppedEvent)
|\ return // CertSrvStopServer()
\break

*/


//+------------------------------------------------------------------------
//  Function:	wWinMain()
//
//  Synopsis:	Entry Point
//
//  Arguments:	[hInstance]	-- Instance handle
//		[hPrevInstance] -- Obsolete
//		[lpCmdLine]	-- App command line
//		[nCmdShow]	-- Starting show state
//-------------------------------------------------------------------------
extern "C" int APIENTRY
wWinMain(
    IN HINSTANCE hInstance,
    IN HINSTANCE hPrevInstance,
    IN LPWSTR lpCmdLine,
    IN int nCmdShow)
{
    MSG msg;
    WNDCLASSEX wcApp;
    ATOM atomClass;
    HRESULT hr;
    BOOL fCoInit = FALSE;
    WCHAR awchr[cwcHRESULTSTRING];
    WCHAR const *pwszMsgAlloc;
    WCHAR const *pwszMsg;
#if DBG_CERTSRV
    WCHAR *pwszThreadModel = NULL;
#endif

    _setmode(_fileno(stdout), _O_TEXT);
    _wsetlocale(LC_ALL, L".OCP");

    DBGPRINTINIT("+certsrv.log");
    DBGPRINT((DBG_SS_CERTSRVI, "Main Thread = %x\n", GetCurrentThreadId()));

    g_dwDelay0 = GetRegistryDwordValue(L"Delay0");
    g_dwDelay1 = GetRegistryDwordValue(L"Delay1");
    g_dwDelay2 = GetRegistryDwordValue(L"Delay2");

    if (0 != g_dwDelay0)
    {
	DBGPRINT((
		DBG_SS_CERTSRV,
		"wWinMain(0): sleeping %u seconds\n",
		g_dwDelay0));
	Sleep(1000 * g_dwDelay0);
    }

    // Save the current instance
    g_hInstApp = hInstance;
    ZeroMemory(&wcApp, sizeof(wcApp));

    // Set up the application's window class
    wcApp.cbSize	= sizeof(wcApp);
    wcApp.lpfnWndProc	= MainWndProc;
    wcApp.hInstance	= hInstance;
    wcApp.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
    wcApp.hCursor	= LoadCursor(NULL, IDC_ARROW);
    wcApp.hbrBackground	= NULL; // try to not pull in GDI32

    wcApp.lpszClassName	= g_wszAppName;

    atomClass = RegisterClassEx(&wcApp);
    if (!atomClass)
    {
	hr = myHLastError();
	_JumpError(hr, error, "RegisterClassEx");
    }

    // Create Main Window

    g_hwndMain = CreateWindowEx(
			0,			   // dwExStyle
			(WCHAR const *) atomClass, // lpClassName
			L"Certification Authority",// lpWindowName
			WS_OVERLAPPEDWINDOW,	   // dwStyle
			//0,		           // dwStyle
			CW_USEDEFAULT,		   // x
			CW_USEDEFAULT,		   // y
			CW_USEDEFAULT,		   // nWidth
			CW_USEDEFAULT,		   // nHeight
			NULL,			   // hWndParent
			NULL,			   // hMenu
			hInstance,		   // hInstance
			NULL);			   // lpParam

    if (NULL == g_hwndMain)
    {
	hr = myHLastError();
	_JumpError(hr, error, "CreateWindowEx");
    }
    DBGPRINT((DBG_SS_CERTSRVI, "Main Window = %x\n", g_hwndMain));

    // Make window visible
    // ShowWindow(g_hwndMain,nCmdShow);

    hr = CertArgvMainDispatch(ArgvParseCommandLine, g_wszAppName, lpCmdLine);
    _JumpIfError2(hr, error, "CertArgvMainDispatch", E_INVALIDARG);

    // Update window client area
    // UpdateWindow(g_hwndMain);

    if (0 != g_dwDelay1)
    {
	DBGPRINT((
		DBG_SS_CERTSRV,
		"wWinMain(1): sleeping %u seconds\n",
		g_dwDelay1));
	Sleep(1000 * g_dwDelay1);
    }

    hr = CoInitializeEx(NULL, GetCertsrvComThreadingModel());
    if (S_OK != hr && S_FALSE != hr)
    {
	LogEventStringHResult(
			EVENTLOG_ERROR_TYPE,
			MSG_E_CO_INITIALIZE,
			g_wszCommonName,
			hr);
	_JumpError(hr, error, "CoInitializeEx");
    }
    fCoInit = TRUE;

    g_hServiceStoppingEvent = CreateEvent(NULL, TRUE, FALSE, NULL);  
    if (NULL == g_hServiceStoppingEvent)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CreateEvent");
    }
    g_hServiceStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (NULL == g_hServiceStoppedEvent)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CreateEvent");
    }
    g_hCRLManualPublishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (NULL == g_hCRLManualPublishEvent)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CreateEvent");
    }
    g_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (NULL == g_hShutdownEvent)
    {
        hr = myHLastError();
        _JumpError(hr, error, "CreateEvent");
    }
    __try
    {
	InitializeCriticalSection(&g_ShutdownCriticalSection);
	g_fShutdownCritSec = TRUE;
	hr = S_OK;
    }
    __except(hr = myHEXCEPTIONCODE(), EXCEPTION_EXECUTE_HANDLER)
    {
    }
    _JumpIfError(hr, error, "InitializeCriticalSection");

    g_hServiceThread = CreateThread(
			    NULL,	// lpThreadAttributes (Security Attr)
			    0,		// dwStackSize
			    CertSrvStartServerThread,
			    (VOID *) UlongToPtr((g_fStartAsService ? CSST_STARTSERVICECONTROLLER : CSST_CONSOLE)), // lpParameter
			    0,		// dwCreationFlags
			    &g_ServiceThreadId);
    if (NULL == g_hServiceThread)
    {
	hr = myHLastError();
	LogEventStringHResult(
			EVENTLOG_ERROR_TYPE,
			MSG_E_SERVICE_THREAD,
			g_wszCommonName,
			hr);
	_JumpError(hr, error, "CreateThread");
    }
    DBGPRINT((DBG_SS_CERTSRVI, "Service Thread = %x\n", g_ServiceThreadId));

    // Message Loop
    while (TRUE)
    {
	BOOL b;

	b = GetMessage(&msg, NULL, 0, 0);
	if (!b)
	{
	    hr = (HRESULT)msg.wParam;
	    _JumpIfError(hr, error, "WM_QUIT");
	    break;
	}
	if (-1 == (LONG) b)
	{
	    hr = myHLastError();
	    _JumpError(hr, error, "GetMessage");
	}
	DBGPRINT((
		DBG_SS_CERTSRVI,
		"DispatchMessage(tid=%d) msg=0x%x, wp=0x%x, lp=0x%x\n",
		GetCurrentThreadId(),
		msg.message,
		msg.wParam,
		msg.lParam));
	DispatchMessage(&msg);
    }

error:
    if (fCoInit)
    {
	CoUninitialize();
    }
    if (g_fShutdownCritSec)
    {
	DeleteCriticalSection(&g_ShutdownCriticalSection);
	g_fShutdownCritSec = FALSE;
    }
    if (NULL != g_hShutdownEvent)
    {
        CloseHandle(g_hShutdownEvent);
    }
    if (NULL != g_hServiceThread)
    {
        CloseHandle(g_hServiceThread);
    }
    if (NULL != g_hServiceStoppingEvent)
    {
	CloseHandle(g_hServiceStoppingEvent);
    }
    if (NULL != g_hServiceStoppedEvent)
    {
        CloseHandle(g_hServiceStoppedEvent);
    }
    if (NULL != g_hCRLManualPublishEvent)
    {
        CloseHandle(g_hCRLManualPublishEvent);
    }
    CAuditEvent::CleanupAuditEventTypeHandles();

    pwszMsgAlloc = NULL;
    pwszMsg = L"S_OK";
    if (S_OK != hr)
    {
	pwszMsgAlloc = myGetErrorMessageText(hr, TRUE);
	if (NULL != pwszMsgAlloc)
	{
	    pwszMsg = pwszMsgAlloc;
	}
	else
	{
	    pwszMsg = myHResultToString(awchr, hr);
	}
    }
    CONSOLEPRINT1((DBG_SS_CERTSRV, "Exit Status = %ws\n", pwszMsg));
    if (NULL != pwszMsgAlloc)
    {
	LocalFree(const_cast<WCHAR *>(pwszMsgAlloc));
    }
    myFreeResourceStrings("certsrv.exe");
    myFreeColumnDisplayNames();
    myRegisterMemDump();
    return(hr);
}