// 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 #include #include #include #include // SVCS_ #include #include #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)); } }