Source code of Windows XP (NT5)
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.

812 lines
19 KiB

  1. //+-----------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (c) Microsoft Corporation 1992 - 1996
  6. //
  7. // File: ctxtmgr.cxx
  8. //
  9. // Contents: Code for managing contexts list for the Kerberos package
  10. //
  11. //
  12. // History: 17-April-1996 Created MikeSw
  13. //
  14. //------------------------------------------------------------------------
  15. #define CTXTMGR_ALLOCATE
  16. #include <kerbkrnl.h>
  17. #ifdef ALLOC_PRAGMA
  18. #pragma alloc_text(PAGE, KerbInitContextList)
  19. #pragma alloc_text(PAGE, KerbFreeContextList)
  20. #pragma alloc_text(PAGEMSG, KerbAllocateContext)
  21. #pragma alloc_text(PAGEMSG, KerbInsertContext)
  22. #pragma alloc_text(PAGEMSG, KerbReferenceContext)
  23. #pragma alloc_text(PAGEMSG, KerbDereferenceContext)
  24. #pragma alloc_text(PAGEMSG, KerbReferenceContextByPointer)
  25. #pragma alloc_text(PAGEMSG, KerbReferenceContextByLsaHandle)
  26. #pragma alloc_text(PAGEMSG, KerbCreateKernelModeContext)
  27. #endif
  28. #define MAYBE_PAGED_CODE() \
  29. if ( KerbPoolType == PagedPool ) \
  30. { \
  31. PAGED_CODE(); \
  32. }
  33. //+-------------------------------------------------------------------------
  34. //
  35. // Function: KerbInitContextList
  36. //
  37. // Synopsis: Initializes the contexts list
  38. //
  39. // Effects: allocates a resources
  40. //
  41. // Arguments: none
  42. //
  43. // Requires:
  44. //
  45. // Returns: STATUS_SUCCESS on success, other error codes
  46. // on failure
  47. //
  48. // Notes:
  49. //
  50. //
  51. //--------------------------------------------------------------------------
  52. NTSTATUS
  53. KerbInitContextList(
  54. VOID
  55. )
  56. {
  57. NTSTATUS Status;
  58. PAGED_CODE();
  59. Status = ExInitializeResourceLite( &KerbContextResource );
  60. if (!NT_SUCCESS(Status))
  61. {
  62. return(Status);
  63. }
  64. Status = KerbInitializeList( &KerbContextList );
  65. if (!NT_SUCCESS(Status))
  66. {
  67. ExDeleteResourceLite( &KerbContextResource );
  68. goto Cleanup;
  69. }
  70. KerberosContextsInitialized = TRUE;
  71. Cleanup:
  72. return(Status);
  73. }
  74. #if 0
  75. //+-------------------------------------------------------------------------
  76. //
  77. // Function: KerbFreeContextList
  78. //
  79. // Synopsis: Frees the contexts list
  80. //
  81. // Effects:
  82. //
  83. // Arguments: none
  84. //
  85. // Requires:
  86. //
  87. // Returns: none
  88. //
  89. // Notes:
  90. //
  91. //
  92. //--------------------------------------------------------------------------
  93. VOID
  94. KerbFreeContextList(
  95. VOID
  96. )
  97. {
  98. PLIST_ENTRY ListEntry;
  99. PKERB_KERNEL_CONTEXT Context;
  100. PAGED_CODE();
  101. if (KerberosContextsInitialized)
  102. {
  103. KerbLockList(&KerbContextList);
  104. //
  105. // Go through the list of logon sessions and dereferences them all
  106. //
  107. while (!IsListEmpty(&KerbContextList.List))
  108. {
  109. Context = CONTAINING_RECORD(
  110. KerbContextList.List.Flink,
  111. KERB_KERNEL_CONTEXT,
  112. ListEntry.Next
  113. );
  114. KerbReferenceListEntry(
  115. &KerbContextList,
  116. (PKERBEROS_LIST_ENTRY) Context,
  117. TRUE
  118. );
  119. KerbDereferenceContext(Context);
  120. }
  121. KerbUnlockList(&KerbContextList);
  122. KerbFreeList(&KerbContextList);
  123. }
  124. }
  125. #endif
  126. //+-------------------------------------------------------------------------
  127. //
  128. // Function: KerbAllocateContext
  129. //
  130. // Synopsis: Allocates a Context structure
  131. //
  132. // Effects: Allocates a Context, but does not add it to the
  133. // list of Contexts
  134. //
  135. // Arguments: NewContext - receives a new Context allocated
  136. // with KerbAllocate
  137. //
  138. // Requires:
  139. //
  140. // Returns: STATUS_SUCCESS on success
  141. // STATUS_INSUFFICIENT_RESOURCES if the allocation fails
  142. //
  143. // Notes:
  144. //
  145. //
  146. //--------------------------------------------------------------------------
  147. NTSTATUS
  148. KerbAllocateContext(
  149. PKERB_KERNEL_CONTEXT * NewContext
  150. )
  151. {
  152. PKERB_KERNEL_CONTEXT Context;
  153. NTSTATUS Status;
  154. MAYBE_PAGED_CODE();
  155. //
  156. // Get the client process ID if we are running in the LSA
  157. //
  158. Context = (PKERB_KERNEL_CONTEXT) KerbAllocate(
  159. sizeof(KERB_KERNEL_CONTEXT) );
  160. if (Context == NULL)
  161. {
  162. return(STATUS_INSUFFICIENT_RESOURCES);
  163. }
  164. RtlZeroMemory(
  165. Context,
  166. sizeof(KERB_KERNEL_CONTEXT)
  167. );
  168. KsecInitializeListEntry( &Context->List, KERB_CONTEXT_SIGNATURE );
  169. *NewContext = Context;
  170. return(STATUS_SUCCESS);
  171. }
  172. //+-------------------------------------------------------------------------
  173. //
  174. // Function: KerbInsertContext
  175. //
  176. // Synopsis: Inserts a logon session into the list of logon sessions
  177. //
  178. // Effects: bumps reference count on logon session
  179. //
  180. // Arguments: Context - Context to insert
  181. //
  182. // Requires:
  183. //
  184. // Returns: STATUS_SUCCESS always
  185. //
  186. // Notes:
  187. //
  188. //
  189. //--------------------------------------------------------------------------
  190. NTSTATUS
  191. KerbInsertContext(
  192. IN PKERB_KERNEL_CONTEXT Context
  193. )
  194. {
  195. MAYBE_PAGED_CODE();
  196. KSecInsertListEntry(
  197. KerbActiveList,
  198. (PKSEC_LIST_ENTRY) Context
  199. );
  200. return(STATUS_SUCCESS);
  201. }
  202. #if 0
  203. //+-------------------------------------------------------------------------
  204. //
  205. // Function: KerbReferenceContextByLsaHandle
  206. //
  207. // Synopsis: Locates a context by lsa handle and references it
  208. //
  209. // Effects: Increments reference count and possible unlinks it from list
  210. //
  211. // Arguments: ContextHandle - Handle of context to reference.
  212. // RemoveFromList - If TRUE, context will be delinked.
  213. //
  214. // Requires:
  215. //
  216. // Returns:
  217. //
  218. // Notes:
  219. //
  220. //
  221. //--------------------------------------------------------------------------
  222. PKERB_KERNEL_CONTEXT
  223. KerbReferenceContextByLsaHandle(
  224. IN LSA_SEC_HANDLE ContextHandle,
  225. IN BOOLEAN RemoveFromList
  226. )
  227. {
  228. PLIST_ENTRY ListEntry;
  229. PKERB_KERNEL_CONTEXT Context = NULL;
  230. BOOLEAN Found = FALSE;
  231. SECPKG_CLIENT_INFO ClientInfo;
  232. NTSTATUS Status;
  233. PAGED_CODE();
  234. KerbLockList(&KerbContextList);
  235. //
  236. // Go through the list of logon sessions looking for the correct
  237. // LUID
  238. //
  239. for (ListEntry = KerbContextList.List.Flink ;
  240. ListEntry != &KerbContextList.List ;
  241. ListEntry = ListEntry->Flink )
  242. {
  243. Context = CONTAINING_RECORD(ListEntry, KERB_KERNEL_CONTEXT, ListEntry.Next);
  244. if (ContextHandle == Context->LsaContextHandle)
  245. {
  246. KerbReferenceListEntry(
  247. &KerbContextList,
  248. (PKERBEROS_LIST_ENTRY) Context,
  249. RemoveFromList
  250. );
  251. Found = TRUE;
  252. break;
  253. }
  254. }
  255. if (!Found)
  256. {
  257. Context = NULL;
  258. }
  259. KerbUnlockList(&KerbContextList);
  260. return(Context);
  261. }
  262. #endif
  263. //+-------------------------------------------------------------------------
  264. //
  265. // Function: KerbReferenceContext
  266. //
  267. // Synopsis: Locates a context and references it
  268. //
  269. // Effects: Increments reference count and possible unlinks it from list
  270. //
  271. // Arguments: ContextHandle - Lsa Handle of context to reference.
  272. // RemoveFromList - If TRUE, context will be delinked.
  273. //
  274. // Requires:
  275. //
  276. // Returns:
  277. //
  278. // Notes:
  279. //
  280. //
  281. //--------------------------------------------------------------------------
  282. PKERB_KERNEL_CONTEXT
  283. KerbReferenceContext(
  284. IN LSA_SEC_HANDLE ContextHandle,
  285. IN BOOLEAN RemoveFromList
  286. )
  287. {
  288. PKERB_KERNEL_CONTEXT Context = NULL;
  289. NTSTATUS Status;
  290. MAYBE_PAGED_CODE();
  291. Status = KSecReferenceListEntry(
  292. (PKSEC_LIST_ENTRY) ContextHandle,
  293. KERB_CONTEXT_SIGNATURE,
  294. RemoveFromList );
  295. if ( NT_SUCCESS( Status ) )
  296. {
  297. Context = (PKERB_KERNEL_CONTEXT) ContextHandle ;
  298. }
  299. //
  300. // In kernel mode we trust the caller to provide a valid pointer, but
  301. // make sure it is a kernel mode pointer.
  302. //
  303. return(Context);
  304. }
  305. #if 0
  306. //+-------------------------------------------------------------------------
  307. //
  308. // Function: KerbReferenceContextByPointer
  309. //
  310. // Synopsis: References a context by the context pointer itself.
  311. //
  312. // Effects: Increments reference count and possible unlinks it from list
  313. //
  314. // Arguments: Context - The context to reference.
  315. // RemoveFromList - If TRUE, context will be delinked
  316. //
  317. // Requires:
  318. //
  319. // Returns:
  320. //
  321. // Notes:
  322. //
  323. //
  324. //--------------------------------------------------------------------------
  325. VOID
  326. KerbReferenceContextByPointer(
  327. IN PKERB_KERNEL_CONTEXT Context,
  328. IN BOOLEAN RemoveFromList
  329. )
  330. {
  331. PAGED_CODE();
  332. KerbLockList(&KerbContextList);
  333. KerbReferenceListEntry(
  334. &KerbContextList,
  335. (PKERBEROS_LIST_ENTRY) Context,
  336. RemoveFromList
  337. );
  338. if (RemoveFromList)
  339. {
  340. Context->ContextSignature = KERB_CONTEXT_DELETED_SIGNATURE;
  341. }
  342. KerbUnlockList(&KerbContextList);
  343. }
  344. #endif
  345. //+-------------------------------------------------------------------------
  346. //
  347. // Function: KerbFreeContext
  348. //
  349. // Synopsis: Frees a context that is unlinked
  350. //
  351. // Effects: frees all storage associated with the context
  352. //
  353. // Arguments: Context - context to free
  354. //
  355. // Requires:
  356. //
  357. // Returns: none
  358. //
  359. // Notes:
  360. //
  361. //
  362. //--------------------------------------------------------------------------
  363. VOID
  364. KerbFreeContext(
  365. IN PKERB_KERNEL_CONTEXT Context
  366. )
  367. {
  368. PAGED_CODE();
  369. if (Context->TokenHandle != NULL)
  370. {
  371. NtClose(Context->TokenHandle);
  372. }
  373. if (Context->AccessToken != NULL)
  374. {
  375. ObDereferenceObject( Context->AccessToken );
  376. }
  377. if (Context->FullName.Buffer != NULL)
  378. {
  379. KerbFree(Context->FullName.Buffer);
  380. }
  381. if (Context->SessionKey.keyvalue.value != NULL)
  382. {
  383. KerbFree(Context->SessionKey.keyvalue.value);
  384. }
  385. if (Context->pbMarshalledTargetInfo != NULL)
  386. {
  387. KerbFree(Context->pbMarshalledTargetInfo);
  388. }
  389. KerbFree(Context);
  390. }
  391. //+-------------------------------------------------------------------------
  392. //
  393. // Function: KerbDereferenceContext
  394. //
  395. // Synopsis: Dereferences a logon session - if reference count goes
  396. // to zero it frees the logon session
  397. //
  398. // Effects: decrements reference count
  399. //
  400. // Arguments: Context - Logon session to dereference
  401. //
  402. // Requires:
  403. //
  404. // Returns: none
  405. //
  406. // Notes:
  407. //
  408. //
  409. //--------------------------------------------------------------------------
  410. VOID
  411. KerbDereferenceContext(
  412. IN PKERB_KERNEL_CONTEXT Context
  413. )
  414. {
  415. BOOLEAN Delete ;
  416. MAYBE_PAGED_CODE();
  417. KSecDereferenceListEntry(
  418. (PKSEC_LIST_ENTRY) Context,
  419. &Delete );
  420. if ( Delete )
  421. {
  422. KerbFreeContext( Context );
  423. }
  424. }
  425. //+-------------------------------------------------------------------------
  426. //
  427. // Function: KerbCreateKernelModeContext
  428. //
  429. // Synopsis: Creates a kernel-mode context to support impersonation and
  430. // message integrity and privacy
  431. //
  432. // Effects:
  433. //
  434. // Arguments:
  435. //
  436. // Requires:
  437. //
  438. // Returns:
  439. //
  440. // Notes:
  441. //
  442. //
  443. //--------------------------------------------------------------------------
  444. NTSTATUS
  445. KerbCreateKernelModeContext(
  446. IN LSA_SEC_HANDLE ContextHandle,
  447. IN PSecBuffer MarshalledContext,
  448. OUT PKERB_KERNEL_CONTEXT * NewContext
  449. )
  450. {
  451. NTSTATUS Status;
  452. PKERB_KERNEL_CONTEXT Context = NULL;
  453. PKERB_PACKED_CONTEXT PackedContext ;
  454. PUCHAR Where;
  455. PAGED_CODE();
  456. if (MarshalledContext->cbBuffer < sizeof(KERB_PACKED_CONTEXT))
  457. {
  458. DebugLog((DEB_ERROR,"Invalid buffer size for marshalled context: was 0x%x, needed 0x%x\n",
  459. MarshalledContext->cbBuffer, sizeof(KERB_CONTEXT)));
  460. return(STATUS_INVALID_PARAMETER);
  461. }
  462. PackedContext = (PKERB_PACKED_CONTEXT) MarshalledContext->pvBuffer;
  463. Status = KerbAllocateContext( &Context );
  464. if (!NT_SUCCESS(Status))
  465. {
  466. goto Cleanup;
  467. }
  468. KsecInitializeListEntry( &Context->List, KERB_CONTEXT_SIGNATURE );
  469. Context->Lifetime = PackedContext->Lifetime;
  470. Context->RenewTime = PackedContext->RenewTime;
  471. Context->Nonce = PackedContext->Nonce;
  472. Context->ReceiveNonce = PackedContext->ReceiveNonce;
  473. Context->ContextFlags = PackedContext->ContextFlags;
  474. Context->ContextAttributes = PackedContext->ContextAttributes;
  475. Context->EncryptionType = PackedContext->EncryptionType;
  476. Context->LsaContextHandle = ContextHandle;
  477. Context->ReceiveNonce = Context->Nonce;
  478. Context->TokenHandle = (HANDLE) ULongToPtr(PackedContext->TokenHandle);
  479. //
  480. // Fill in the full name, which is the concatenation of the client name
  481. // and client realm with a '\\' separator
  482. //
  483. Context->FullName.MaximumLength = PackedContext->ClientName.Length +
  484. PackedContext->ClientRealm.Length +
  485. sizeof(WCHAR);
  486. Context->FullName.Buffer = (LPWSTR) KerbAllocate(Context->FullName.MaximumLength);
  487. if (Context->FullName.Buffer == NULL)
  488. {
  489. Status = STATUS_INSUFFICIENT_RESOURCES;
  490. goto Cleanup;
  491. }
  492. Where = (PUCHAR) Context->FullName.Buffer;
  493. if (PackedContext->ClientRealm.Length != 0)
  494. {
  495. RtlCopyMemory(
  496. Where,
  497. (PUCHAR) PackedContext + (ULONG_PTR) PackedContext->ClientRealm.Buffer,
  498. PackedContext->ClientRealm.Length
  499. );
  500. Where += PackedContext->ClientRealm.Length;
  501. *(LPWSTR) Where = L'\\';
  502. Where += sizeof(WCHAR);
  503. }
  504. if (PackedContext->ClientName.Length != 0)
  505. {
  506. RtlCopyMemory(
  507. Where,
  508. (PUCHAR) PackedContext + (ULONG_PTR) PackedContext->ClientName.Buffer,
  509. PackedContext->ClientName.Length
  510. );
  511. Where += PackedContext->ClientName.Length;
  512. }
  513. Context->FullName.Length = (USHORT) (Where - (PUCHAR) Context->FullName.Buffer);
  514. //
  515. // Copy in the session key
  516. //
  517. Context->SessionKey.keytype = PackedContext->SessionKeyType;
  518. Context->SessionKey.keyvalue.length = PackedContext->SessionKeyLength;
  519. if (Context->SessionKey.keyvalue.length != 0)
  520. {
  521. Context->SessionKey.keyvalue.value = (PUCHAR) KerbAllocate( Context->SessionKey.keyvalue.length );
  522. if (Context->SessionKey.keyvalue.value == NULL)
  523. {
  524. Status = STATUS_INSUFFICIENT_RESOURCES;
  525. goto Cleanup;
  526. }
  527. RtlCopyMemory(
  528. Context->SessionKey.keyvalue.value,
  529. (PUCHAR) PackedContext + PackedContext->SessionKeyOffset,
  530. Context->SessionKey.keyvalue.length
  531. );
  532. }
  533. //
  534. // copy in the marshalled target info.
  535. //
  536. Context->cbMarshalledTargetInfo = PackedContext->MarshalledTargetInfoLength;
  537. if (PackedContext->MarshalledTargetInfo)
  538. {
  539. Context->pbMarshalledTargetInfo = (PUCHAR) KerbAllocate( Context->cbMarshalledTargetInfo );
  540. if (Context->pbMarshalledTargetInfo == NULL)
  541. {
  542. Status = STATUS_INSUFFICIENT_RESOURCES;
  543. goto Cleanup;
  544. }
  545. RtlCopyMemory(
  546. Context->pbMarshalledTargetInfo,
  547. (PUCHAR) PackedContext + PackedContext->MarshalledTargetInfo,
  548. Context->cbMarshalledTargetInfo
  549. );
  550. } else {
  551. Context->pbMarshalledTargetInfo = NULL;
  552. }
  553. Status = KerbInsertContext(
  554. Context
  555. );
  556. if (!NT_SUCCESS(Status))
  557. {
  558. DebugLog((DEB_ERROR,"Failed to insert context: 0x%x\n",Status));
  559. goto Cleanup;
  560. }
  561. *NewContext = Context;
  562. Cleanup:
  563. if (!NT_SUCCESS(Status))
  564. {
  565. if (Context != NULL)
  566. {
  567. KerbFreeContext(Context);
  568. }
  569. }
  570. return(Status);
  571. }
  572. #if 0
  573. NTSTATUS
  574. KerbCreateKernelModeContext(
  575. IN LSA_SEC_HANDLE ContextHandle,
  576. IN PSecBuffer MarshalledContext,
  577. OUT PKERB_KERNEL_CONTEXT * NewContext
  578. )
  579. {
  580. NTSTATUS Status;
  581. PKERB_KERNEL_CONTEXT Context = NULL;
  582. PKERB_CONTEXT LsaContext;
  583. PUCHAR Where;
  584. PAGED_CODE();
  585. if (MarshalledContext->cbBuffer < sizeof(KERB_CONTEXT))
  586. {
  587. DebugLog((DEB_ERROR,"Invalid buffer size for marshalled context: was 0x%x, needed 0x%x\n",
  588. MarshalledContext->cbBuffer, sizeof(KERB_CONTEXT)));
  589. return(STATUS_INVALID_PARAMETER);
  590. }
  591. LsaContext = (PKERB_CONTEXT) MarshalledContext->pvBuffer;
  592. Status = KerbAllocateContext( &Context );
  593. if (!NT_SUCCESS(Status))
  594. {
  595. goto Cleanup;
  596. }
  597. KsecInitializeListEntry( &Context->List, KERB_CONTEXT_SIGNATURE );
  598. Context->Lifetime = LsaContext->Lifetime;
  599. Context->RenewTime = LsaContext->RenewTime;
  600. Context->Nonce = LsaContext->Nonce;
  601. Context->ReceiveNonce = LsaContext->ReceiveNonce;
  602. Context->ContextFlags = LsaContext->ContextFlags;
  603. Context->ContextAttributes = LsaContext->ContextAttributes;
  604. Context->EncryptionType = LsaContext->EncryptionType;
  605. Context->LsaContextHandle = ContextHandle;
  606. Context->ReceiveNonce = Context->Nonce;
  607. Context->TokenHandle = LsaContext->TokenHandle;
  608. //
  609. // Fill in the full name, which is the concatenation of the client name
  610. // and client realm with a '\\' separator
  611. //
  612. Context->FullName.MaximumLength = LsaContext->ClientName.Length +
  613. LsaContext->ClientRealm.Length +
  614. sizeof(WCHAR);
  615. Context->FullName.Buffer = (LPWSTR) KerbAllocate(Context->FullName.MaximumLength);
  616. if (Context->FullName.Buffer == NULL)
  617. {
  618. Status = STATUS_INSUFFICIENT_RESOURCES;
  619. goto Cleanup;
  620. }
  621. Where = (PUCHAR) Context->FullName.Buffer;
  622. if (LsaContext->ClientRealm.Length != 0)
  623. {
  624. RtlCopyMemory(
  625. Where,
  626. (PUCHAR) LsaContext + (ULONG_PTR) LsaContext->ClientRealm.Buffer,
  627. LsaContext->ClientRealm.Length
  628. );
  629. Where += LsaContext->ClientRealm.Length;
  630. *(LPWSTR) Where = L'\\';
  631. Where += sizeof(WCHAR);
  632. }
  633. if (LsaContext->ClientName.Length != 0)
  634. {
  635. RtlCopyMemory(
  636. Where,
  637. (PUCHAR) LsaContext + (ULONG_PTR) LsaContext->ClientName.Buffer,
  638. LsaContext->ClientName.Length
  639. );
  640. Where += LsaContext->ClientName.Length;
  641. }
  642. Context->FullName.Length = (USHORT) (Where - (PUCHAR) Context->FullName.Buffer);
  643. //
  644. // Copy in the session key
  645. //
  646. LsaContext->SessionKey.keyvalue.value = (PUCHAR) LsaContext->SessionKey.keyvalue.value + (ULONG_PTR) LsaContext;
  647. Context->SessionKey.keytype = LsaContext->SessionKey.keytype;
  648. Context->SessionKey.keyvalue.length = LsaContext->SessionKey.keyvalue.length;
  649. if (Context->SessionKey.keyvalue.length != 0)
  650. {
  651. Context->SessionKey.keyvalue.value = (PUCHAR) KerbAllocate(LsaContext->SessionKey.keyvalue.length);
  652. if (Context->SessionKey.keyvalue.value == NULL)
  653. {
  654. Status = STATUS_INSUFFICIENT_RESOURCES;
  655. goto Cleanup;
  656. }
  657. RtlCopyMemory(
  658. Context->SessionKey.keyvalue.value,
  659. LsaContext->SessionKey.keyvalue.value,
  660. LsaContext->SessionKey.keyvalue.length
  661. );
  662. }
  663. Status = KerbInsertContext(
  664. Context
  665. );
  666. if (!NT_SUCCESS(Status))
  667. {
  668. DebugLog((DEB_ERROR,"Failed to insert context: 0x%x\n",Status));
  669. goto Cleanup;
  670. }
  671. *NewContext = Context;
  672. Cleanup:
  673. if (!NT_SUCCESS(Status))
  674. {
  675. if (Context != NULL)
  676. {
  677. KerbFreeContext(Context);
  678. }
  679. }
  680. return(Status);
  681. }
  682. #endif