Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

728 lines
20 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows NT Security
  4. // Copyright (C) Microsoft Corporation, 1997 - 1999
  5. //
  6. // File: api.cpp
  7. //
  8. // Contents: Certificate Chaining Infrastructure
  9. //
  10. // History: 28-Jan-98 kirtd Created
  11. //
  12. //----------------------------------------------------------------------------
  13. #include <global.hxx>
  14. #include <dbgdef.h>
  15. //
  16. // Globals
  17. //
  18. HMODULE g_hCryptnet = NULL;
  19. CRITICAL_SECTION g_CryptnetLock;
  20. CDefaultChainEngineMgr DefaultChainEngineMgr;
  21. CRITICAL_SECTION g_RoamingLogoffNotificationLock;
  22. BOOL g_fRoamingLogoffNotificationInitialized = FALSE;
  23. HMODULE g_hChainInst;
  24. VOID WINAPI
  25. CreateRoamingLogoffNotificationEvent();
  26. VOID WINAPI
  27. InitializeRoamingLogoffNotification();
  28. VOID WINAPI
  29. UninitializeRoamingLogoffNotification();
  30. //+---------------------------------------------------------------------------
  31. //
  32. // Function: ChainDllMain
  33. //
  34. // Synopsis: Chaining infrastructure initialization
  35. //
  36. //----------------------------------------------------------------------------
  37. BOOL WINAPI
  38. ChainDllMain (
  39. IN HMODULE hModule,
  40. IN ULONG ulReason,
  41. IN LPVOID pvReserved
  42. )
  43. {
  44. BOOL fResult = TRUE;
  45. switch ( ulReason )
  46. {
  47. case DLL_PROCESS_ATTACH:
  48. g_hChainInst = hModule;
  49. fResult = Pki_InitializeCriticalSection( &g_CryptnetLock );
  50. if (fResult)
  51. {
  52. fResult = Pki_InitializeCriticalSection(
  53. &g_RoamingLogoffNotificationLock );
  54. if (fResult)
  55. {
  56. fResult = DefaultChainEngineMgr.Initialize();
  57. if (fResult)
  58. {
  59. CreateRoamingLogoffNotificationEvent();
  60. }
  61. else
  62. {
  63. DeleteCriticalSection( &g_RoamingLogoffNotificationLock );
  64. }
  65. }
  66. if (!fResult)
  67. {
  68. DeleteCriticalSection( &g_CryptnetLock );
  69. }
  70. }
  71. break;
  72. case DLL_PROCESS_DETACH:
  73. UninitializeRoamingLogoffNotification();
  74. DefaultChainEngineMgr.Uninitialize();
  75. if ( g_hCryptnet != NULL )
  76. {
  77. FreeLibrary( g_hCryptnet );
  78. }
  79. DeleteCriticalSection( &g_CryptnetLock );
  80. DeleteCriticalSection( &g_RoamingLogoffNotificationLock );
  81. break;
  82. }
  83. return( fResult );
  84. }
  85. //+---------------------------------------------------------------------------
  86. //
  87. // Function: InternalCertCreateCertificateChainEngine
  88. //
  89. // Synopsis: create a chain engine handle
  90. //
  91. //----------------------------------------------------------------------------
  92. BOOL WINAPI
  93. InternalCertCreateCertificateChainEngine (
  94. IN PCERT_CHAIN_ENGINE_CONFIG pConfig,
  95. IN BOOL fDefaultEngine,
  96. OUT HCERTCHAINENGINE* phChainEngine
  97. )
  98. {
  99. BOOL fResult = TRUE;
  100. PCCERTCHAINENGINE pChainEngine = NULL;
  101. CERT_CHAIN_ENGINE_CONFIG Config;
  102. if ( pConfig->cbSize != sizeof( CERT_CHAIN_ENGINE_CONFIG ) )
  103. {
  104. SetLastError( (DWORD) E_INVALIDARG );
  105. return( FALSE );
  106. }
  107. Config = *pConfig;
  108. if ( Config.MaximumCachedCertificates == 0 )
  109. {
  110. Config.MaximumCachedCertificates = DEFAULT_MAX_INDEX_ENTRIES;
  111. }
  112. pChainEngine = new CCertChainEngine( &Config, fDefaultEngine, fResult );
  113. if ( pChainEngine == NULL )
  114. {
  115. SetLastError( (DWORD) E_OUTOFMEMORY );
  116. fResult = FALSE;
  117. }
  118. if ( fResult == TRUE )
  119. {
  120. *phChainEngine = (HCERTCHAINENGINE)pChainEngine;
  121. }
  122. else
  123. {
  124. delete pChainEngine;
  125. }
  126. return( fResult );
  127. }
  128. //+---------------------------------------------------------------------------
  129. //
  130. // Function: CertCreateCertificateChainEngine
  131. //
  132. // Synopsis: create a certificate chain engine
  133. //
  134. //----------------------------------------------------------------------------
  135. BOOL WINAPI
  136. CertCreateCertificateChainEngine (
  137. IN PCERT_CHAIN_ENGINE_CONFIG pConfig,
  138. OUT HCERTCHAINENGINE* phChainEngine
  139. )
  140. {
  141. return( InternalCertCreateCertificateChainEngine(
  142. pConfig,
  143. FALSE,
  144. phChainEngine
  145. ) );
  146. }
  147. //+---------------------------------------------------------------------------
  148. //
  149. // Function: CertFreeCertificateChainEngine
  150. //
  151. // Synopsis: free the chain engine handle
  152. //
  153. //----------------------------------------------------------------------------
  154. VOID WINAPI
  155. CertFreeCertificateChainEngine (
  156. IN HCERTCHAINENGINE hChainEngine
  157. )
  158. {
  159. if ( ( hChainEngine == HCCE_CURRENT_USER ) ||
  160. ( hChainEngine == HCCE_LOCAL_MACHINE ) )
  161. {
  162. DefaultChainEngineMgr.FlushDefaultEngine( hChainEngine );
  163. return;
  164. }
  165. ( (PCCERTCHAINENGINE)hChainEngine )->Release();
  166. }
  167. //+---------------------------------------------------------------------------
  168. //
  169. // Function: CertResyncCertificateChainEngine
  170. //
  171. // Synopsis: resync the chain engine
  172. //
  173. //----------------------------------------------------------------------------
  174. BOOL WINAPI
  175. CertResyncCertificateChainEngine (
  176. IN HCERTCHAINENGINE hChainEngine
  177. )
  178. {
  179. BOOL fResult;
  180. PCCERTCHAINENGINE pChainEngine = (PCCERTCHAINENGINE)hChainEngine;
  181. PCCHAINCALLCONTEXT pCallContext = NULL;
  182. if ( ( hChainEngine == HCCE_LOCAL_MACHINE ) ||
  183. ( hChainEngine == HCCE_CURRENT_USER ) )
  184. {
  185. if ( DefaultChainEngineMgr.GetDefaultEngine(
  186. hChainEngine,
  187. (HCERTCHAINENGINE *)&pChainEngine
  188. ) == FALSE )
  189. {
  190. return( FALSE );
  191. }
  192. }
  193. else
  194. {
  195. pChainEngine->AddRef();
  196. }
  197. fResult = CallContextCreateCallObject(
  198. pChainEngine,
  199. NULL, // pRequestedTime
  200. NULL, // pChainPara
  201. CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL,
  202. &pCallContext
  203. );
  204. if (fResult)
  205. {
  206. pChainEngine->LockEngine();
  207. fResult = pChainEngine->Resync( pCallContext, TRUE );
  208. CertPerfIncrementChainRequestedEngineResyncCount();
  209. pChainEngine->UnlockEngine();
  210. CallContextFreeCallObject(pCallContext);
  211. }
  212. pChainEngine->Release();
  213. return( fResult );
  214. }
  215. //+---------------------------------------------------------------------------
  216. //
  217. // Function: CertGetCertificateChain
  218. //
  219. // Synopsis: get the certificate chain for the given end certificate
  220. //
  221. //----------------------------------------------------------------------------
  222. BOOL WINAPI
  223. CertGetCertificateChain (
  224. IN OPTIONAL HCERTCHAINENGINE hChainEngine,
  225. IN PCCERT_CONTEXT pCertContext,
  226. IN OPTIONAL LPFILETIME pTime,
  227. IN OPTIONAL HCERTSTORE hAdditionalStore,
  228. IN OPTIONAL PCERT_CHAIN_PARA pChainPara,
  229. IN DWORD dwFlags,
  230. IN LPVOID pvReserved,
  231. OUT PCCERT_CHAIN_CONTEXT* ppChainContext
  232. )
  233. {
  234. BOOL fResult;
  235. PCCERTCHAINENGINE pChainEngine = (PCCERTCHAINENGINE)hChainEngine;
  236. InitializeRoamingLogoffNotification();
  237. if ( ( pChainPara == NULL ) || ( pvReserved != NULL ) )
  238. {
  239. SetLastError( (DWORD) E_INVALIDARG );
  240. return( FALSE );
  241. }
  242. if ( ( hChainEngine == HCCE_LOCAL_MACHINE ) ||
  243. ( hChainEngine == HCCE_CURRENT_USER ) )
  244. {
  245. if ( DefaultChainEngineMgr.GetDefaultEngine(
  246. hChainEngine,
  247. (HCERTCHAINENGINE *)&pChainEngine
  248. ) == FALSE )
  249. {
  250. return( FALSE );
  251. }
  252. }
  253. else
  254. {
  255. pChainEngine->AddRef();
  256. }
  257. fResult = pChainEngine->GetChainContext(
  258. pCertContext,
  259. pTime,
  260. hAdditionalStore,
  261. pChainPara,
  262. dwFlags,
  263. pvReserved,
  264. ppChainContext
  265. );
  266. pChainEngine->Release();
  267. return( fResult );
  268. }
  269. //+---------------------------------------------------------------------------
  270. //
  271. // Function: CertFreeCertificateChain
  272. //
  273. // Synopsis: free a certificate chain context
  274. //
  275. //----------------------------------------------------------------------------
  276. VOID WINAPI
  277. CertFreeCertificateChain (
  278. IN PCCERT_CHAIN_CONTEXT pChainContext
  279. )
  280. {
  281. ChainReleaseInternalChainContext(
  282. (PINTERNAL_CERT_CHAIN_CONTEXT)pChainContext
  283. );
  284. }
  285. //+---------------------------------------------------------------------------
  286. //
  287. // Function: CertDuplicateCertificateChain
  288. //
  289. // Synopsis: duplicate (add a reference to) a certificate chain
  290. //
  291. //----------------------------------------------------------------------------
  292. PCCERT_CHAIN_CONTEXT WINAPI
  293. CertDuplicateCertificateChain (
  294. IN PCCERT_CHAIN_CONTEXT pChainContext
  295. )
  296. {
  297. ChainAddRefInternalChainContext(
  298. (PINTERNAL_CERT_CHAIN_CONTEXT)pChainContext
  299. );
  300. return( pChainContext );
  301. }
  302. //+---------------------------------------------------------------------------
  303. //
  304. // Function: ChainGetCryptnetModule
  305. //
  306. // Synopsis: get the cryptnet.dll module handle
  307. //
  308. //----------------------------------------------------------------------------
  309. HMODULE WINAPI
  310. ChainGetCryptnetModule ()
  311. {
  312. HMODULE hModule;
  313. EnterCriticalSection( &g_CryptnetLock );
  314. if ( g_hCryptnet == NULL )
  315. {
  316. g_hCryptnet = LoadLibraryA( "cryptnet.dll" );
  317. }
  318. hModule = g_hCryptnet;
  319. LeaveCriticalSection( &g_CryptnetLock );
  320. return( hModule );
  321. }
  322. //+===========================================================================
  323. // RegisterWaitForSingleObject and UnregisterWaitEx are only supported
  324. // in kernel32.dll on NT5.
  325. //
  326. // Internal functions to do dynamic calls
  327. //-===========================================================================
  328. typedef BOOL (WINAPI *PFN_REGISTER_WAIT_FOR_SINGLE_OBJECT)(
  329. PHANDLE hNewWaitObject,
  330. HANDLE hObject,
  331. WAITORTIMERCALLBACK Callback,
  332. PVOID Context,
  333. ULONG dwMilliseconds,
  334. ULONG dwFlags
  335. );
  336. typedef BOOL (WINAPI *PFN_UNREGISTER_WAIT_EX)(
  337. HANDLE WaitHandle,
  338. HANDLE CompletionEvent // INVALID_HANDLE_VALUE => create event
  339. // to wait for
  340. );
  341. #define sz_KERNEL32_DLL "kernel32.dll"
  342. #define sz_RegisterWaitForSingleObject "RegisterWaitForSingleObject"
  343. #define sz_UnregisterWaitEx "UnregisterWaitEx"
  344. BOOL
  345. WINAPI
  346. InternalRegisterWaitForSingleObject(
  347. PHANDLE hNewWaitObject,
  348. HANDLE hObject,
  349. WAITORTIMERCALLBACK Callback,
  350. PVOID Context,
  351. ULONG dwMilliseconds,
  352. ULONG dwFlags
  353. )
  354. {
  355. BOOL fResult;
  356. HMODULE hKernel32Dll = NULL;
  357. PFN_REGISTER_WAIT_FOR_SINGLE_OBJECT pfnRegisterWaitForSingleObject;
  358. if (NULL == (hKernel32Dll = LoadLibraryA(sz_KERNEL32_DLL)))
  359. goto LoadKernel32DllError;
  360. if (NULL == (pfnRegisterWaitForSingleObject =
  361. (PFN_REGISTER_WAIT_FOR_SINGLE_OBJECT) GetProcAddress(
  362. hKernel32Dll, sz_RegisterWaitForSingleObject)))
  363. goto GetRegisterWaitForSingleObjectProcAddressError;
  364. fResult = pfnRegisterWaitForSingleObject(
  365. hNewWaitObject,
  366. hObject,
  367. Callback,
  368. Context,
  369. dwMilliseconds,
  370. dwFlags
  371. );
  372. CommonReturn:
  373. if (hKernel32Dll) {
  374. DWORD dwErr = GetLastError();
  375. FreeLibrary(hKernel32Dll);
  376. SetLastError(dwErr);
  377. }
  378. return fResult;
  379. ErrorReturn:
  380. *hNewWaitObject = NULL;
  381. fResult = FALSE;
  382. goto CommonReturn;
  383. TRACE_ERROR(LoadKernel32DllError)
  384. TRACE_ERROR(GetRegisterWaitForSingleObjectProcAddressError)
  385. }
  386. BOOL
  387. WINAPI
  388. InternalUnregisterWaitEx(
  389. HANDLE WaitHandle,
  390. HANDLE CompletionEvent // INVALID_HANDLE_VALUE => create event
  391. // to wait for
  392. )
  393. {
  394. BOOL fResult;
  395. HMODULE hKernel32Dll = NULL;
  396. PFN_UNREGISTER_WAIT_EX pfnUnregisterWaitEx;
  397. if (NULL == (hKernel32Dll = LoadLibraryA(sz_KERNEL32_DLL)))
  398. goto LoadKernel32DllError;
  399. if (NULL == (pfnUnregisterWaitEx =
  400. (PFN_UNREGISTER_WAIT_EX) GetProcAddress(
  401. hKernel32Dll, sz_UnregisterWaitEx)))
  402. goto GetUnregisterWaitExProcAddressError;
  403. fResult = pfnUnregisterWaitEx(
  404. WaitHandle,
  405. CompletionEvent
  406. );
  407. CommonReturn:
  408. if (hKernel32Dll) {
  409. DWORD dwErr = GetLastError();
  410. FreeLibrary(hKernel32Dll);
  411. SetLastError(dwErr);
  412. }
  413. return fResult;
  414. ErrorReturn:
  415. fResult = FALSE;
  416. goto CommonReturn;
  417. TRACE_ERROR(LoadKernel32DllError)
  418. TRACE_ERROR(GetUnregisterWaitExProcAddressError)
  419. }
  420. //+===========================================================================
  421. // We only get logoff notification in winlogon.exe.
  422. //
  423. // The work around is to have the winlogon ChainWlxLogoffEvent pulse a
  424. // named event. All processes where crypt32.dll is loaded will be doing
  425. // a RegisterWaitForObject for this event.
  426. //
  427. // Note, there is a very small window where we might not be waiting at the
  428. // time the event is pulsed.
  429. //-===========================================================================
  430. #define CRYPT32_LOGOFF_EVENT "Global\\crypt32LogoffEvent"
  431. HANDLE g_hLogoffEvent;
  432. HANDLE g_hLogoffRegWaitFor;
  433. typedef BOOL (WINAPI *PFN_WLX_LOGOFF)(
  434. PWLX_NOTIFICATION_INFO pNotificationInfo
  435. );
  436. VOID NTAPI LogoffWaitForCallback(
  437. PVOID Context,
  438. BOOLEAN fWaitOrTimedOut // ???
  439. )
  440. {
  441. HMODULE hModule;
  442. CertFreeCertificateChainEngine( HCCE_CURRENT_USER );
  443. // Only call if cryptnet has been loaded
  444. if (NULL != GetModuleHandleA("cryptnet.dll")) {
  445. hModule = ChainGetCryptnetModule();
  446. if (hModule) {
  447. PFN_WLX_LOGOFF pfn;
  448. pfn = (PFN_WLX_LOGOFF) GetProcAddress(hModule,
  449. "CryptnetWlxLogoffEvent");
  450. if (pfn)
  451. pfn(NULL);
  452. }
  453. }
  454. }
  455. // Note, the event must not be created while impersonating. That's why it
  456. // is created at ProcessAttach.
  457. VOID WINAPI
  458. CreateRoamingLogoffNotificationEvent()
  459. {
  460. SECURITY_ATTRIBUTES sa;
  461. SECURITY_DESCRIPTOR sd;
  462. SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
  463. SID_IDENTIFIER_AUTHORITY siaWorldSidAuthority =
  464. SECURITY_WORLD_SID_AUTHORITY;
  465. PSID psidLocalSystem = NULL;
  466. PSID psidEveryone = NULL;
  467. PACL pDacl = NULL;
  468. DWORD dwAclSize;
  469. if (!FIsWinNT5())
  470. return;
  471. // Allow Everyone to have SYNCHRONIZE access to the logoff event.
  472. // Only allow LocalSystem to have ALL access
  473. if (!AllocateAndInitializeSid(
  474. &siaNtAuthority,
  475. 1,
  476. SECURITY_LOCAL_SYSTEM_RID,
  477. 0, 0, 0, 0, 0, 0, 0,
  478. &psidLocalSystem
  479. ))
  480. goto AllocateAndInitializeSidError;
  481. if (!AllocateAndInitializeSid(
  482. &siaWorldSidAuthority,
  483. 1,
  484. SECURITY_WORLD_RID,
  485. 0, 0, 0, 0, 0, 0, 0,
  486. &psidEveryone
  487. ))
  488. goto AllocateAndInitializeSidError;
  489. //
  490. // compute size of ACL
  491. //
  492. dwAclSize = sizeof(ACL) +
  493. 2 * ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
  494. GetLengthSid(psidLocalSystem) +
  495. GetLengthSid(psidEveryone)
  496. ;
  497. //
  498. // allocate storage for Acl
  499. //
  500. if (NULL == (pDacl = (PACL) PkiNonzeroAlloc(dwAclSize)))
  501. goto OutOfMemory;
  502. if (!InitializeAcl(pDacl, dwAclSize, ACL_REVISION))
  503. goto InitializeAclError;
  504. if (!AddAccessAllowedAce(
  505. pDacl,
  506. ACL_REVISION,
  507. EVENT_ALL_ACCESS,
  508. psidLocalSystem
  509. ))
  510. goto AddAceError;
  511. if (!AddAccessAllowedAce(
  512. pDacl,
  513. ACL_REVISION,
  514. SYNCHRONIZE,
  515. psidEveryone
  516. ))
  517. goto AddAceError;
  518. if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
  519. goto InitializeSecurityDescriptorError;
  520. if (!SetSecurityDescriptorDacl(&sd, TRUE, pDacl, FALSE))
  521. goto SetSecurityDescriptorDaclError;
  522. sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  523. sa.lpSecurityDescriptor = &sd;
  524. sa.bInheritHandle = FALSE;
  525. g_hLogoffEvent = CreateEventA(
  526. &sa,
  527. TRUE, // fManualReset, must be TRUE to pulse all waitors
  528. FALSE, // fInitialState
  529. CRYPT32_LOGOFF_EVENT
  530. );
  531. if (NULL == g_hLogoffEvent) {
  532. // Try to open with only SYNCHRONIZE access
  533. g_hLogoffEvent = OpenEventA(
  534. SYNCHRONIZE,
  535. FALSE, // fInherit
  536. CRYPT32_LOGOFF_EVENT
  537. );
  538. if (NULL == g_hLogoffEvent)
  539. goto CreateEventError;
  540. }
  541. CommonReturn:
  542. if (psidLocalSystem)
  543. FreeSid(psidLocalSystem);
  544. if (psidEveryone)
  545. FreeSid(psidEveryone);
  546. PkiFree(pDacl);
  547. return;
  548. ErrorReturn:
  549. goto CommonReturn;
  550. TRACE_ERROR(AllocateAndInitializeSidError)
  551. TRACE_ERROR(OutOfMemory)
  552. TRACE_ERROR(InitializeAclError)
  553. TRACE_ERROR(AddAceError)
  554. TRACE_ERROR(InitializeSecurityDescriptorError)
  555. TRACE_ERROR(SetSecurityDescriptorDaclError)
  556. TRACE_ERROR(CreateEventError)
  557. }
  558. VOID WINAPI
  559. InitializeRoamingLogoffNotification()
  560. {
  561. if (!FIsWinNT5())
  562. return;
  563. if (g_fRoamingLogoffNotificationInitialized)
  564. return;
  565. EnterCriticalSection(&g_RoamingLogoffNotificationLock);
  566. if (g_fRoamingLogoffNotificationInitialized)
  567. goto CommonReturn;
  568. if (NULL == g_hLogoffEvent)
  569. goto NoLogoffEvent;
  570. // No need to do roaming logoff notification for TS processes
  571. if (0 != GetSystemMetrics(SM_REMOTESESSION))
  572. goto CommonReturn;
  573. // Note, this can't be called at ProcessAttach
  574. if (!InternalRegisterWaitForSingleObject(
  575. &g_hLogoffRegWaitFor,
  576. g_hLogoffEvent,
  577. LogoffWaitForCallback,
  578. NULL, // Context
  579. INFINITE, // no timeout
  580. WT_EXECUTEINWAITTHREAD
  581. ))
  582. goto RegisterWaitForError;
  583. CommonReturn:
  584. g_fRoamingLogoffNotificationInitialized = TRUE;
  585. LeaveCriticalSection(&g_RoamingLogoffNotificationLock);
  586. return;
  587. ErrorReturn:
  588. goto CommonReturn;
  589. SET_ERROR(NoLogoffEvent, E_UNEXPECTED)
  590. TRACE_ERROR(RegisterWaitForError)
  591. }
  592. VOID WINAPI
  593. UninitializeRoamingLogoffNotification()
  594. {
  595. if (g_hLogoffRegWaitFor) {
  596. InternalUnregisterWaitEx(g_hLogoffRegWaitFor, INVALID_HANDLE_VALUE);
  597. g_hLogoffRegWaitFor = NULL;
  598. }
  599. if (g_hLogoffEvent) {
  600. CloseHandle(g_hLogoffEvent);
  601. g_hLogoffEvent = NULL;
  602. }
  603. }
  604. //+---------------------------------------------------------------------------
  605. //
  606. // Function: ChainWlxLogoffEvent
  607. //
  608. // Synopsis: logoff event processing
  609. //
  610. //----------------------------------------------------------------------------
  611. BOOL WINAPI
  612. ChainWlxLogoffEvent (PWLX_NOTIFICATION_INFO pNotificationInfo)
  613. {
  614. if (g_hLogoffRegWaitFor) {
  615. InternalUnregisterWaitEx(g_hLogoffRegWaitFor, INVALID_HANDLE_VALUE);
  616. g_hLogoffRegWaitFor = NULL;
  617. }
  618. CertFreeCertificateChainEngine( HCCE_CURRENT_USER );
  619. if (g_hLogoffEvent) {
  620. // Trigger all non-winlogon processes to do logoff processing
  621. PulseEvent(g_hLogoffEvent);
  622. }
  623. return( TRUE );
  624. }