//+-------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1996-1996 // // File: rpc.cpp // // Contents: Cert Server RPC // // History: 03-Sep-96 larrys created // //--------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include "certrpc.h" #include "certcli.h" #include "certlib.h" #include "cs.h" #include "csext.h" #include "csprop.h" #include "ese.h" #include "dbtable.h" CRITICAL_SECTION g_RPCCriticalSection; CERT_CONTEXT const *pPrevCertContext = NULL; RPC_BINDING_VECTOR *pvBindings = NULL; #define USE_NP 1 #ifdef USE_NP char *pszProtSeq = "ncacn_np"; #else char *pszProtSeq = "ncacn_ip_tcp"; #endif SERVICE_STATUS g_ssStatus; SERVICE_STATUS_HANDLE g_sshStatusHandle; HANDLE g_hServiceDoneEvent = NULL; VOID ServiceControlHandler(DWORD dwCtrlCode); BOOL ReportStatusToSCMgr( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint); typedef struct _ATTRIBVALUETABLE { WCHAR *pwAttrib; DWORD cbAttrib; WCHAR *pwValue; DWORD cbValue; } ATTRIBVALUETABLE; DWORD RPCInit(VOID) { #ifdef USE_NP char * pszEndpoint = "\\pipe\\cert"; #endif SECURITY_DESCRIPTOR *psd; DWORD err; if ((psd = (SECURITY_DESCRIPTOR *) LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, SECURITY_DESCRIPTOR_MIN_LENGTH)) == 0) { DBGERRORPRINTLINE("LocalAlloc", ERROR_NOT_ENOUGH_MEMORY); goto fail; } #ifdef USE_NP err = RpcServerUseProtseqEpA( (unsigned char *) pszProtSeq, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, (unsigned char *) pszEndpoint, NULL); #else err = RpcServerUseProtseqA( (unsigned char *) pszProtSeq, RPC_C_PROTSEQ_MAX_REQS_DEFAULT, NULL); #endif if (err != RPC_S_OK) { DBGERRORPRINTLINE("RpcServerUseProtseqA", err); goto fail; } err = RpcServerInqBindings(&pvBindings); if (err != RPC_S_OK) { DBGERRORPRINTLINE("RpcServerInqBindings", err); goto fail; } err = RpcServerRegisterIf(s_ICertPassage_v0_0_s_ifspec, NULL, NULL); if (err != RPC_S_OK) { DBGERRORPRINTLINE("RpcServerRegisterIf", err); goto fail; } err = RpcEpRegister(s_ICertPassage_v0_0_s_ifspec, pvBindings, NULL, NULL); if (err != RPC_S_OK) { DBGERRORPRINTLINE("RpcEpRegister", err); goto fail; } // Listen, but don't wait... err = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE); if (err != RPC_S_OK) { DBGERRORPRINTLINE("RpcServerListen", err); goto fail; } fail: return(err); } DWORD IsRpcClientAllowed( handle_t h, WCHAR *pwszUserName, DWORD cbUserName, BOOL *pbAllowed) { DWORD entriesread; DWORD totalentries; DWORD resumehandle = 0; LOCALGROUP_MEMBERS_INFO_1 *plgmi = NULL; LOCALGROUP_MEMBERS_INFO_1 *plgmiT; DWORD cnt; DWORD Totalcnt; DWORD err; *pbAllowed = FALSE; do { err = RpcImpersonateClient((RPC_BINDING_HANDLE) h); if (RPC_S_OK != err) { continue; // error exit } if (!GetUserName(pwszUserName, &cbUserName)) { err = GetLastError(); } RpcRevertToSelf(); if (ERROR_SUCCESS != err) { continue; // error exit } Totalcnt = 0; do { err = NetLocalGroupGetMembers( NULL, g_wszRequestGroupName, 1, (LPBYTE *) &plgmi, 0xffff, &entriesread, &totalentries, &resumehandle); if (NERR_Success != err) { break; } plgmiT = plgmi; for (cnt = 0; cnt < entriesread; cnt++) { if (lstrcmpi(plgmiT->lgrmi1_name, pwszUserName) == 0) { CONSOLEPRINT2(( DBG_SS_CERTSRV, "Found user %ws in group %ws\n", pwszUserName, g_wszRequestGroupName)); *pbAllowed = TRUE; break; } Totalcnt++; plgmiT++; } NetApiBufferFree(plgmi); } while (!*pbAllowed && Totalcnt < totalentries); } while (FALSE); return(err); } BOOL IsValidAttributeChar(TCHAR chChar) { BOOL fRetVal=TRUE; switch(chChar) { case TEXT('\r'): case TEXT('\n'): case TEXT(' '): case TEXT('-'): fRetVal = FALSE; break; } return fRetVal; } DWORD ParseAttributes( IN ICertDBRow *prow, IN WCHAR const *pwszAttributes) { HRESULT hr = S_OK; WCHAR *pwb = NULL; WCHAR *pwbEnd; WCHAR *pwAttrib = NULL; WCHAR *pwValue = NULL; WCHAR *pwszTemp = NULL; WCHAR const *pwszPropName; WCHAR *pwbtmp; WCHAR *pwbtmp2; WCHAR *pwbtmp3; WCHAR *pwbtmp4; DWORD cwc; DWORD i; DWORD cwcAttributes; DWORD cbProp; if (NULL == pwszAttributes) { goto error; // silently ignore empty string } cwcAttributes = wcslen(pwszAttributes); // Allocate and copy bytes to null terminate pwb = (WCHAR *) LocalAlloc(LMEM_FIXED, (cwcAttributes + 1) * sizeof(WCHAR)); if (NULL == pwb) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } wcscpy(pwb, pwszAttributes); pwbEnd = &pwb[cwcAttributes]; pwbtmp = pwb; while(TRUE) { pwbtmp2 = pwbtmp; pwbtmp = wcschr(pwbtmp, TEXT(':')); if (NULL == pwbtmp) { break; } // If there's a newline before the next colon, start over. pwbtmp3 = wcschr(pwbtmp2, TEXT('\n')); if (NULL != pwbtmp3 && pwbtmp3 < pwbtmp) { pwbtmp = pwbtmp3 + 1; continue; } // Find beginning of Attrib string while(iswspace(*pwbtmp2)) { pwbtmp2++; } // Count size of Attrib string cwc = pwbtmp - pwbtmp2; pwbtmp3 = pwbtmp; // move before : pwbtmp3--; while (iswspace(*pwbtmp3) && pwbtmp3 > pwbtmp2) { cwc--; pwbtmp3--; } pwAttrib = (WCHAR *) LocalAlloc(LMEM_FIXED, (cwc + 1) * sizeof(WCHAR)); if (NULL == pwAttrib) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } pwbtmp3 = pwAttrib; for (i = 0; i < cwc; i++, pwbtmp2++) { if (IsValidAttributeChar(*pwbtmp2)) { *pwbtmp3++ = *pwbtmp2; } } *pwbtmp3 = L'\0'; // Skip over the colon pwbtmp2 = pwbtmp + 1; // Find beginning of Value string while(L'\n' != *pwbtmp2 && iswspace(*pwbtmp2)) { pwbtmp2++; } pwbtmp3 = pwbtmp2; // find end of Value string pwbtmp4 = wcschr(pwbtmp3, TEXT('\n')); if (NULL == pwbtmp4) { // Check for case when last Value isn't newline terminated if (pwbtmp3 >= pwbEnd) { break; } pwbtmp4 = pwbEnd; } cwc = pwbtmp4 - pwbtmp3; pwbtmp3 = pwbtmp4; // move before '\n' or '\0' pwbtmp3--; while (iswspace(*pwbtmp3) && pwbtmp3 > pwbtmp2) { cwc--; pwbtmp3--; } pwValue = (WCHAR *) LocalAlloc(LMEM_FIXED, (cwc + 1) * sizeof(WCHAR)); if (NULL == pwValue) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } memcpy(pwValue, pwbtmp2, cwc * sizeof(WCHAR)); pwValue[cwc] = L'\0'; // if the attribute name and value are both non-empty ... if (L'\0' != *pwValue && L'\0' != *pwAttrib) { DWORD dwTable = PROPTABLE_REQUEST; // See if the attribute name can be mapped to a standard property. pwszPropName = DBMapAttributeName(pwAttrib); if (NULL == pwszPropName || NULL == g_pwszNameSeparator) { if (NULL == pwszPropName) { dwTable = PROPTABLE_ATTRIBUTES; } pwszPropName = pwAttrib; } else { cbProp = 0; hr = prow->GetProperty( pwszPropName, PROPTYPE_STRING | PROPCALLER_SERVER | PROPTABLE_REQUEST, &cbProp, NULL); if (CERTSRV_E_PROPERTY_EMPTY != hr) { _JumpIfError(hr, error, "GetProperty"); pwszTemp = (WCHAR *) LocalAlloc( LMEM_FIXED, (cbProp + 2 + wcslen(pwValue)) * sizeof(WCHAR)); if (NULL == pwszTemp) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } hr = prow->GetProperty( pwszPropName, PROPTYPE_STRING | PROPCALLER_SERVER | PROPTABLE_REQUEST, &cbProp, (BYTE *) pwszTemp); _JumpIfError(hr, error, "GetProperty"); wcscat(pwszTemp, g_pwszNameSeparator); wcscat(pwszTemp, L" "); wcscat(pwszTemp, pwValue); LocalFree(pwValue); pwValue = pwszTemp; pwszTemp = NULL; } } hr = prow->SetProperty( pwszPropName, dwTable | PROPTYPE_STRING | PROPCALLER_SERVER, MAXDWORD, (BYTE const *) pwValue); _JumpIfError(hr, error, "SetProperty"); if (NULL != pwszTemp) { LocalFree(pwszTemp); pwszTemp = NULL; } } LocalFree(pwValue); pwValue = NULL; LocalFree(pwAttrib); pwAttrib = NULL; if (pwbtmp4 >= pwbEnd) { break; } pwbtmp = pwbtmp4 + 1; } error: if (NULL != pwAttrib) { LocalFree(pwAttrib); } if (NULL != pwValue) { LocalFree(pwValue); } if (NULL != pwb) { LocalFree(pwb); } if (NULL != pwszTemp) { LocalFree(pwszTemp); } return(hr); } /* server prototype */ DWORD s_CertServerRequest( /* [in] */ handle_t h, /* [in] */ DWORD dwFlags, /* [unique][string][in] */ const wchar_t __RPC_FAR *pwszAuthority, /* [ref][out][in] */ DWORD __RPC_FAR *pdwRequestId, /* [out] */ DWORD __RPC_FAR *pdwDisposition, /* [unique][string][in] */ const wchar_t __RPC_FAR *pwszAttributes, /* [ref][in] */ CERTTRANSBLOB const __RPC_FAR *pctbRequest, /* [ref][out] */ CERTTRANSBLOB __RPC_FAR *pctbCertChain, /* [ref][out] */ CERTTRANSBLOB __RPC_FAR *pctbEncodedCert, /* [ref][out] */ CERTTRANSBLOB __RPC_FAR *pctbDispositionMessage) { HRESULT hr = S_OK; BOOL bAllowed = TRUE; WCHAR UserName[MAX_PATH]; DWORD OpRequest; EnterCriticalSection(&g_RPCCriticalSection); // Set up default output parameters: OpRequest = CR_IN_RETRIEVE; if (NULL != pctbRequest->pb) { *pdwRequestId = 0; OpRequest = CR_IN_NEW; } *pdwDisposition = CR_DISP_ERROR; if (0 != lstrcmpi(pwszAuthority, g_wszAuthority)) { hr = E_INVALIDARG; _JumpError(hr, error, "Bad Authority name"); } //BUGBUG: do something with the error: pass it in the policy module flags? hr = IsRpcClientAllowed(h, UserName, sizeof(UserName), &bAllowed); hr = CoreProcessRequest( OpRequest | (dwFlags & CR_IN_FORMATMASK), UserName, pctbRequest->cb, // cbRequest pctbRequest->pb, // pbRequest pwszAttributes, (WCHAR **) &pctbDispositionMessage->pb, pdwRequestId, pdwDisposition, &pctbCertChain->pb, // Allocates returned memory &pctbCertChain->cb, &pctbEncodedCert->pb,// Allocates returned memory &pctbEncodedCert->cb); _JumpIfError(hr, error, "CoreProcessRequest"); error: pctbDispositionMessage->cb = 0; if (NULL != pctbDispositionMessage->pb) { pctbDispositionMessage->cb = (wcslen((WCHAR *) pctbDispositionMessage->pb) + 1) * sizeof(WCHAR); } LeaveCriticalSection(&g_RPCCriticalSection); PKCSCRLTimerProc(NULL, 0, 0, 0); return(hr); } typedef struct _CRLELEMENT { LIST_ENTRY Next; CRYPT_INTEGER_BLOB SerialNumber; FILETIME RevocationDate; } CRLELEMENT; VOID FreeCRLArray( IN DWORD cCRL, IN OUT CRL_ENTRY *aCRL) { if (NULL != aCRL) { CRL_ENTRY *pCRL = &aCRL[cCRL]; while (--pCRL >= aCRL) { if (NULL != pCRL->SerialNumber.pbData) { LocalFree(pCRL->SerialNumber.pbData); } } LocalFree(aCRL); } } // Convert linked list of CRL_ENTRYs to an array. // If the output array pointer is NULL, just free the list. HRESULT ConvertOrFreeCRLList( IN OUT LIST_ENTRY *pleCRL, IN DWORD cCRL, OPTIONAL OUT CRL_ENTRY **paCRL) { HRESULT hr; CRL_ENTRY *aCRL = NULL; CRL_ENTRY *pCRL; CRL_ENTRY *pCRLT; DWORD i; LIST_ENTRY *pleNext; CRLELEMENT *pElement; if (NULL != paCRL) { aCRL = (CRL_ENTRY *) LocalAlloc(LMEM_ZEROINIT, sizeof(aCRL[0]) * cCRL); if (NULL == aCRL) { hr = E_OUTOFMEMORY; _JumpError(hr, error, "LocalAlloc"); } } pCRL = aCRL; pleNext = pleCRL->Flink; for (i = 0; pleNext != pleCRL; i++) { pElement = CONTAINING_RECORD(pleNext, CRLELEMENT, Next); pleNext = pleNext->Flink; if (NULL != pElement) { if (NULL != pCRL) { pCRL->SerialNumber.pbData = pElement->SerialNumber.pbData; pCRL->SerialNumber.cbData = pElement->SerialNumber.cbData; pCRL->RevocationDate = pElement->RevocationDate; pCRL++; } else { if (NULL != pElement->SerialNumber.pbData) { LocalFree(pElement->SerialNumber.pbData); } } RemoveEntryList(&pElement->Next); LocalFree(pElement); } } CSASSERT(i == cCRL); if (NULL != paCRL) { *paCRL = aCRL; aCRL = NULL; } hr = S_OK; error: if (NULL != aCRL) { FreeCRLArray(cCRL, aCRL); } return(hr); } HRESULT AddCRLElement( IN OUT LIST_ENTRY *pleCRL, WCHAR const *pwszSerialNumber, FILETIME const *pftRevokedEffectiveWhen) { HRESULT hr = S_OK; CRLELEMENT *pElement = NULL; pElement = (CRLELEMENT *) LocalAlloc(LMEM_ZEROINIT, sizeof(CRLELEMENT)); if (NULL == pElement) { hr = ERROR_NOT_ENOUGH_MEMORY; goto error; } hr = myWszToMultiByteInteger( pwszSerialNumber, &pElement->SerialNumber.cbData, &pElement->SerialNumber.pbData); _JumpIfError(hr, error, "myWszToMultiByteInteger"); pElement->RevocationDate = *pftRevokedEffectiveWhen; InsertTailList(pleCRL, &pElement->Next); pElement = NULL; error: if (NULL != pElement) { LocalFree(pElement); } return(hr); } DWORD g_aColCRL[] = { #define ICOL_REQUESTID 0 DTI_CERTIFICATETABLE | DTC_REQUESTID, #define ICOL_EFFECTIVEWHEN 1 DTI_REQUESTTABLE | DTR_REQUESTREVOKEDEFFECTIVEWHEN, #define ICOL_SERIAL 2 DTI_CERTIFICATETABLE | DTC_CERTIFICATESERIALNUMBER, #define ICOL_NOTAFTER 3 DTI_CERTIFICATETABLE | DTC_CERTIFICATENOTAFTERDATE, }; #define CCOL_CRLVIEW (sizeof(g_aColCRL)/sizeof(g_aColCRL[0])) HRESULT BuildCRLList( OUT LIST_ENTRY *pleCRL, IN DWORD *pcCRL, IN FILETIME ThisUpdate) { HRESULT hr; CERTVIEWRESTRICTION cvr; IEnumCERTDBRESULTROW *pView = NULL; DWORD celtFetched; DWORD Disposition; DWORD i; BOOL fCoInitialized = FALSE; DWORD cCRL = 0; CERTDBRESULTROW Result; BOOL fResultActive = FALSE; Disposition = CR_DISP_REVOKED; cvr.ColumnIndex = DTI_REQUESTTABLE | DTR_REQUESTDISPOSITION; cvr.dwSeek = CVR_SEEK_EQ; cvr.pbValue = (BYTE *) &Disposition; cvr.cbValue = sizeof(Disposition); hr = CoInitialize(NULL); if (S_OK != hr && S_FALSE != hr) { _JumpError(hr, error, "CoInitialize"); } fCoInitialized = TRUE; hr = g_pCertDB->OpenView( 1, &cvr, CCOL_CRLVIEW, g_aColCRL, &pView); _JumpIfError(hr, error, "OpenView"); while (TRUE) { hr = pView->Next(1, &Result, &celtFetched); if (S_FALSE == hr) { break; } _JumpIfError(hr, error, "Next"); fResultActive = TRUE; CSASSERT(1 == celtFetched); CSASSERT(CCOL_CRLVIEW == Result.ccol); CSASSERT(PROPTYPE_LONG == Result.acol[ICOL_REQUESTID].Type); CSASSERT(PROPTYPE_DATE == Result.acol[ICOL_EFFECTIVEWHEN].Type); CSASSERT(PROPTYPE_STRING == Result.acol[ICOL_SERIAL].Type); CSASSERT(PROPTYPE_DATE == Result.acol[ICOL_NOTAFTER].Type); CSASSERT(NULL != Result.acol[ICOL_REQUESTID].pbValue); CSASSERT(NULL != Result.acol[ICOL_SERIAL].pbValue); CSASSERT(NULL != Result.acol[ICOL_NOTAFTER].pbValue); CSASSERT(sizeof(DWORD) == Result.acol[ICOL_REQUESTID].cbValue); CSASSERT( sizeof(FILETIME) == Result.acol[ICOL_EFFECTIVEWHEN].cbValue || NULL == Result.acol[ICOL_EFFECTIVEWHEN].pbValue); CSASSERT(0 < Result.acol[ICOL_SERIAL].cbValue); CSASSERT(sizeof(FILETIME) == Result.acol[ICOL_NOTAFTER].cbValue); if (NULL != Result.acol[ICOL_NOTAFTER].pbValue && NULL != Result.acol[ICOL_EFFECTIVEWHEN].pbValue && NULL != Result.acol[ICOL_SERIAL].pbValue && CompareFileTime( (FILETIME *) Result.acol[ICOL_NOTAFTER].pbValue, &ThisUpdate) > 0 && CompareFileTime( (FILETIME *) Result.acol[ICOL_EFFECTIVEWHEN].pbValue, &ThisUpdate) < 0) { hr = AddCRLElement( pleCRL, (WCHAR const *) Result.acol[ICOL_SERIAL].pbValue, (FILETIME const *) Result.acol[ICOL_EFFECTIVEWHEN].pbValue); _JumpIfError(hr, error, "AddCRLElement"); CONSOLEPRINT1(( DBG_SS_CERTSRV, "Cert is revoked: %ws\n", Result.acol[ICOL_SERIAL].pbValue)); cCRL++; } pView->ReleaseResultRow(1, &Result); fResultActive = FALSE; } *pcCRL = cCRL; hr = S_OK; error: if (NULL != pView) { if (fResultActive) { pView->ReleaseResultRow(1, &Result); } pView->Release(); } if (fCoInitialized) { CoUninitialize(); } return(hr); } HRESULT BuildCRLArray( IN FILETIME ThisUpdate, OUT DWORD *pcCRL, OUT CRL_ENTRY **paCRL) { HRESULT hr; LIST_ENTRY leCRL; *pcCRL = 0; *paCRL = NULL; InitializeListHead(&leCRL); hr = BuildCRLList(&leCRL, pcCRL, ThisUpdate); _JumpIfError(hr, error, "BuildCRLList"); hr = ConvertOrFreeCRLList(&leCRL, *pcCRL, paCRL); _JumpIfError(hr, error, "ConvertOrFreeCRLList"); error: ConvertOrFreeCRLList(&leCRL, 0, NULL); return(hr); } void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t cb) { return(LocalAlloc(LMEM_ZEROINIT, cb)); } void __RPC_USER MIDL_user_free( void __RPC_FAR * pb) { LocalFree(pb); } VOID ServiceMain( IN DWORD dwArgc, IN LPWSTR *lpszArgv) { HRESULT hr = S_OK; g_sshStatusHandle = RegisterServiceCtrlHandler( g_wszCertSrvServiceName, ServiceControlHandler); if (NULL == g_sshStatusHandle) { hr = GetLastError(); _JumpError(hr, error, "RegisterServiceCtrlHandler"); } g_ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; g_ssStatus.dwServiceSpecificExitCode = 0; ReportStatusToSCMgr(SERVICE_START_PENDING, hr, 1, 3000); g_hServiceDoneEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (NULL == g_hServiceDoneEvent) { hr = GetLastError(); _JumpError(hr, error, "CreateEvent"); } if (0 != g_dwDelay2) { DBGPRINT(( DBG_SS_CERTSRV, "ServiceMain: sleeping %u seconds\n", g_dwDelay2)); Sleep(1000 * g_dwDelay2); } hr = certsrvStartServerThread((VOID *) 0); _JumpIfError(hr, error, "certsrvStartServer"); ReportStatusToSCMgr(SERVICE_RUNNING, hr, 0, 0); WaitForSingleObject(g_hServiceDoneEvent, INFINITE); DBGPRINT((DBG_SS_CERTSRV, "ServiceMain: Service Done\n")); error: ReportStatusToSCMgr(SERVICE_STOPPED, hr, 0, 0); if (NULL != g_hServiceDoneEvent) { CloseHandle(g_hServiceDoneEvent); } DBGPRINT((DBG_SS_CERTSRV, "ServiceMain: Exit: %x\n", hr)); } VOID ServiceControlHandler( IN DWORD dwCtrlCode) { DWORD dwState = SERVICE_RUNNING; switch (dwCtrlCode) { case SERVICE_CONTROL_PAUSE: if (SERVICE_RUNNING == g_ssStatus.dwCurrentState) { dwState = SERVICE_PAUSED; } break; case SERVICE_CONTROL_CONTINUE: if (SERVICE_PAUSED == g_ssStatus.dwCurrentState) { dwState = SERVICE_RUNNING; } break; case SERVICE_CONTROL_STOP: ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 1, 3000); certsrvStopServer(FALSE); SetEvent(g_hServiceDoneEvent); return; case SERVICE_CONTROL_INTERROGATE: break; } ReportStatusToSCMgr(dwState, NO_ERROR, 0, 0); } BOOL ReportStatusToSCMgr( IN DWORD dwCurrentState, IN DWORD dwWin32ExitCode, IN DWORD dwCheckPoint, IN DWORD dwWaitHint) { BOOL fResult; HRESULT hr; g_ssStatus.dwControlsAccepted = SERVICE_START_PENDING == dwCurrentState? 0 : SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; g_ssStatus.dwCurrentState = dwCurrentState; g_ssStatus.dwWin32ExitCode = dwWin32ExitCode; g_ssStatus.dwCheckPoint = dwCheckPoint; g_ssStatus.dwWaitHint = dwWaitHint; fResult = SetServiceStatus(g_sshStatusHandle, &g_ssStatus); if (!fResult) { hr = GetLastError(); _JumpError(hr, error, "SetServiceStatus"); } DBGPRINT(( DBG_SS_CERTSRV, "ReportStatusToSCMgr(state=%x, hr=%x, ckpt=%x, wait=%x)\n", dwCurrentState, dwWin32ExitCode, dwCheckPoint, dwWaitHint)); error: return(fResult); }