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.

2068 lines
52 KiB

  1. //+-----------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (c) Microsoft Corporation 1992 - 1996
  6. //
  7. // File: tktcache.cxx
  8. //
  9. // Contents: Ticket cache for Kerberos Package
  10. //
  11. //
  12. // History: 16-April-1996 Created MikeSw
  13. //
  14. //------------------------------------------------------------------------
  15. #include <kerb.hxx>
  16. #define TKTCACHE_ALLOCATE
  17. #include <kerbp.h>
  18. #ifdef DEBUG_SUPPORT
  19. static TCHAR THIS_FILE[]=TEXT(__FILE__);
  20. #endif
  21. //
  22. // Statistics for tracking hits/misses in cache
  23. //
  24. #define UpdateCacheHits() (InterlockedIncrement(&KerbTicketCacheHits))
  25. #define UpdateCacheMisses() (InterlockedIncrement(&KerbTicketCacheMisses))
  26. //
  27. // Ticket expiration/renewal
  28. //
  29. void
  30. KerbScheduleTgtRenewal(
  31. IN KERB_TICKET_CACHE_ENTRY * CacheEntry
  32. );
  33. void
  34. KerbTgtRenewalTrigger(
  35. void * TaskHandle,
  36. void * TaskItem
  37. );
  38. void
  39. KerbTgtRenewalReaper(
  40. void * TaskItem
  41. );
  42. #if DBG
  43. LIST_ENTRY GlobalTicketList;
  44. #endif
  45. LONG GlobalTicketListSize;
  46. ULONG ScavengedSessions = 60;
  47. //+-------------------------------------------------------------------------
  48. //
  49. // Function: KerbScheduleTicketCleanup
  50. //
  51. // Synopsis: Schedules garbage collection for tickets.
  52. //
  53. // Effects:
  54. //
  55. // Arguments: TaskItem - cache entry to destroy
  56. //
  57. // Requires:
  58. //
  59. // Returns: Nothing
  60. //
  61. // Notes:
  62. //
  63. //
  64. //--------------------------------------------------------------------------
  65. VOID
  66. KerbScheduleTicketCleanup()
  67. {
  68. NTSTATUS Status;
  69. ULONG Interval = KERB_TICKET_COLLECTOR_INTERVAL; // change to global
  70. if ( Interval > 0 )
  71. {
  72. Status = KerbAddScavengerTask(
  73. TRUE,
  74. Interval,
  75. 0, // no special processing flags
  76. KerbTicketScavenger,
  77. NULL,
  78. NULL,
  79. NULL
  80. );
  81. if (!NT_SUCCESS( Status ))
  82. {
  83. D_DebugLog((DEB_ERROR, "ScheduleTicketCleanup failed %x\n", Status));
  84. }
  85. }
  86. }
  87. //+-------------------------------------------------------------------------
  88. //
  89. // Function: KerbInitTicketCaching
  90. //
  91. // Synopsis: Initialize the ticket caching code
  92. //
  93. // Effects: Creates a SAFE_RESOURCE
  94. //
  95. // Arguments: none
  96. //
  97. // Requires:
  98. //
  99. // Returns: STATUS_SUCCESS on success, NTSTATUS from Rtl routines
  100. // on error
  101. //
  102. // Notes:
  103. //
  104. //
  105. //--------------------------------------------------------------------------
  106. NTSTATUS
  107. KerbInitTicketCaching(
  108. VOID
  109. )
  110. {
  111. __try
  112. {
  113. SafeInitializeResource(&KerberosTicketCacheLock, TICKET_CACHE_LOCK_ENUM);
  114. }
  115. __except(EXCEPTION_EXECUTE_HANDLER)
  116. {
  117. return STATUS_INSUFFICIENT_RESOURCES;
  118. }
  119. KerberosTicketCacheInitialized = TRUE;
  120. KerbScheduleTicketCleanup();
  121. return STATUS_SUCCESS;
  122. }
  123. //+-------------------------------------------------------------------------
  124. //
  125. // Function: KerbInitTicketCache
  126. //
  127. // Synopsis: Initializes a single ticket cache
  128. //
  129. // Effects: initializes list entry
  130. //
  131. // Arguments: TicketCache - The ticket cache to initialize
  132. //
  133. // Requires:
  134. //
  135. // Returns: none
  136. //
  137. // Notes:
  138. //
  139. //
  140. //--------------------------------------------------------------------------
  141. VOID
  142. KerbInitTicketCache(
  143. IN PKERB_TICKET_CACHE TicketCache
  144. )
  145. {
  146. InitializeListHead(&TicketCache->CacheEntries);
  147. GetSystemTimeAsFileTime(( PFILETIME )&TicketCache->LastCleanup );
  148. }
  149. //+-------------------------------------------------------------------------
  150. //
  151. // Function: KerbFreeTicketCache
  152. //
  153. // Synopsis: Frees the ticket cache global data
  154. //
  155. // Effects:
  156. //
  157. // Arguments: none
  158. //
  159. // Requires:
  160. //
  161. // Returns: none
  162. //
  163. // Notes:
  164. //
  165. //
  166. //--------------------------------------------------------------------------
  167. VOID
  168. KerbFreeTicketCache(
  169. VOID
  170. )
  171. {
  172. // if (KerberosTicketCacheInitialized)
  173. // {
  174. // KerbWriteLockTicketCache();
  175. // RtlDeleteResource(&KerberosTicketCacheLock);
  176. // }
  177. }
  178. //+-------------------------------------------------------------------------
  179. //
  180. // Function: KerbDereferenceTicketCacheEntry
  181. //
  182. // Synopsis: Dereferences a ticket cache entry
  183. //
  184. // Effects: Dereferences the ticket cache entry to make it go away
  185. // when it is no longer being used.
  186. //
  187. // Arguments: decrements reference count and delets cache entry if it goes
  188. // to zero
  189. //
  190. // Requires: TicketCacheEntry - The ticket cache entry to dereference.
  191. //
  192. // Returns: none
  193. //
  194. // Notes:
  195. //
  196. //
  197. //--------------------------------------------------------------------------
  198. VOID
  199. KerbDereferenceTicketCacheEntry(
  200. IN PKERB_TICKET_CACHE_ENTRY TicketCacheEntry
  201. )
  202. {
  203. DsysAssert(TicketCacheEntry->ListEntry.ReferenceCount != 0);
  204. #if DBG
  205. if (TicketCacheEntry->ListEntry.ReferenceCount >= 20)
  206. {
  207. D_DebugLog((DEB_WARN, "KerbDereferenceTicketCacheEntry TicketCacheEntry %p, ReferenceCount %#x\n",
  208. TicketCacheEntry, TicketCacheEntry->ListEntry.ReferenceCount));
  209. }
  210. #endif
  211. if ( 0 == InterlockedDecrement( (LONG *)&TicketCacheEntry->ListEntry.ReferenceCount )) {
  212. FRE_ASSERT( !TicketCacheEntry->Linked );
  213. #if DBG
  214. KerbWriteLockTicketCache();
  215. RemoveEntryList( &TicketCacheEntry->GlobalListEntry );
  216. KerbUnlockTicketCache();
  217. #endif
  218. InterlockedDecrement(&GlobalTicketListSize);
  219. DsysAssert( TicketCacheEntry->ScavengerHandle == NULL );
  220. KerbFreeKdcName(&TicketCacheEntry->ServiceName);
  221. KerbFreeString(&TicketCacheEntry->DomainName);
  222. KerbFreeString(&TicketCacheEntry->TargetDomainName);
  223. KerbFreeString(&TicketCacheEntry->AltTargetDomainName);
  224. KerbFreeString(&TicketCacheEntry->ClientDomainName);
  225. KerbFreeKdcName(&TicketCacheEntry->ClientName);
  226. KerbFreeKdcName(&TicketCacheEntry->AltClientName);
  227. KerbFreeKdcName(&TicketCacheEntry->TargetName);
  228. KerbFreeDuplicatedTicket(&TicketCacheEntry->Ticket);
  229. KerbFreeKey(&TicketCacheEntry->SessionKey);
  230. KerbFreeKey(&TicketCacheEntry->CredentialKey);
  231. KerbFree(TicketCacheEntry);
  232. }
  233. return;
  234. }
  235. //+-------------------------------------------------------------------------
  236. //
  237. // Function: KerbReferenceTicketCacheEntry
  238. //
  239. // Synopsis: References a ticket cache entry
  240. //
  241. // Effects: Increments the reference count on the ticket cache entry
  242. //
  243. // Arguments: TicketCacheEntry - Ticket cache entry to reference
  244. //
  245. // Returns: none
  246. //
  247. // Notes:
  248. //
  249. //
  250. //--------------------------------------------------------------------------
  251. VOID
  252. KerbReferenceTicketCacheEntry(
  253. IN PKERB_TICKET_CACHE_ENTRY TicketCacheEntry
  254. )
  255. {
  256. DsysAssert(TicketCacheEntry->ListEntry.ReferenceCount != 0);
  257. InterlockedIncrement( (LONG *)&TicketCacheEntry->ListEntry.ReferenceCount );
  258. #if DBG
  259. if (TicketCacheEntry->ListEntry.ReferenceCount >= 20)
  260. {
  261. D_DebugLog((DEB_WARN, "KerbReferenceTicketCacheEntry TicketCacheEntry %p, ReferenceCount %#x\n",
  262. TicketCacheEntry, TicketCacheEntry->ListEntry.ReferenceCount));
  263. }
  264. #endif
  265. }
  266. //+-------------------------------------------------------------------------
  267. //
  268. // Function: KerbInsertTicketCachEntry
  269. //
  270. // Synopsis: Inserts a ticket cache entry onto a ticket cache
  271. //
  272. // Effects:
  273. //
  274. // Arguments: TicketCache - The ticket cache on which to stick the ticket.
  275. // TicketCacheEntry - The entry to stick in the cache.
  276. //
  277. // Returns: none
  278. //
  279. // Notes:
  280. //
  281. //
  282. //--------------------------------------------------------------------------
  283. VOID
  284. KerbInsertTicketCacheEntry(
  285. IN PKERB_TICKET_CACHE TicketCache,
  286. IN PKERB_TICKET_CACHE_ENTRY TicketCacheEntry
  287. )
  288. {
  289. InterlockedIncrement( (LONG *)&TicketCacheEntry->ListEntry.ReferenceCount );
  290. KerbWriteLockTicketCache();
  291. InsertHeadList(
  292. &TicketCache->CacheEntries,
  293. &TicketCacheEntry->ListEntry.Next
  294. );
  295. KerbUnlockTicketCache();
  296. if ( FALSE != InterlockedCompareExchange(
  297. &TicketCacheEntry->Linked,
  298. (LONG)TRUE,
  299. (LONG)FALSE )) {
  300. //
  301. // Attempted to insert an already-linked entry
  302. //
  303. FRE_ASSERT( FALSE );
  304. }
  305. }
  306. //+-------------------------------------------------------------------------
  307. //
  308. // Function: KerbRemoveTicketCachEntry
  309. //
  310. // Synopsis: Removes a ticket cache entry from its ticket cache
  311. //
  312. // Effects:
  313. //
  314. // Arguments: TicketCacheEntry - The entry to yank out of the cache.
  315. //
  316. // Returns: none
  317. //
  318. // Notes:
  319. //
  320. //
  321. //--------------------------------------------------------------------------
  322. VOID
  323. KerbRemoveTicketCacheEntry(
  324. IN PKERB_TICKET_CACHE_ENTRY TicketCacheEntry
  325. )
  326. {
  327. //
  328. // An entry can only be unlinked from the cache once
  329. //
  330. if ( InterlockedCompareExchange(
  331. &TicketCacheEntry->Linked,
  332. (LONG)FALSE,
  333. (LONG)TRUE )) {
  334. HANDLE TaskHandle = NULL;
  335. DsysAssert(TicketCacheEntry->ListEntry.ReferenceCount != 0);
  336. KerbWriteLockTicketCache();
  337. RemoveEntryList(&TicketCacheEntry->ListEntry.Next);
  338. TaskHandle = TicketCacheEntry->ScavengerHandle;
  339. TicketCacheEntry->ScavengerHandle = NULL;
  340. KerbUnlockTicketCache();
  341. if ( TaskHandle ) {
  342. KerbTaskDoItNow( TaskHandle );
  343. }
  344. KerbDereferenceTicketCacheEntry(TicketCacheEntry);
  345. }
  346. }
  347. VOID
  348. FORCEINLINE
  349. ReplaceEntryList(
  350. IN PLIST_ENTRY OldEntry,
  351. IN PLIST_ENTRY NewEntry
  352. )
  353. {
  354. PLIST_ENTRY Flink;
  355. PLIST_ENTRY Blink;
  356. Flink = NewEntry->Flink = OldEntry->Flink;
  357. Blink = NewEntry->Blink = OldEntry->Blink;
  358. Blink->Flink = NewEntry;
  359. Flink->Blink = NewEntry;
  360. }
  361. //+-------------------------------------------------------------------------
  362. //
  363. // Function: KerbReplaceTicketCachEntry
  364. //
  365. // Synopsis: Replaces a ticket cache entry with a new one
  366. //
  367. // Effects:
  368. //
  369. // Arguments: OldTicketCacheEntry - The entry to yank out of the cache
  370. // NewTicketCacheEntry - The entry to put into old entry's place
  371. //
  372. // Returns: TRUE if the entry has been replaced
  373. // FALSE if the OldTicketCacheEntry was unlinked by someone else
  374. // before we got to it
  375. //
  376. // Notes:
  377. //
  378. //
  379. //--------------------------------------------------------------------------
  380. BOOLEAN
  381. KerbReplaceTicketCacheEntry(
  382. IN PKERB_TICKET_CACHE_ENTRY OldTicketCacheEntry,
  383. IN PKERB_TICKET_CACHE_ENTRY NewTicketCacheEntry
  384. )
  385. {
  386. BOOLEAN Result = FALSE;
  387. //
  388. // An entry can only be unlinked from the cache once
  389. //
  390. if ( InterlockedCompareExchange(
  391. &OldTicketCacheEntry->Linked,
  392. (LONG)FALSE,
  393. (LONG)TRUE )) {
  394. Result = TRUE;
  395. DsysAssert( OldTicketCacheEntry->ListEntry.ReferenceCount != 0 );
  396. //
  397. // make sure the new ticket cache entry won't be deleted when purged
  398. //
  399. KerbReferenceTicketCacheEntry( NewTicketCacheEntry );
  400. FRE_ASSERT( !NewTicketCacheEntry->Linked );
  401. KerbWriteLockTicketCache();
  402. ReplaceEntryList(
  403. &OldTicketCacheEntry->ListEntry.Next,
  404. &NewTicketCacheEntry->ListEntry.Next
  405. );
  406. NewTicketCacheEntry->Linked = TRUE;
  407. KerbUnlockTicketCache();
  408. KerbDereferenceTicketCacheEntry( OldTicketCacheEntry );
  409. }
  410. return Result;
  411. }
  412. //+-------------------------------------------------------------------------
  413. //
  414. // Function: KerbCacheTicket
  415. //
  416. // Synopsis: Caches a ticket in the ticket cache
  417. //
  418. // Effects: creates a cache entry
  419. //
  420. // Arguments: Ticket - The ticket to cache
  421. // KdcReply - The KdcReply corresponding to the ticket cache
  422. // KdcReplyBody -
  423. // TargetName - Name of service supplied by client
  424. // TargetDomainname -
  425. // CacheFlags -
  426. // TicketCache - The cache in which to store the ticket
  427. // not needed if ticket won't be linked.
  428. // NewCacheEntry - receives new cache entry (referenced)
  429. //
  430. // Requires:
  431. //
  432. // Returns:
  433. //
  434. // Notes: The ticket cache owner must be locked for write access
  435. //
  436. //
  437. //--------------------------------------------------------------------------
  438. NTSTATUS
  439. KerbCreateTicketCacheEntry(
  440. IN PKERB_KDC_REPLY KdcReply,
  441. IN OPTIONAL PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody,
  442. IN OPTIONAL PKERB_INTERNAL_NAME TargetName,
  443. IN OPTIONAL PUNICODE_STRING TargetDomainName,
  444. IN ULONG CacheFlags,
  445. IN OPTIONAL PKERB_TICKET_CACHE TicketCache,
  446. IN OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey,
  447. OUT PKERB_TICKET_CACHE_ENTRY * NewCacheEntry
  448. )
  449. {
  450. PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL;
  451. NTSTATUS Status = STATUS_SUCCESS;
  452. ULONG OldCacheFlags = 0;
  453. *NewCacheEntry = NULL;
  454. CacheEntry = (PKERB_TICKET_CACHE_ENTRY)
  455. KerbAllocate(sizeof(KERB_TICKET_CACHE_ENTRY));
  456. if (CacheEntry == NULL)
  457. {
  458. Status = STATUS_INSUFFICIENT_RESOURCES;
  459. goto Cleanup;
  460. }
  461. CacheEntry->ListEntry.ReferenceCount = 1;
  462. #if DBG
  463. if ( CacheEntry ) {
  464. KerbWriteLockTicketCache();
  465. InsertHeadList(
  466. &GlobalTicketList,
  467. &CacheEntry->GlobalListEntry
  468. );
  469. KerbUnlockTicketCache();
  470. }
  471. #endif
  472. InterlockedIncrement( &GlobalTicketListSize );
  473. //
  474. // Fill in the entries from the KDC reply
  475. //
  476. if (ARGUMENT_PRESENT(KdcReplyBody))
  477. {
  478. CacheEntry->TicketFlags = KerbConvertFlagsToUlong(&KdcReplyBody->flags);
  479. if (!KERB_SUCCESS(KerbDuplicateKey(
  480. &CacheEntry->SessionKey,
  481. &KdcReplyBody->session_key
  482. )))
  483. {
  484. Status = STATUS_INSUFFICIENT_RESOURCES;
  485. goto Cleanup;
  486. }
  487. if (KdcReplyBody->bit_mask & KERB_ENCRYPTED_KDC_REPLY_starttime_present)
  488. {
  489. KerbConvertGeneralizedTimeToLargeInt(
  490. &CacheEntry->StartTime,
  491. &KdcReplyBody->KERB_ENCRYPTED_KDC_REPLY_starttime,
  492. NULL
  493. );
  494. }
  495. else
  496. {
  497. KerbConvertGeneralizedTimeToLargeInt(
  498. &CacheEntry->StartTime,
  499. &KdcReplyBody->authtime,
  500. NULL
  501. );
  502. }
  503. KerbConvertGeneralizedTimeToLargeInt(
  504. &CacheEntry->EndTime,
  505. &KdcReplyBody->endtime,
  506. NULL
  507. );
  508. if (KdcReplyBody->bit_mask & KERB_ENCRYPTED_KDC_REPLY_renew_until_present)
  509. {
  510. KerbConvertGeneralizedTimeToLargeInt(
  511. &CacheEntry->RenewUntil,
  512. &KdcReplyBody->KERB_ENCRYPTED_KDC_REPLY_renew_until,
  513. NULL
  514. );
  515. }
  516. //
  517. // Check to see if the ticket has already expired
  518. //
  519. if (KerbTicketIsExpiring(CacheEntry, FALSE))
  520. {
  521. DebugLog((DEB_ERROR,"Tried to cache an already-expired ticket\n"));
  522. Status = STATUS_TIME_DIFFERENCE_AT_DC;
  523. goto Cleanup;
  524. }
  525. }
  526. //
  527. // Fill in the FullServiceName which is the domain name concatenated with
  528. // the service name.
  529. //
  530. // The full service name is domain name '\' service name.
  531. //
  532. //
  533. // Fill in the domain name and service name separately in the
  534. // cache entry, using the FullServiceName buffer.
  535. //
  536. if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString(
  537. &CacheEntry->DomainName,
  538. &KdcReply->ticket.realm
  539. )))
  540. {
  541. Status = STATUS_INSUFFICIENT_RESOURCES;
  542. goto Cleanup;
  543. }
  544. if (!KERB_SUCCESS(KerbConvertPrincipalNameToKdcName(
  545. &CacheEntry->ServiceName,
  546. &KdcReply->ticket.server_name
  547. )))
  548. {
  549. Status = STATUS_INSUFFICIENT_RESOURCES;
  550. goto Cleanup;
  551. }
  552. //
  553. // Extract the realm name from the principal name
  554. //
  555. Status = KerbExtractDomainName(
  556. &CacheEntry->TargetDomainName,
  557. CacheEntry->ServiceName,
  558. &CacheEntry->DomainName
  559. );
  560. if (!NT_SUCCESS(Status))
  561. {
  562. goto Cleanup;
  563. }
  564. //
  565. // The reply need not include the name
  566. //
  567. if (KdcReply->client_realm != NULL)
  568. {
  569. if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString(
  570. &CacheEntry->ClientDomainName,
  571. &KdcReply->client_realm)))
  572. {
  573. Status = STATUS_INSUFFICIENT_RESOURCES;
  574. goto Cleanup;
  575. }
  576. }
  577. //
  578. // Fill in the target name the client provided, which may be
  579. // different then the service name
  580. //
  581. if (ARGUMENT_PRESENT(TargetName))
  582. {
  583. Status = KerbDuplicateKdcName(
  584. &CacheEntry->TargetName,
  585. TargetName
  586. );
  587. if (!NT_SUCCESS(Status))
  588. {
  589. goto Cleanup;
  590. }
  591. }
  592. if (ARGUMENT_PRESENT(TargetDomainName))
  593. {
  594. Status = KerbDuplicateString(
  595. &CacheEntry->AltTargetDomainName,
  596. TargetDomainName
  597. );
  598. if (!NT_SUCCESS(Status))
  599. {
  600. goto Cleanup;
  601. }
  602. }
  603. //
  604. // Store the client name so we can use the right name
  605. // in later requests.
  606. //
  607. if (KdcReply->client_name.name_string != NULL)
  608. {
  609. if (!KERB_SUCCESS( KerbConvertPrincipalNameToKdcName(
  610. &CacheEntry->ClientName,
  611. &KdcReply->client_name)))
  612. {
  613. Status = STATUS_INSUFFICIENT_RESOURCES;
  614. goto Cleanup;
  615. }
  616. }
  617. if (!KERB_SUCCESS(KerbDuplicateTicket(
  618. &CacheEntry->Ticket,
  619. &KdcReply->ticket
  620. )))
  621. {
  622. Status = STATUS_INSUFFICIENT_RESOURCES;
  623. goto Cleanup;
  624. }
  625. if (ARGUMENT_PRESENT( CredentialKey ))
  626. {
  627. if (!KERB_SUCCESS(KerbDuplicateKey(
  628. &CacheEntry->CredentialKey,
  629. CredentialKey
  630. )))
  631. {
  632. Status = STATUS_INSUFFICIENT_RESOURCES;
  633. goto Cleanup;
  634. }
  635. }
  636. CacheEntry->CacheFlags = CacheFlags;
  637. //
  638. // Before we insert this ticket we want to remove any
  639. // previous instances of tickets to the same service.
  640. //
  641. if (TicketCache)
  642. {
  643. PKERB_TICKET_CACHE_ENTRY OldCacheEntry = NULL;
  644. if ((CacheFlags & (KERB_TICKET_CACHE_DELEGATION_TGT |
  645. KERB_TICKET_CACHE_PRIMARY_TGT)) != 0)
  646. {
  647. OldCacheEntry = KerbLocateTicketCacheEntryByRealm(
  648. TicketCache,
  649. NULL,
  650. CacheFlags
  651. );
  652. }
  653. else
  654. {
  655. OldCacheEntry = KerbLocateTicketCacheEntry(
  656. TicketCache,
  657. CacheEntry->ServiceName,
  658. &CacheEntry->DomainName
  659. );
  660. }
  661. if (OldCacheEntry != NULL)
  662. {
  663. OldCacheFlags = OldCacheEntry->CacheFlags;
  664. KerbRemoveTicketCacheEntry( OldCacheEntry );
  665. KerbDereferenceTicketCacheEntry( OldCacheEntry );
  666. OldCacheEntry = NULL;
  667. }
  668. //
  669. // If the old ticket was the primary TGT, mark this one as the
  670. // primary TGT as well.
  671. //
  672. CacheEntry->CacheFlags |= (OldCacheFlags &
  673. (KERB_TICKET_CACHE_DELEGATION_TGT |
  674. KERB_TICKET_CACHE_PRIMARY_TGT));
  675. //
  676. // Insert the cache entry into the cache
  677. //
  678. KerbInsertTicketCacheEntry(
  679. TicketCache,
  680. CacheEntry
  681. );
  682. //
  683. // If this is the primary TGT, schedule pre-emptive ticket renewal
  684. //
  685. if (( CacheEntry->CacheFlags & KERB_TICKET_CACHE_PRIMARY_TGT ) &&
  686. ( CacheEntry->TicketFlags & KERB_TICKET_FLAGS_renewable )) {
  687. KerbScheduleTgtRenewal( CacheEntry );
  688. }
  689. }
  690. //
  691. // Update the statistics
  692. //
  693. UpdateCacheMisses();
  694. *NewCacheEntry = CacheEntry;
  695. Cleanup:
  696. if (!NT_SUCCESS(Status))
  697. {
  698. if (NULL != CacheEntry)
  699. {
  700. KerbDereferenceTicketCacheEntry(CacheEntry);
  701. }
  702. }
  703. return(Status);
  704. }
  705. //+-------------------------------------------------------------------------
  706. //
  707. // Function: KerbDuplicateTicketCacheEntry
  708. //
  709. // Synopsis: Duplicate a ticket cache entry.
  710. //
  711. // Effects:
  712. //
  713. // Arguments:
  714. //
  715. // Requires:
  716. //
  717. // Returns: none
  718. //
  719. // Notes:
  720. //
  721. //
  722. //--------------------------------------------------------------------------
  723. NTSTATUS
  724. KerbDuplicateTicketCacheEntry(
  725. IN PKERB_TICKET_CACHE_ENTRY CacheEntry,
  726. IN OUT PKERB_TICKET_CACHE_ENTRY * NewCacheEntry
  727. )
  728. {
  729. NTSTATUS Status;
  730. PKERB_TICKET_CACHE_ENTRY LocalEntry = NULL;
  731. *NewCacheEntry = NULL;
  732. LocalEntry = (PKERB_TICKET_CACHE_ENTRY) KerbAllocate(sizeof(KERB_TICKET_CACHE_ENTRY));
  733. if ( LocalEntry == NULL )
  734. {
  735. Status = STATUS_NO_MEMORY;
  736. goto Cleanup;
  737. }
  738. RtlCopyMemory(
  739. LocalEntry,
  740. CacheEntry,
  741. sizeof(KERB_TICKET_CACHE_ENTRY)
  742. );
  743. LocalEntry->Linked = FALSE;
  744. LocalEntry->ListEntry.Next.Flink = NULL;
  745. LocalEntry->ListEntry.Next.Blink = NULL;
  746. LocalEntry->ListEntry.ReferenceCount = 1;
  747. LocalEntry->ScavengerHandle = NULL;
  748. LocalEntry->EvidenceLogonId = CacheEntry->EvidenceLogonId;
  749. #if DBG
  750. if ( LocalEntry ) {
  751. KerbWriteLockTicketCache();
  752. InsertHeadList(
  753. &GlobalTicketList,
  754. &LocalEntry->GlobalListEntry
  755. );
  756. KerbUnlockTicketCache();
  757. }
  758. #endif
  759. InterlockedIncrement( &GlobalTicketListSize );
  760. if (!KERB_SUCCESS(KerbDuplicateKey(
  761. &LocalEntry->SessionKey,
  762. &CacheEntry->SessionKey
  763. )))
  764. {
  765. Status = STATUS_NO_MEMORY;
  766. goto Cleanup;
  767. }
  768. Status = KerbDuplicateKdcName(
  769. &LocalEntry->ServiceName,
  770. CacheEntry->ServiceName
  771. );
  772. if (!NT_SUCCESS(Status))
  773. {
  774. goto Cleanup;
  775. }
  776. if ( CacheEntry->TargetName != NULL )
  777. {
  778. Status = KerbDuplicateKdcName(
  779. &LocalEntry->TargetName,
  780. CacheEntry->TargetName
  781. );
  782. if (!NT_SUCCESS(Status))
  783. {
  784. goto Cleanup;
  785. }
  786. }
  787. if ( CacheEntry->ClientName != NULL )
  788. {
  789. Status = KerbDuplicateKdcName(
  790. &LocalEntry->ClientName,
  791. CacheEntry->ClientName
  792. );
  793. if (!NT_SUCCESS(Status))
  794. {
  795. goto Cleanup;
  796. }
  797. }
  798. if ( CacheEntry->AltClientName != NULL )
  799. {
  800. Status = KerbDuplicateKdcName(
  801. &LocalEntry->AltClientName,
  802. CacheEntry->AltClientName
  803. );
  804. if (!NT_SUCCESS(Status))
  805. {
  806. goto Cleanup;
  807. }
  808. }
  809. if ( CacheEntry->DomainName.Buffer != NULL )
  810. {
  811. Status = KerbDuplicateStringEx(
  812. &LocalEntry->DomainName,
  813. &CacheEntry->DomainName,
  814. FALSE
  815. );
  816. if (!NT_SUCCESS(Status))
  817. {
  818. goto Cleanup;
  819. }
  820. }
  821. if ( CacheEntry->TargetDomainName.Buffer != NULL )
  822. {
  823. Status = KerbDuplicateStringEx(
  824. &LocalEntry->TargetDomainName,
  825. &CacheEntry->TargetDomainName,
  826. FALSE
  827. );
  828. if (!NT_SUCCESS(Status))
  829. {
  830. goto Cleanup;
  831. }
  832. }
  833. if ( CacheEntry->AltTargetDomainName.Buffer != NULL )
  834. {
  835. Status = KerbDuplicateStringEx(
  836. &LocalEntry->AltTargetDomainName,
  837. &CacheEntry->AltTargetDomainName,
  838. FALSE
  839. );
  840. if (!NT_SUCCESS(Status))
  841. {
  842. goto Cleanup;
  843. }
  844. }
  845. if ( CacheEntry->ClientDomainName.Buffer != NULL )
  846. {
  847. Status = KerbDuplicateStringEx(
  848. &LocalEntry->ClientDomainName,
  849. &CacheEntry->ClientDomainName,
  850. FALSE
  851. );
  852. if (!NT_SUCCESS(Status))
  853. {
  854. goto Cleanup;
  855. }
  856. }
  857. //
  858. // Don't need session key.
  859. //
  860. if (!KERB_SUCCESS(KerbDuplicateTicket(
  861. &LocalEntry->Ticket,
  862. &CacheEntry->Ticket
  863. )))
  864. {
  865. Status = STATUS_NO_MEMORY;
  866. goto Cleanup;
  867. }
  868. *NewCacheEntry = LocalEntry;
  869. LocalEntry = NULL;
  870. Cleanup:
  871. if ((LocalEntry != NULL) &&
  872. (!NT_SUCCESS( Status )))
  873. {
  874. KerbDereferenceTicketCacheEntry( LocalEntry );
  875. }
  876. return Status;
  877. }
  878. //+-------------------------------------------------------------------------
  879. //
  880. // Function: KerbPurgeTicketCache
  881. //
  882. // Synopsis: Purges a cache of all its tickets
  883. //
  884. // Effects: unreferences all tickets in the cache
  885. //
  886. // Arguments: Cache - Ticket cache to purge
  887. //
  888. // Requires:
  889. //
  890. // Returns: none
  891. //
  892. // Notes:
  893. //
  894. //
  895. //--------------------------------------------------------------------------
  896. VOID
  897. KerbPurgeTicketCache(
  898. IN PKERB_TICKET_CACHE Cache
  899. )
  900. {
  901. PKERB_TICKET_CACHE_ENTRY CacheEntry;
  902. while (TRUE)
  903. {
  904. KerbWriteLockTicketCache();
  905. if (IsListEmpty(&Cache->CacheEntries))
  906. {
  907. KerbUnlockTicketCache();
  908. break;
  909. }
  910. CacheEntry = CONTAINING_RECORD(
  911. Cache->CacheEntries.Flink,
  912. KERB_TICKET_CACHE_ENTRY,
  913. ListEntry.Next
  914. );
  915. //
  916. // make sure CacheEntry won't be deleted by the ticket renewal thread
  917. //
  918. KerbReferenceTicketCacheEntry(CacheEntry);
  919. KerbUnlockTicketCache();
  920. KerbRemoveTicketCacheEntry(
  921. CacheEntry
  922. );
  923. KerbDereferenceTicketCacheEntry(CacheEntry);
  924. }
  925. }
  926. //+-------------------------------------------------------------------------
  927. //
  928. // Function: KerbLocateTicketCacheEntry
  929. //
  930. // Synopsis: References a ticket cache entry by name
  931. //
  932. // Effects: Increments the reference count on the ticket cache entry
  933. //
  934. // Arguments: TicketCache - the ticket cache to search
  935. // FullServiceName - Optionally contains full service name
  936. // of target, including domain name. If it is NULL,
  937. // then the first element in the list is returned.
  938. // RealmName - Realm of service name. If length is zero, is not
  939. // used for comparison.
  940. //
  941. // Requires:
  942. //
  943. // Returns: The referenced cache entry or NULL if it was not found.
  944. //
  945. // Notes: If an invalid entry is found it may be dereferenced
  946. //
  947. //
  948. //--------------------------------------------------------------------------
  949. PKERB_TICKET_CACHE_ENTRY
  950. KerbLocateTicketCacheEntry(
  951. IN PKERB_TICKET_CACHE TicketCache,
  952. IN PKERB_INTERNAL_NAME FullServiceName,
  953. IN PUNICODE_STRING RealmName
  954. )
  955. {
  956. PLIST_ENTRY ListEntry;
  957. PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL;
  958. BOOLEAN Found = FALSE;
  959. BOOLEAN Remove = FALSE;
  960. KerbReadLockTicketCache();
  961. //
  962. // Go through the ticket cache looking for the correct entry
  963. //
  964. for (ListEntry = TicketCache->CacheEntries.Flink ;
  965. ListEntry != &TicketCache->CacheEntries ;
  966. ListEntry = ListEntry->Flink )
  967. {
  968. CacheEntry = CONTAINING_RECORD(ListEntry, KERB_TICKET_CACHE_ENTRY, ListEntry.Next);
  969. if (!ARGUMENT_PRESENT(FullServiceName) ||
  970. KerbEqualKdcNames(
  971. CacheEntry->ServiceName,
  972. FullServiceName
  973. ) ||
  974. ((CacheEntry->TargetName != NULL) &&
  975. KerbEqualKdcNames(
  976. CacheEntry->TargetName,
  977. FullServiceName
  978. ) ) )
  979. {
  980. //
  981. // Make sure they are principals in the same realm
  982. //
  983. if ((RealmName->Length != 0) &&
  984. !RtlEqualUnicodeString(
  985. RealmName,
  986. &CacheEntry->DomainName,
  987. TRUE // case insensitive
  988. ) &&
  989. !RtlEqualUnicodeString(
  990. RealmName,
  991. &CacheEntry->AltTargetDomainName,
  992. TRUE // case insensitive
  993. ))
  994. {
  995. continue;
  996. }
  997. //
  998. // We don't want to return any special tickets.
  999. //
  1000. if (CacheEntry->CacheFlags != 0)
  1001. {
  1002. continue;
  1003. }
  1004. //
  1005. // Check to see if the entry has expired, or is not yet valid. If it has, just
  1006. // remove it now.
  1007. //
  1008. if (KerbTicketIsExpiring(CacheEntry, FALSE ))
  1009. {
  1010. Remove = TRUE;
  1011. Found = FALSE;
  1012. }
  1013. else
  1014. {
  1015. Found = TRUE;
  1016. }
  1017. KerbReferenceTicketCacheEntry(CacheEntry);
  1018. break;
  1019. }
  1020. }
  1021. KerbUnlockTicketCache();
  1022. if (Remove)
  1023. {
  1024. KerbRemoveTicketCacheEntry(CacheEntry);
  1025. KerbDereferenceTicketCacheEntry(CacheEntry);
  1026. }
  1027. if (!Found)
  1028. {
  1029. CacheEntry = NULL;
  1030. }
  1031. //
  1032. // Update the statistics
  1033. //
  1034. if (Found)
  1035. {
  1036. UpdateCacheHits();
  1037. }
  1038. else
  1039. {
  1040. UpdateCacheMisses();
  1041. }
  1042. return(CacheEntry);
  1043. }
  1044. //+-------------------------------------------------------------------------
  1045. //
  1046. // Function: KerbLocateTicketCacheEntryByRealm
  1047. //
  1048. // Synopsis: References a ticket cache entry by realm name. This is used
  1049. // only for the cache of TGTs
  1050. //
  1051. // Effects: Increments the reference count on the ticket cache entry
  1052. //
  1053. // Arguments: TicketCache - the ticket cache to search
  1054. // RealmName - Optioanl realm of ticket - if NULL looks for
  1055. // initial ticket
  1056. // RequiredFlags - any ticket cache flags the return ticket must
  1057. // have. If the caller asks for a primary TGT, then this
  1058. // API won't do expiration checking.
  1059. //
  1060. // Requires:
  1061. //
  1062. // Returns: The referenced cache entry or NULL if it was not found.
  1063. //
  1064. // Notes: If an invalid entry is found it may be dereferenced
  1065. // We we weren't given a RealmName, then we need to look
  1066. // through the whole list for the ticket.
  1067. //
  1068. //
  1069. //--------------------------------------------------------------------------
  1070. PKERB_TICKET_CACHE_ENTRY
  1071. KerbLocateTicketCacheEntryByRealm(
  1072. IN PKERB_TICKET_CACHE TicketCache,
  1073. IN PUNICODE_STRING RealmName,
  1074. IN ULONG RequiredFlags
  1075. )
  1076. {
  1077. PLIST_ENTRY ListEntry;
  1078. PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL;
  1079. BOOLEAN Found = FALSE, NoRealmSupplied = FALSE;
  1080. BOOLEAN Remove = FALSE;
  1081. NoRealmSupplied = ((RealmName == NULL) || (RealmName->Length == 0));
  1082. KerbReadLockTicketCache();
  1083. //
  1084. // Go through the ticket cache looking for the correct entry
  1085. //
  1086. for (ListEntry = TicketCache->CacheEntries.Flink ;
  1087. ListEntry != &TicketCache->CacheEntries ;
  1088. ListEntry = ListEntry->Flink )
  1089. {
  1090. CacheEntry = CONTAINING_RECORD(ListEntry, KERB_TICKET_CACHE_ENTRY, ListEntry.Next);
  1091. //
  1092. // Match if the caller supplied no realm name, the realm matches the
  1093. // target domain name, or it matches the alt target domain name
  1094. //
  1095. if (((RealmName == NULL) || (RealmName->Length == 0)) ||
  1096. ((CacheEntry->TargetDomainName.Length != 0) &&
  1097. RtlEqualUnicodeString(
  1098. &CacheEntry->TargetDomainName,
  1099. RealmName,
  1100. TRUE
  1101. )) ||
  1102. ((CacheEntry->AltTargetDomainName.Length != 0) &&
  1103. RtlEqualUnicodeString(
  1104. &CacheEntry->AltTargetDomainName,
  1105. RealmName,
  1106. TRUE
  1107. )) )
  1108. {
  1109. //
  1110. // Check the required flags are set.
  1111. //
  1112. if (((CacheEntry->CacheFlags & RequiredFlags) != RequiredFlags) ||
  1113. (((CacheEntry->CacheFlags & KERB_TICKET_CACHE_DELEGATION_TGT) != 0) &&
  1114. ((RequiredFlags & KERB_TICKET_CACHE_DELEGATION_TGT) == 0)))
  1115. {
  1116. Found = FALSE;
  1117. //
  1118. // We need to continue looking
  1119. //
  1120. continue;
  1121. }
  1122. //
  1123. // Check to see if the entry has expired. If it has, just
  1124. // remove it now.
  1125. //
  1126. if (KerbTicketIsExpiring(CacheEntry, FALSE ))
  1127. {
  1128. //
  1129. // if this is not the primary TGT, go ahead and remove it. We
  1130. // want to save the primary TGT so we can renew it.
  1131. //
  1132. if ((CacheEntry->CacheFlags & KERB_TICKET_CACHE_PRIMARY_TGT) == 0)
  1133. {
  1134. Remove = TRUE;
  1135. Found = FALSE;
  1136. }
  1137. else
  1138. {
  1139. //
  1140. // If the caller was asking for the primary TGT,
  1141. // return it whether or not it expired.
  1142. //
  1143. if ((RequiredFlags & KERB_TICKET_CACHE_PRIMARY_TGT) != 0 )
  1144. {
  1145. Found = TRUE;
  1146. }
  1147. else
  1148. {
  1149. Found = FALSE;
  1150. }
  1151. }
  1152. if ( Remove || Found )
  1153. {
  1154. KerbReferenceTicketCacheEntry(CacheEntry);
  1155. }
  1156. }
  1157. else
  1158. {
  1159. KerbReferenceTicketCacheEntry(CacheEntry);
  1160. Found = TRUE;
  1161. }
  1162. break;
  1163. }
  1164. }
  1165. KerbUnlockTicketCache();
  1166. if (Remove)
  1167. {
  1168. KerbRemoveTicketCacheEntry(CacheEntry);
  1169. KerbDereferenceTicketCacheEntry(CacheEntry);
  1170. }
  1171. if (!Found)
  1172. {
  1173. CacheEntry = NULL;
  1174. }
  1175. //
  1176. // Update the statistics
  1177. //
  1178. if (Found)
  1179. {
  1180. UpdateCacheHits();
  1181. }
  1182. else
  1183. {
  1184. UpdateCacheMisses();
  1185. }
  1186. return(CacheEntry);
  1187. }
  1188. //+-------------------------------------------------------------------------
  1189. //
  1190. // Function: KerbTicketIsExpiring
  1191. //
  1192. // Synopsis: Check if a ticket is expiring
  1193. //
  1194. // Effects:
  1195. //
  1196. // Arguments: CacheEntry - Entry to check
  1197. // AllowSkew - Expire ticket that aren't outside of skew of
  1198. // expiring
  1199. //
  1200. // Requires:
  1201. //
  1202. // Returns:
  1203. //
  1204. // Notes: Ticket cache lock must be held
  1205. //
  1206. //
  1207. //--------------------------------------------------------------------------
  1208. BOOLEAN
  1209. KerbTicketIsExpiring(
  1210. IN PKERB_TICKET_CACHE_ENTRY CacheEntry,
  1211. IN BOOLEAN AllowSkew
  1212. )
  1213. {
  1214. TimeStamp CutoffTime;
  1215. GetSystemTimeAsFileTime((PFILETIME) &CutoffTime);
  1216. //
  1217. // We want to make sure we have at least skewtime left on the ticket
  1218. //
  1219. if (AllowSkew)
  1220. {
  1221. KerbSetTime(&CutoffTime, KerbGetTime(CutoffTime) + KerbGetTime(KerbGlobalSkewTime));
  1222. }
  1223. //
  1224. // Adjust for server skew time.
  1225. //
  1226. KerbSetTime(&CutoffTime, KerbGetTime(CutoffTime) + KerbGetTime(CacheEntry->TimeSkew));
  1227. if (KerbGetTime(CacheEntry->EndTime) < KerbGetTime(CutoffTime))
  1228. {
  1229. return(TRUE);
  1230. }
  1231. else
  1232. {
  1233. return(FALSE);
  1234. }
  1235. }
  1236. //+-------------------------------------------------------------------------
  1237. //
  1238. // Function: KerbAgeTickets
  1239. //
  1240. // Synopsis: Removes stale, old tickets from caches in a ticket cache.
  1241. //
  1242. // Effects:
  1243. //
  1244. // Arguments:
  1245. //
  1246. // Requires:
  1247. //
  1248. // Returns:
  1249. //
  1250. // Notes:
  1251. //
  1252. //
  1253. //--------------------------------------------------------------------------
  1254. VOID
  1255. KerbAgeTickets(
  1256. PKERB_TICKET_CACHE TicketCache
  1257. )
  1258. {
  1259. PKERB_TICKET_CACHE_ENTRY CacheEntry = NULL;
  1260. PLIST_ENTRY ListEntry;
  1261. KerbWriteLockTicketCache();
  1262. for (ListEntry = TicketCache->CacheEntries.Flink ;
  1263. ListEntry != &TicketCache->CacheEntries ;
  1264. ListEntry = ListEntry->Flink )
  1265. {
  1266. CacheEntry = CONTAINING_RECORD(ListEntry, KERB_TICKET_CACHE_ENTRY, ListEntry.Next);
  1267. if (KerbTicketIsExpiring(CacheEntry, FALSE ))
  1268. {
  1269. D_DebugLog((DEB_TRACE, "Aging ticket %p\n", CacheEntry ));
  1270. ListEntry = ListEntry->Blink;
  1271. KerbRemoveTicketCacheEntry( CacheEntry );
  1272. }
  1273. }
  1274. GetSystemTimeAsFileTime(( PFILETIME )&TicketCache->LastCleanup );
  1275. KerbUnlockTicketCache();
  1276. }
  1277. //+-------------------------------------------------------------------------
  1278. //
  1279. // Function: KerbAgeTicketsForLogonSession
  1280. //
  1281. // Synopsis: Removes stale, old tickets from a logon session
  1282. //
  1283. // Effects:
  1284. //
  1285. // Arguments:
  1286. //
  1287. // Requires:
  1288. //
  1289. // Returns:
  1290. //
  1291. // Notes:
  1292. //
  1293. //
  1294. //--------------------------------------------------------------------------
  1295. VOID
  1296. KerbAgeTicketsForLogonSession(
  1297. PKERB_LOGON_SESSION LogonSession
  1298. )
  1299. {
  1300. //
  1301. // Don't age TGT cache. Just S4U and service ticket caches.
  1302. //
  1303. KerbAgeTickets( &LogonSession->PrimaryCredentials.S4UTicketCache );
  1304. KerbAgeTickets( &LogonSession->PrimaryCredentials.ServerTicketCache );
  1305. }
  1306. //+-------------------------------------------------------------------------
  1307. //
  1308. // Function: KerbSetTicketCacheEntryTarget
  1309. //
  1310. // Synopsis: Sets target name for a cache entry
  1311. //
  1312. // Effects:
  1313. //
  1314. // Arguments:
  1315. //
  1316. // Requires:
  1317. //
  1318. // Returns:
  1319. //
  1320. // Notes:
  1321. //
  1322. //
  1323. //--------------------------------------------------------------------------
  1324. VOID
  1325. KerbSetTicketCacheEntryTarget(
  1326. IN PUNICODE_STRING TargetName,
  1327. IN PKERB_TICKET_CACHE_ENTRY TicketCacheEntry
  1328. )
  1329. {
  1330. KerbWriteLockTicketCache();
  1331. KerbFreeString(&TicketCacheEntry->AltTargetDomainName);
  1332. KerbDuplicateString(
  1333. &TicketCacheEntry->AltTargetDomainName,
  1334. TargetName
  1335. );
  1336. KerbUnlockTicketCache();
  1337. }
  1338. void
  1339. KerbScheduleTgtRenewal(
  1340. IN KERB_TICKET_CACHE_ENTRY * CacheEntry
  1341. )
  1342. {
  1343. NTSTATUS Status;
  1344. LONGLONG CurrentTime;
  1345. LONGLONG RenewalTime;
  1346. DWORD TgtRenewalTime = KerbGlobalTgtRenewalTime;
  1347. #if DBG
  1348. FILETIME ft;
  1349. SYSTEMTIME st;
  1350. #endif
  1351. //
  1352. // The only condition on which the TGT should be renewed
  1353. //
  1354. DsysAssert(
  1355. CacheEntry &&
  1356. ( CacheEntry->CacheFlags & KERB_TICKET_CACHE_PRIMARY_TGT ) &&
  1357. ( CacheEntry->TicketFlags & KERB_TICKET_FLAGS_renewable )
  1358. );
  1359. DsysAssert( CacheEntry->ScavengerHandle == NULL );
  1360. //
  1361. // A renewal time of '0 before expiration' means renewing is turned off
  1362. //
  1363. if ( TgtRenewalTime == 0 ) {
  1364. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Not scheduling ticket renewal for %p -- \n", CacheEntry));
  1365. return;
  1366. }
  1367. //
  1368. // The scavenger maintains a reference on the cache entry
  1369. //
  1370. KerbReferenceTicketCacheEntry( CacheEntry );
  1371. #if DBG
  1372. FileTimeToLocalFileTime(( PFILETIME )&CacheEntry->EndTime.QuadPart, &ft );
  1373. FileTimeToSystemTime( &ft, &st );
  1374. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "The end time on entry %p is %02d:%02d:%02d\n", CacheEntry, (ULONG)st.wHour, (ULONG)st.wMinute, (ULONG)st.wSecond));
  1375. #endif
  1376. //
  1377. // Compute how far in the future, in milliseconds, the renewal
  1378. // should be attempted
  1379. //
  1380. GetSystemTimeAsFileTime(( PFILETIME )&CurrentTime );
  1381. #if DBG
  1382. FileTimeToLocalFileTime(( PFILETIME )&CurrentTime, &ft );
  1383. FileTimeToSystemTime( &ft, &st );
  1384. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "The current time is %02d:%02d:%02d\n", (ULONG)st.wHour, (ULONG)st.wMinute, (ULONG)st.wSecond));
  1385. #endif
  1386. //
  1387. // Convert renewal time from seconds to 100 ns intervals
  1388. //
  1389. RenewalTime = CacheEntry->EndTime.QuadPart - ((LONGLONG)TgtRenewalTime) * 1000 * 1000 * 10;
  1390. #if DBG
  1391. FileTimeToLocalFileTime(( PFILETIME )&RenewalTime, &ft );
  1392. FileTimeToSystemTime( &ft, &st );
  1393. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Will try to schedule ticket renewal of %p for %02d:%02d:%02d\n", CacheEntry, (ULONG)st.wHour, (ULONG)st.wMinute, (ULONG)st.wSecond));
  1394. #endif
  1395. if ( RenewalTime <= CurrentTime ) {
  1396. Status = STATUS_INVALID_PARAMETER;
  1397. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Not scheduling ticket renewal for %p -- too late\n", CacheEntry));
  1398. } else {
  1399. //
  1400. // Scavenger tasks are timed in milliseconds
  1401. //
  1402. LONG Interval = ( LONG )(( RenewalTime - CurrentTime ) / ( 10 * 1000 ));
  1403. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Ticket %p will be renewed %d seconds from now\n", CacheEntry, Interval / 1000 ));
  1404. if ( Interval > 0 ) {
  1405. Status = KerbAddScavengerTask(
  1406. FALSE,
  1407. Interval,
  1408. 0, // no special processing flags
  1409. KerbTgtRenewalTrigger,
  1410. KerbTgtRenewalReaper,
  1411. CacheEntry,
  1412. &CacheEntry->ScavengerHandle
  1413. );
  1414. } else {
  1415. Status = STATUS_UNSUCCESSFUL;
  1416. }
  1417. }
  1418. if ( !NT_SUCCESS( Status )) {
  1419. KerbDereferenceTicketCacheEntry( CacheEntry );
  1420. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Failed to schedule ticket renewal for %p (0x%x)\n", CacheEntry, Status));
  1421. }
  1422. }
  1423. //+-------------------------------------------------------------------------
  1424. //
  1425. // Function: KerbTgtRenewalTrigger
  1426. //
  1427. // Synopsis: Handles a TGT renewal event
  1428. //
  1429. // Effects:
  1430. //
  1431. // Arguments: TaskHandle - handle to the task (for rescheduling, etc.)
  1432. // TaskItem - task context
  1433. //
  1434. // Requires:
  1435. //
  1436. // Returns: Nothing
  1437. //
  1438. // Notes:
  1439. //
  1440. //
  1441. //--------------------------------------------------------------------------
  1442. void
  1443. KerbTgtRenewalTrigger(
  1444. void * TaskHandle,
  1445. void * TaskItem
  1446. )
  1447. {
  1448. NTSTATUS Status;
  1449. KERB_TICKET_CACHE_ENTRY * CacheEntry = ( KERB_TICKET_CACHE_ENTRY * )TaskItem;
  1450. BOOLEAN TicketCacheLocked = FALSE;
  1451. ULONG CacheFlags;
  1452. UNICODE_STRING ServiceRealm = NULL_UNICODE_STRING;
  1453. PKERB_INTERNAL_NAME ServiceName = NULL;
  1454. PKERB_KDC_REPLY KdcReply = NULL;
  1455. PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL;
  1456. ULONG RetryFlags = 0;
  1457. KERB_TICKET_CACHE_ENTRY * NewTicket;
  1458. DsysAssert( CacheEntry );
  1459. //
  1460. // Optimization: do not attempt to renew unlinked entries
  1461. //
  1462. if ( !CacheEntry->Linked ) {
  1463. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Not renewing %p -- already unlinked\n", CacheEntry));
  1464. return;
  1465. }
  1466. //
  1467. // Copy the names out of the input structures so we can
  1468. // unlock the structures while going over the network.
  1469. //
  1470. DsysAssert( !TicketCacheLocked );
  1471. KerbWriteLockTicketCache();
  1472. TicketCacheLocked = TRUE;
  1473. //
  1474. // No scavenger handle means that this entry has been canceled
  1475. // and does not need renewal
  1476. //
  1477. if ( CacheEntry->ScavengerHandle == NULL ) {
  1478. Status = STATUS_UNSUCCESSFUL;
  1479. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Not renewing %p -- previously canceled\n", CacheEntry));
  1480. goto Cleanup;
  1481. } else {
  1482. CacheEntry->ScavengerHandle = NULL;
  1483. }
  1484. //
  1485. // If the renew time is not much bigger than the end time, don't bother
  1486. // renewing
  1487. //
  1488. if ( KerbGetTime( CacheEntry->EndTime ) +
  1489. KerbGetTime( KerbGlobalSkewTime ) >=
  1490. KerbGetTime( CacheEntry->RenewUntil )) {
  1491. Status = STATUS_UNSUCCESSFUL;
  1492. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Not renewing %p -- not much time left\n", CacheEntry));
  1493. goto Cleanup;
  1494. }
  1495. if ( !( CacheEntry->TicketFlags & KERB_TICKET_FLAGS_renewable )) {
  1496. Status = STATUS_ILLEGAL_FUNCTION;
  1497. D_DebugLog((DEB_ERROR, "Trying to renew a non renewable ticket to "));
  1498. D_KerbPrintKdcName((DEB_ERROR, CacheEntry->ServiceName));
  1499. D_DebugLog((DEB_ERROR, " %ws, line %d\n", THIS_FILE, __LINE__));
  1500. goto Cleanup;
  1501. }
  1502. CacheFlags = CacheEntry->CacheFlags;
  1503. Status = KerbDuplicateString(
  1504. &ServiceRealm,
  1505. &CacheEntry->DomainName
  1506. );
  1507. if ( !NT_SUCCESS( Status )) {
  1508. goto Cleanup;
  1509. }
  1510. Status = KerbDuplicateKdcName(
  1511. &ServiceName,
  1512. CacheEntry->ServiceName
  1513. );
  1514. if ( !NT_SUCCESS( Status )) {
  1515. goto Cleanup;
  1516. }
  1517. DsysAssert( TicketCacheLocked );
  1518. KerbUnlockTicketCache();
  1519. TicketCacheLocked = FALSE;
  1520. Status = KerbGetTgsTicket(
  1521. &ServiceRealm,
  1522. CacheEntry,
  1523. ServiceName,
  1524. FALSE,
  1525. KERB_KDC_OPTIONS_renew,
  1526. 0, // no encryption type
  1527. NULL, // no authorization data
  1528. NULL, // no pa data
  1529. NULL, // no tgt reply
  1530. NULL, // no evidence ticket
  1531. NULL, // let kdc determine end time
  1532. &KdcReply,
  1533. &KdcReplyBody,
  1534. &RetryFlags
  1535. );
  1536. if ( !NT_SUCCESS( Status )) {
  1537. DebugLog((DEB_WARN, "Failed to get TGS ticket for service 0x%x ", Status ));
  1538. KerbPrintKdcName(DEB_WARN, ServiceName);
  1539. DebugLog((DEB_WARN, " %ws, line %d\n", THIS_FILE, __LINE__));
  1540. goto Cleanup;
  1541. }
  1542. Status = KerbCreateTicketCacheEntry(
  1543. KdcReply,
  1544. KdcReplyBody,
  1545. ServiceName,
  1546. &ServiceRealm,
  1547. CacheFlags,
  1548. NULL,
  1549. &CacheEntry->CredentialKey,
  1550. &NewTicket
  1551. );
  1552. if ( !NT_SUCCESS( Status )) {
  1553. DebugLog((DEB_WARN,"Failed to create a ticket cache entry for service 0x%x ", Status ));
  1554. KerbPrintKdcName(DEB_WARN, ServiceName);
  1555. DebugLog((DEB_WARN, " %ws, line %d\n", THIS_FILE, __LINE__));
  1556. goto Cleanup;
  1557. }
  1558. D_DebugLog((DEB_TRACE_TKT_RENEWAL, "Entry %p renewed successfully. New entry is %p\n", CacheEntry, NewTicket));
  1559. if (( NewTicket->CacheFlags & KERB_TICKET_CACHE_PRIMARY_TGT ) &&
  1560. ( NewTicket->TicketFlags & KERB_TICKET_FLAGS_renewable )) {
  1561. if ( KerbReplaceTicketCacheEntry( CacheEntry, NewTicket )) {
  1562. KerbScheduleTgtRenewal( NewTicket );
  1563. }
  1564. }
  1565. KerbDereferenceTicketCacheEntry( NewTicket );
  1566. Cleanup:
  1567. if ( TicketCacheLocked ) {
  1568. KerbUnlockTicketCache();
  1569. }
  1570. KerbFreeTgsReply( KdcReply );
  1571. KerbFreeKdcReplyBody( KdcReplyBody );
  1572. KerbFreeKdcName( &ServiceName );
  1573. KerbFreeString( &ServiceRealm );
  1574. return;
  1575. }
  1576. //+-------------------------------------------------------------------------
  1577. //
  1578. // Function: KerbTgtRenewalReaper
  1579. //
  1580. // Synopsis: Destroys a ticket renewal task
  1581. //
  1582. // Effects:
  1583. //
  1584. // Arguments: TaskItem - cache entry to destroy
  1585. //
  1586. // Requires:
  1587. //
  1588. // Returns: Nothing
  1589. //
  1590. // Notes:
  1591. //
  1592. //
  1593. //--------------------------------------------------------------------------
  1594. void
  1595. KerbTgtRenewalReaper(
  1596. void * TaskItem
  1597. )
  1598. {
  1599. KERB_TICKET_CACHE_ENTRY * CacheEntry = ( KERB_TICKET_CACHE_ENTRY * )TaskItem;
  1600. if ( CacheEntry ) {
  1601. KerbWriteLockTicketCache();
  1602. CacheEntry->ScavengerHandle = NULL;
  1603. KerbUnlockTicketCache();
  1604. KerbDereferenceTicketCacheEntry( CacheEntry );
  1605. }
  1606. }
  1607. //+-------------------------------------------------------------------------
  1608. //
  1609. // Function: KerbScheduleTicketCleanup
  1610. //
  1611. // Synopsis: Schedules garbage collection for tickets.
  1612. //
  1613. // Effects:
  1614. //
  1615. // Arguments: TaskItem - cache entry to destroy
  1616. //
  1617. // Requires:
  1618. //
  1619. // Returns: Nothing
  1620. //
  1621. // Notes:
  1622. //
  1623. //
  1624. //--
  1625. void
  1626. KerbTicketScavenger(
  1627. void * TaskHandle,
  1628. void * TaskItem
  1629. )
  1630. {
  1631. PKERB_LOGON_SESSION *SessionsToAge = NULL;
  1632. PKERB_LOGON_SESSION LogonSession = NULL;
  1633. PLIST_ENTRY ListEntry;
  1634. ULONG Count = 0;
  1635. LUID SystemLuid = SYSTEM_LUID;
  1636. LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
  1637. LONG Max = KerbGlobalMaxTickets;
  1638. LONG Threshold = KerbGlobalMaxTickets / 2;
  1639. D_DebugLog((DEB_TRACE, "Triggering ticket scavenger\n"));
  1640. //
  1641. // No reason to burn cycles w/ < XXXX tickets
  1642. //
  1643. if ( GlobalTicketListSize < Max )
  1644. {
  1645. return;
  1646. }
  1647. //
  1648. // Ok - now we have some work to do
  1649. //
  1650. //
  1651. // First, cleanup the expired tickets for the 3e7 and 3e4 logon sessions
  1652. //
  1653. LogonSession = KerbReferenceLogonSession( &SystemLuid, FALSE );
  1654. if ( LogonSession )
  1655. {
  1656. KerbAgeTicketsForLogonSession( LogonSession );
  1657. KerbDereferenceLogonSession( LogonSession );
  1658. }
  1659. LogonSession = KerbReferenceLogonSession( &NetworkServiceLuid, FALSE );
  1660. if ( LogonSession )
  1661. {
  1662. KerbAgeTicketsForLogonSession( LogonSession );
  1663. KerbDereferenceLogonSession( LogonSession );
  1664. }
  1665. if ( GlobalTicketListSize < Threshold )
  1666. {
  1667. return;
  1668. }
  1669. //
  1670. // Now, iterate through the logon sessions, cleaning up tickets until
  1671. // we've dropped below the threshhold, starting at the tail of the list
  1672. //
  1673. SafeAllocaAllocate( SessionsToAge, ( sizeof(PKERB_LOGON_SESSION) * ScavengedSessions ));
  1674. if (SessionsToAge == NULL )
  1675. {
  1676. return;
  1677. }
  1678. KerbLockList(&KerbLogonSessionList);
  1679. for ( ListEntry = KerbLogonSessionList.List.Blink ;
  1680. (( ListEntry->Blink != &KerbLogonSessionList.List ) && ( Count < ScavengedSessions )) ;
  1681. ListEntry = ListEntry->Blink, Count++ )
  1682. {
  1683. SessionsToAge[Count] = CONTAINING_RECORD(ListEntry, KERB_LOGON_SESSION, ListEntry.Next);
  1684. SessionsToAge[Count]->ListEntry.ReferenceCount++;
  1685. }
  1686. KerbUnlockList(&KerbLogonSessionList);
  1687. for ( ULONG i = 0; i < Count ; i++ )
  1688. {
  1689. KerbAgeTicketsForLogonSession( SessionsToAge[i] );
  1690. KerbDereferenceLogonSession( SessionsToAge[i] );
  1691. }
  1692. SafeAllocaFree( SessionsToAge );
  1693. if ( GlobalTicketListSize < Threshold )
  1694. {
  1695. return;
  1696. }
  1697. //
  1698. // Hmm.. This can grow unbounded - is this a problem?
  1699. //
  1700. if ( Count == (ScavengedSessions - 1))
  1701. {
  1702. ScavengedSessions += 30;
  1703. }
  1704. D_DebugLog((DEB_TRACE, "Dang it - we're hosed - up the number of logon sessions to %x\n", ScavengedSessions));
  1705. }