windows-nt/Source/XPSP1/NT/ds/security/cryptoapi/cryptsvc/service.cpp

881 lines
21 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1993-1995 Microsoft Corporation. All Rights Reserved.
//
// MODULE: service.c
//
// PURPOSE: Implements functions required by all services
// and takes into account dumbed-down win95 services
//
// FUNCTIONS:
// main(int argc, char **argv);
// service_ctrl(DWORD dwCtrlCode);
// CryptServiceMain(DWORD dwArgc, LPWSTR *lpszArgv);
// WinNTDebugService(int argc, char **argv);
// ControlHandler ( DWORD dwCtrlType );
//
// COMMENTS:
//
// AUTHOR: Craig Link - Microsoft Developer Support
// MODIFIED: Matt Thomlinson
// Scott Field
//
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <svcs.h> // SVCS_
#include <wincrypt.h>
#include <cryptui.h>
#include "keysvr.h"
#include "lenroll.h"
#include "keysvc.h"
#include "keysvcc.h"
#include "cerrpc.h"
#include "service.h"
#include "unicode.h"
#include "unicode5.h"
#include "catdb.h"
#define CRYPTSVC_EVENT_STOP "CRYPTSVC_EVENT_STOP"
#define RTN_OK 0 // no errors
#define RTN_USAGE 1 // usage error (invalid commandline)
#define RTN_ERROR_INIT 2 // error during service initialization
#define RTN_ERROR_INSTALL 13 // error during -install or -remove
#define RTN_ERROR_INSTALL_SIG 14 // error installing signature(s)
#define RTN_ERROR_INSTALL_START 15 // could not start service during install
#define RTN_ERROR_INSTALL_SHEXT 16 // error installing shell extension
//
// global module handle used to reference resources contained in this module.
//
HINSTANCE g_hInst = NULL;
// internal variables
static SERVICE_STATUS ssStatus; // current status of the service
SERVICE_STATUS_HANDLE sshStatusHandle;
// internal function prototypes
void WINAPI service_ctrl(DWORD dwCtrlCode);
void WINAPI CryptServiceMain(DWORD dwArgc, LPWSTR *lpszArgv);
extern BOOL _CatDBServiceInit(BOOL fUnInit);
//
// forward declaration for security callbacks
//
RPC_IF_CALLBACK_FN CryptSvcSecurityCallback;
long __stdcall
CryptSvcSecurityCallback(void * Interface, void *Context)
{
RPC_STATUS Status;
unsigned int RpcClientLocalFlag;
Status = I_RpcBindingIsClientLocal(NULL, &RpcClientLocalFlag);
if (Status != RPC_S_OK)
{
return (RPC_S_ACCESS_DENIED);
}
if (RpcClientLocalFlag == 0)
{
return (RPC_S_ACCESS_DENIED);
}
return (RPC_S_OK);
}
BOOL
WINAPI
DllMain(
HMODULE hInst,
DWORD dwReason,
LPVOID lpReserved
)
{
if( dwReason == DLL_PROCESS_ATTACH ) {
g_hInst = hInst;
DisableThreadLibraryCalls(hInst);
}
return TRUE;
}
DWORD
WINAPI
Start(
LPVOID lpV
)
{
BOOL fIsNT = FIsWinNT();
int iRet;
//
// surpress dialog boxes generated by missing files, etc.
//
SetErrorMode(SEM_NOOPENFILEERRORBOX);
SERVICE_TABLE_ENTRYW dispatchTable[] =
{
{ SZSERVICENAME, (LPSERVICE_MAIN_FUNCTIONW)CryptServiceMain },
{ NULL, NULL }
};
#ifdef WIN95_LEGACY
if (!fIsNT)
goto dispatch95;
#endif // WIN95_LEGACY
// if it doesn't match any of the above parameters
// the service control manager may be starting the service
// so we must call StartServiceCtrlDispatcher
if(!FIsWinNT5()) {
if (!StartServiceCtrlDispatcherW(dispatchTable))
AddToMessageLog(L"StartServiceCtrlDispatcher failed.");
} else {
CryptServiceMain( 0, NULL );
}
return RTN_OK;
#ifdef WIN95_LEGACY
dispatch95:
//
// Win95 doesn't support services, except as pseudo-.exe files
//
HMODULE hKernel = GetModuleHandleA("kernel32.dll");
if (NULL == hKernel)
{
AddToMessageLog(L"RegisterServiceProcess module handle failed");
return RTN_ERROR_INIT;
}
// inline typedef: COOL!
typedef DWORD REGISTERSERVICEPROCESS(
DWORD dwProcessId,
DWORD dwServiceType);
REGISTERSERVICEPROCESS* pfnRegSvcProc = NULL;
// Make sure Win95 Logoff won't stop our .exe
if (NULL == (pfnRegSvcProc = (REGISTERSERVICEPROCESS*)GetProcAddress(hKernel, "RegisterServiceProcess")))
{
AddToMessageLog(L"RegisterServiceProcess failed");
return RTN_ERROR_INIT;
}
pfnRegSvcProc(GetCurrentProcessId(), TRUE); // register this process ID as a service process
//
// call re-entry point and return with result of it.
//
iRet = ServiceStart(0, 0);
if(iRet != ERROR_SUCCESS)
AddToMessageLog(L"ServiceStart error!");
return iRet;
#endif // WIN95_LEGACY
}
//
// FUNCTION: CryptServiceMain
//
// PURPOSE: To perform actual initialization of the service
//
// PARAMETERS:
// dwArgc - number of command line arguments
// lpszArgv - array of command line arguments
//
// RETURN VALUE:
// none
//
// COMMENTS:
// This routine performs the service initialization and then calls
// the user defined ServiceStart() routine to perform majority
// of the work.
//
void WINAPI CryptServiceMain(DWORD dwArgc, LPWSTR * /*lpszArgv*/)
{
DWORD dwLastError = ERROR_SUCCESS;
// register our service control handler:
//
sshStatusHandle = RegisterServiceCtrlHandlerW( SZSERVICENAME, service_ctrl);
if (!sshStatusHandle)
return;
// SERVICE_STATUS members that don't change in example
//
ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ssStatus.dwServiceSpecificExitCode = 0;
// report the status to the service control manager.
//
if (!ReportStatusToSCMgr(
SERVICE_START_PENDING, // service state
NO_ERROR, // exit code
3000 // wait hint
)) return ;
dwLastError = ServiceStart(0, 0);
return;
}
//
// FUNCTION: service_ctrl
//
// PURPOSE: This function is called by the SCM whenever
// ControlService() is called on this service.
//
// PARAMETERS:
// dwCtrlCode - type of control requested
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
VOID WINAPI service_ctrl(DWORD dwCtrlCode)
{
// Handle the requested control code.
//
switch(dwCtrlCode)
{
// Stop the service.
//
case SERVICE_CONTROL_STOP:
//
// tell the SCM we are stopping before triggering StopService() code
// to avoid potential race condition during STOP_PENDING -> STOPPED transition
//
ssStatus.dwCurrentState = SERVICE_STOP_PENDING;
ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
ServiceStop();
return;
case SERVICE_CONTROL_SHUTDOWN:
ServiceStop();
return;
// Update the service status.
//
case SERVICE_CONTROL_INTERROGATE:
break;
// invalid control code
//
default:
break;
}
ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
}
//
// FUNCTION: ReportStatusToSCMgr()
//
// PURPOSE: Sets the current status of the service and
// reports it to the Service Control Manager
//
// PARAMETERS:
// dwCurrentState - the state of the service
// dwWin32ExitCode - error code to report
// dwWaitHint - worst case estimate to next checkpoint
//
// RETURN VALUE:
// TRUE - success
// FALSE - failure
//
// COMMENTS:
//
BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
BOOL fResult = TRUE;
if (dwCurrentState == SERVICE_START_PENDING)
ssStatus.dwControlsAccepted = 0;
else
ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_SHUTDOWN;
ssStatus.dwCurrentState = dwCurrentState;
if(dwWin32ExitCode == 0) {
ssStatus.dwWin32ExitCode = 0;
} else {
ssStatus.dwServiceSpecificExitCode = dwWin32ExitCode;
ssStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
}
ssStatus.dwWaitHint = dwWaitHint;
if ( ( dwCurrentState == SERVICE_RUNNING ) ||
( dwCurrentState == SERVICE_STOPPED ) )
ssStatus.dwCheckPoint = 0;
else
ssStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the service control manager.
//
if (!(fResult = SetServiceStatus( sshStatusHandle, &ssStatus))) {
AddToMessageLog(L"SetServiceStatus");
}
return fResult;
}
//
// FUNCTION: AddToMessageLog(LPWSTR lpszMsg)
//
// PURPOSE: Allows any thread to log an error message
//
// PARAMETERS:
// lpszMsg - text for message
//
// RETURN VALUE:
// none
//
// COMMENTS:
//
VOID AddToMessageLog(LPWSTR lpszMsg)
{
DWORD dwLastError = GetLastError();
if(FIsWinNT()) {
//
// WinNT: Use event logging to log the error.
//
WCHAR szMsg[512];
HANDLE hEventSource;
LPWSTR lpszStrings[2];
hEventSource = RegisterEventSourceW(NULL, SZSERVICENAME);
if(hEventSource == NULL)
return;
wsprintfW(szMsg, L"%s error: %lu", SZSERVICENAME, dwLastError);
lpszStrings[0] = szMsg;
lpszStrings[1] = lpszMsg;
ReportEventW(hEventSource, // handle of event source
EVENTLOG_ERROR_TYPE, // event type
0, // event category
0, // event ID
NULL, // current user's SID
2, // strings in lpszStrings
0, // no bytes of raw data
(LPCWSTR*)lpszStrings, // array of error strings
NULL); // no raw data
(VOID) DeregisterEventSource(hEventSource);
}
#ifdef WIN95_LEGACY
else {
//
// Win95: log error to file
//
HANDLE hFile;
SYSTEMTIME st;
CHAR szMsgOut[512];
DWORD cchMsgOut;
DWORD dwBytesWritten;
hFile = CreateFileA(
"pstore.log",
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
0,
NULL
);
if(hFile == INVALID_HANDLE_VALUE)
return;
GetSystemTime( &st );
cchMsgOut = wsprintfA(szMsgOut, "%.2u-%.2u-%.2u %.2u:%.2u:%.2u %ls (rc=%lu)\015\012",
st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond,
lpszMsg,
dwLastError
);
//
// seek to EOF
//
SetFilePointer(hFile, 0, NULL, FILE_END);
WriteFile(hFile, szMsgOut, cchMsgOut, &dwBytesWritten, NULL);
CloseHandle(hFile);
}
#endif // WIN95_LEGACY
}
// this event is signalled when the
// service should end
//
HANDLE hServerStopEvent = NULL;
extern DWORD GlobalSecurityMask;
extern BOOL g_bAudit;
//
// waitable thread pool handle.
//
HANDLE hRegisteredWait = NULL;
VOID
TeardownServer(
DWORD dwLastError
);
VOID
NTAPI
TerminationNotify(
PVOID Context,
BOOLEAN TimerOrWaitFired
);
//
// FUNCTION: ServiceStart
//
// COMMENTS:
// The service
// stops when hServerStopEvent is signalled
DWORD
ServiceStart(
HINSTANCE hInstance,
int nCmdShow
)
{
DWORD dwLastError = ERROR_SUCCESS;
BOOL fStartedKeyService = FALSE;
BOOL bListConstruct = FALSE;
LPWSTR pwszServerPrincipalName = NULL;
// Only on Win95 do we use a named event, in order to support shutting
// down the server cleanly on that platform, since Win95 does not support
// real services.
hServerStopEvent = CreateEventA(
NULL,
TRUE, // manual reset event
FALSE, // not-signalled
(FIsWinNT() ? NULL : CRYPTSVC_EVENT_STOP) // WinNT: unnamed, Win95 named
);
//
// if event already exists, terminate quietly so that only one instance
// of the service is allowed.
//
if(hServerStopEvent && GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(hServerStopEvent);
hServerStopEvent = NULL;
}
if(hServerStopEvent == NULL) {
dwLastError = GetLastError();
goto cleanup;
}
//
// report the status to the service control manager.
// (service start still pending).
//
if (!ReportStatusToSCMgr(
SERVICE_START_PENDING, // service state
NO_ERROR, // exit code
3000 // wait hint
)) {
dwLastError = GetLastError();
goto cleanup;
}
dwLastError = StartKeyService();
if(ERROR_SUCCESS != dwLastError)
{
dwLastError = GetLastError();
goto cleanup;
}
if (!_CatDBServiceInit(FALSE))
{
dwLastError = GetLastError();
goto cleanup;
}
//
// Initialize the RPC interfaces for
// Key Service, ICertProt, etc.
RPC_STATUS status;
status = RpcServerUseProtseqEpW(KEYSVC_DEFAULT_PROT_SEQ,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
KEYSVC_DEFAULT_ENDPOINT,
NULL); //Security Descriptor
if(RPC_S_DUPLICATE_ENDPOINT == status)
{
status = RPC_S_OK;
}
if (status)
{
dwLastError = status;
goto cleanup;
}
status = RpcServerUseProtseqEpW(KEYSVC_LOCAL_PROT_SEQ, //ncalrpc
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
KEYSVC_LOCAL_ENDPOINT, //keysvc
NULL); //Security Descriptor
if(RPC_S_DUPLICATE_ENDPOINT == status)
{
status = RPC_S_OK;
}
if (status)
{
dwLastError = status;
goto cleanup;
}
status = RpcServerRegisterIfEx(s_IKeySvc_v1_0_s_ifspec,
NULL,
NULL,
RPC_IF_AUTOLISTEN,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
NULL);
if (status)
{
dwLastError = status;
goto cleanup;
}
status = RpcServerRegisterIfEx(s_IKeySvcR_v1_0_s_ifspec,
NULL,
NULL,
RPC_IF_AUTOLISTEN,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
NULL);
if (status)
{
dwLastError = status;
goto cleanup;
}
status = RpcServerRegisterIfEx(s_ICertProtectFunctions_v1_0_s_ifspec,
NULL,
NULL,
RPC_IF_AUTOLISTEN,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
CryptSvcSecurityCallback);
if (status)
{
dwLastError = status;
goto cleanup;
}
status = RpcServerRegisterIfEx(s_ICatDBSvc_v1_0_s_ifspec,
NULL,
NULL,
RPC_IF_AUTOLISTEN,
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
CryptSvcSecurityCallback);
if (status)
{
dwLastError = status;
goto cleanup;
}
//
// report the status to the service control manager.
//
if (!ReportStatusToSCMgr(
SERVICE_RUNNING, // service state
NO_ERROR, // exit code
0 // wait hint
)) {
dwLastError = GetLastError();
goto cleanup;
}
// allow clients to make authenticated requests
status = RpcServerInqDefaultPrincName(RPC_C_AUTHN_GSS_NEGOTIATE, &pwszServerPrincipalName);
if (RPC_S_OK != status)
{
//BUGBUG: Shouldn't prevent service from startin just because auth failed.
// We should log an event here.
}
else
{
status = RpcServerRegisterAuthInfo
(pwszServerPrincipalName,
RPC_C_AUTHN_GSS_NEGOTIATE,
NULL, // Use default key acquisiting function
NULL // Use default creds
);
if (RPC_S_OK != status)
{
//BUGBUG: Shouldn't prevent service from startin just because auth failed.
// We should log an event here.
}
}
//
// on WinNT5, ask services.exe to notify us when the service is shutting
// down, and return this thread to the work item queue.
//
if(!RegisterWaitForSingleObject(
&hRegisteredWait,
hServerStopEvent, // wait handle
TerminationNotify, // callback fcn
NULL, // parameter
INFINITE, // timeout
WT_EXECUTELONGFUNCTION | WT_EXECUTEONLYONCE
)) {
hRegisteredWait = NULL;
dwLastError = GetLastError();
}
if (NULL != pwszServerPrincipalName) { RpcStringFreeW(&pwszServerPrincipalName); }
return dwLastError;
cleanup:
TeardownServer( dwLastError );
if (NULL != pwszServerPrincipalName) { RpcStringFreeW(&pwszServerPrincipalName); }
return dwLastError;
}
VOID
NTAPI
TerminationNotify(
PVOID Context,
BOOLEAN TimerOrWaitFired
)
/*++
Routine Description:
This function gets called by a services worker thread when the
termination event gets signaled.
Arguments:
Return Value:
--*/
{
//
// per JSchwart:
// safe to unregister during callback.
//
if( hRegisteredWait ) {
UnregisterWaitEx( hRegisteredWait, NULL );
hRegisteredWait = NULL;
}
TeardownServer( ERROR_SUCCESS );
}
VOID
TeardownServer(
DWORD dwLastError
)
{
RPC_STATUS rpcStatus;
DWORD dwErrToReport = dwLastError;
//
// Unregister RPC Interfaces
rpcStatus = RpcServerUnregisterIf(s_IKeySvc_v1_0_s_ifspec, 0, 0);
if ((rpcStatus != RPC_S_OK) && (dwErrToReport == ERROR_SUCCESS))
{
dwErrToReport = rpcStatus;
}
rpcStatus = RpcServerUnregisterIf(s_IKeySvcR_v1_0_s_ifspec, 0, 0);
if ((rpcStatus != RPC_S_OK) && (dwErrToReport == ERROR_SUCCESS))
{
dwErrToReport = rpcStatus;
}
rpcStatus = RpcServerUnregisterIf(s_ICertProtectFunctions_v1_0_s_ifspec, 0, 0);
if ((rpcStatus != RPC_S_OK) && (dwErrToReport == ERROR_SUCCESS))
{
dwErrToReport = rpcStatus;
}
rpcStatus = RpcServerUnregisterIf(s_ICatDBSvc_v1_0_s_ifspec, 0, 0);
if ((rpcStatus != RPC_S_OK) && (dwErrToReport == ERROR_SUCCESS))
{
dwErrToReport = rpcStatus;
}
//
// stop backup key server
// Note: this function knows internally whether the backup key server
// really started or not.
//
StopKeyService();
_CatDBServiceInit(TRUE);
if(hServerStopEvent) {
SetEvent(hServerStopEvent); // make event signalled to release anyone waiting for termination
CloseHandle(hServerStopEvent);
hServerStopEvent = NULL;
}
ReportStatusToSCMgr(
SERVICE_STOPPED,
dwErrToReport,
0
);
}
// FUNCTION: ServiceStop
//
// PURPOSE: Stops the service
//
// COMMENTS:
// If a ServiceStop procedure is going to
// take longer than 3 seconds to execute,
// it should spawn a thread to execute the
// stop code, and return. Otherwise, the
// ServiceControlManager will believe that
// the service has stopped responding.
//
VOID ServiceStop()
{
if(hServerStopEvent)
{
PulseEvent(hServerStopEvent); // signal waiting threads and reset to non-signalled
}
}
extern "C"
{
/*********************************************************************/
/* MIDL allocate and free */
/*********************************************************************/
void __RPC_FAR * __RPC_API midl_user_allocate(size_t len)
{
return(LocalAlloc(LMEM_ZEROINIT, len));
}
void __RPC_API midl_user_free(void __RPC_FAR * ptr)
{
//
// sfield: zero memory before freeing it.
// do this because RPC allocates alot on our behalf, and we want to
// be as sanitary as possible with respect to not letting anything
// sensitive go to pagefile.
//
ZeroMemory( ptr, LocalSize( ptr ) );
LocalFree(ptr);
}
void __RPC_FAR * __RPC_API midl_user_reallocate(void __RPC_FAR * ptr, size_t len)
{
return(LocalReAlloc(ptr, len, LMEM_MOVEABLE | LMEM_ZEROINIT));
}
}