* cc_mslsa.c:
authorJeffrey Altman <jaltman@secure-endpoints.com>
Wed, 30 Jun 2004 05:00:25 +0000 (05:00 +0000)
committerJeffrey Altman <jaltman@secure-endpoints.com>
Wed, 30 Jun 2004 05:00:25 +0000 (05:00 +0000)
    - is_windows_2000() indicates the OS is Windows 2000 or higher
    - is_windows_xp() indicates the OS is Windows XP or higher which
      indicates that PKERB_QUERY_TKT_CACHE_EX_RESPONSE and
      PKERB_TICKET_CACHE_INFO_EX are available.
    - does_retrieve_ticket_cache_ticket() checks to see if a Microsoft
      private fix is available which adds a new Cache Flag,
      KERB_RETRIEVE_TICKET_CACHE_TICKET, which when set causes the
      requested ticket to be stored in the LSA cache even when the
      TicketFlags and EncType are not set to 0.
    - KerbExternalTicketMatch() is a test to determine if two
      Microsoft External Tickets are identical
    + use the KerbQueryTicketCacheExMessage LSA call on XP or higher
    + specify the KERB_RETRIEVE_TICKET_CACHE_TICKET flag when it is
      available
    = The combination of both + items will cause the ClientRealm
      to be displayed properly for all cross realm tickets obtained
      via the MSLSA

ticket: new

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@16528 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/krb5/ccache/ChangeLog
src/lib/krb5/ccache/cc_mslsa.c

index a662900a54be7570ae567be984a83c308d0216a4..ac653ace33f6ff47544b9cff038a549a32266167 100644 (file)
@@ -1,3 +1,25 @@
+2004-06-29  Jeffrey Altman <jaltman@mit.edu>
+
+    * cc_mslsa.c: 
+    - is_windows_2000() indicates the OS is Windows 2000 or higher
+    - is_windows_xp() indicates the OS is Windows XP or higher which
+      indicates that PKERB_QUERY_TKT_CACHE_EX_RESPONSE and 
+      PKERB_TICKET_CACHE_INFO_EX are available.
+    - does_retrieve_ticket_cache_ticket() checks to see if a Microsoft
+      private fix is available which adds a new Cache Flag,
+      KERB_RETRIEVE_TICKET_CACHE_TICKET, which when set causes the 
+      requested ticket to be stored in the LSA cache even when the
+      TicketFlags and EncType are not set to 0.
+    - KerbExternalTicketMatch() is a test to determine if two 
+      Microsoft External Tickets are identical
+    + use the KerbQueryTicketCacheExMessage LSA call on XP or higher
+    + specify the KERB_RETRIEVE_TICKET_CACHE_TICKET flag when it is
+      available
+    = The combination of both + items will cause the ClientRealm
+      to be displayed properly for all cross realm tickets obtained
+      via the MSLSA
+    
+
 2004-06-29  Ken Raeburn  <raeburn@mit.edu>
 
        * cc_memory.c (krb5_mcc_free): Don't destroy the mutex here.
index f57b397e62b4018f47611c7b07848b0aa47fcd69..3ab99cfec9a3847ec55e8077e4c0fd6e2f66b627 100644 (file)
 #define SECURITY_WIN32
 #include <security.h>
 #include <ntsecapi.h>
+#include <ntstatus.h>
 
 #define MAX_MSG_SIZE 256
 #define MAX_MSPRINC_SIZE 1024
 
-static BOOL IsWindows2000 (void)
+static BOOL 
+is_windows_2000 (void)
 {
    static BOOL fChecked = FALSE;
    static BOOL fIsWin2K = FALSE;
@@ -91,6 +93,41 @@ static BOOL IsWindows2000 (void)
    return fIsWin2K;
 }
 
+static BOOL 
+is_windows_xp (void)
+{
+   static BOOL fChecked = FALSE;
+   static BOOL fIsWinXP = FALSE;
+
+   if (!fChecked)
+   {
+       OSVERSIONINFO Version;
+       fChecked = TRUE;
+
+       memset (&Version, 0x00, sizeof(Version));
+       Version.dwOSVersionInfoSize = sizeof(Version);
+
+       if (GetVersionEx (&Version))
+       {
+           if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT &&
+                (Version.dwMajorVersion >= 5 ||
+                 Version.dwMajorVersion == 5 && Version.dwMinorVersion >= 1) )
+               fIsWinXP = TRUE;
+       }
+   }
+
+   return fIsWinXP;
+}
+
+/* This flag is only supported by versions of Windows which have obtained
+ * a code change from Microsoft.   When the code change is installed,
+ * setting this flag will cause all retrieved credentials to be stored 
+ * in the LSA cache.
+ */
+#ifndef KERB_RETRIEVE_TICKET_CACHE_TICKET
+#define KERB_RETRIEVE_TICKET_CACHE_TICKET  0x20
+#endif
+
 static VOID
 ShowWinError(LPSTR szAPI, DWORD dwError)
 {
@@ -136,8 +173,6 @@ ShowLsaError(LPSTR szAPI, NTSTATUS Status)
     ShowWinError(szAPI, LsaNtStatusToWinError(Status));
 }
 
-
-
 static BOOL
 WINAPI
 UnicodeToANSI(LPTSTR lpInputString, LPSTR lpszOutputString, int nOutStringLen)
@@ -198,7 +233,8 @@ MITPrincToMSPrinc(krb5_context context, krb5_principal principal, UNICODE_STRING
 }
 
 static void
-MSPrincToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context, krb5_principal *principal)
+MSPrincToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context, 
+                  krb5_principal *principal)
 {
     WCHAR princbuf[512],tmpbuf[128];
     char aname[512];
@@ -265,7 +301,8 @@ MSTicketToMITTicket(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_d
     tmpdata.length=msticket->EncodedTicketSize;
     tmpdata.data=msticket->EncodedTicket;
 
-    // TODO: fix this up a little. this is ugly and will break krb5_free_data()
+    // this is ugly and will break krb5_free_data() 
+    // now that this is being done within the library it won't break krb5_free_data()
     krb5_copy_data(context, &tmpdata, &newdata);
     memcpy(ticket, newdata, sizeof(krb5_data));
 }
@@ -313,7 +350,7 @@ PreserveInitialTicketIdentity(void)
 
 
 static void
-MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING InitialTicketDomain
+MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING ClientRealm
                 krb5_context context, krb5_creds *creds)
 {
     WCHAR wrealm[128];
@@ -321,13 +358,8 @@ MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING InitialTicketDoma
     creds->magic=KV5M_CREDS;
 
     // construct Client Principal
-    if ( PreserveInitialTicketIdentity() ) {
-        wcsncpy(wrealm, InitialTicketDomain.Buffer, InitialTicketDomain.Length/sizeof(WCHAR));
-        wrealm[InitialTicketDomain.Length/sizeof(WCHAR)]=0;
-    } else {
-        wcsncpy(wrealm, msticket->DomainName.Buffer, msticket->DomainName.Length/sizeof(WCHAR));
-        wrealm[msticket->DomainName.Length/sizeof(WCHAR)]=0;
-    }
+    wcsncpy(wrealm, ClientRealm.Buffer, ClientRealm.Length/sizeof(WCHAR));
+    wrealm[ClientRealm.Length/sizeof(WCHAR)]=0;
     MSPrincToMITPrinc(msticket->ClientName, wrealm, context, &creds->client);
 
     // construct Service Principal
@@ -388,6 +420,67 @@ PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId)
 
 }
 
+static BOOL 
+does_retrieve_ticket_cache_ticket (void)
+{
+   static BOOL fChecked = FALSE;
+   static BOOL fCachesTicket = FALSE;
+
+   if (!fChecked)
+   {
+       NTSTATUS Status = 0;
+       NTSTATUS SubStatus = 0;
+       HANDLE LogonHandle;
+       ULONG  PackageId;
+       ULONG RequestSize;
+       PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+       PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+       ULONG ResponseSize;
+
+       fChecked = TRUE;
+       RequestSize = sizeof(*pTicketRequest) + 1;
+
+       if (!PackageConnectLookup(&LogonHandle, &PackageId))
+           return FALSE;
+
+       pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+       if (!pTicketRequest) {
+           CloseHandle(LogonHandle);
+           return FALSE;
+       }
+
+       pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+       pTicketRequest->LogonId.LowPart = 0;
+       pTicketRequest->LogonId.HighPart = 0;
+       pTicketRequest->TargetName.Length = 0;
+       pTicketRequest->TargetName.MaximumLength = 0;
+       pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+       pTicketRequest->CacheOptions = 
+           KERB_RETRIEVE_TICKET_DONT_USE_CACHE | KERB_RETRIEVE_TICKET_CACHE_TICKET;
+       pTicketRequest->EncryptionType = 0;
+       pTicketRequest->TicketFlags = 0;
+
+       Status = LsaCallAuthenticationPackage( LogonHandle,
+                                              PackageId,
+                                              pTicketRequest,
+                                              RequestSize,
+                                              &pTicketResponse,
+                                              &ResponseSize,
+                                              &SubStatus
+                                              );                                             
+
+       LocalFree(pTicketRequest);
+       CloseHandle(LogonHandle);
+
+       if (FAILED(Status) || FAILED(SubStatus)) {
+           if ( SubStatus == STATUS_NOT_SUPPORTED )
+               fCachesTicket = TRUE;
+       }
+   }
+
+   return fCachesTicket;
+}
+
 
 static DWORD
 ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2)
@@ -591,6 +684,23 @@ PurgeMSTGT(HANDLE LogonHandle, ULONG  PackageId)
     return TRUE;
 }
 
+/* 
+ * A simple function to determine if there is an exact match between two tickets
+ * We rely on the fact that the external tickets contain the raw Kerberos ticket.
+ * If the EncodedTicket fields match, the KERB_EXTERNAL_TICKETs must be the same.
+ */
+static BOOL
+KerbExternalTicketMatch( PKERB_EXTERNAL_TICKET one, PKERB_EXTERNAL_TICKET two )
+{
+    if ( one->EncodedTicketSize != two->EncodedTicketSize )
+        return FALSE;
+
+    if ( memcmp(one->EncodedTicket, two->EncodedTicket, one->EncodedTicketSize) )
+         return FALSE;
+
+    return TRUE;
+}
+
 krb5_boolean
 krb5_is_permitted_tgs_enctype(krb5_context context, krb5_const_principal princ, krb5_enctype etype)
 {
@@ -857,6 +967,8 @@ GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNA
         // 0 is supported.)
         pTicketRequest->EncryptionType = etype;
         pTicketRequest->CacheOptions = 0;
+        if ( does_retrieve_ticket_cache_ticket() )
+            pTicketRequest->CacheOptions |= KERB_RETRIEVE_TICKET_CACHE_TICKET;
 
         if ( pTicketResponse ) {
             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
@@ -927,8 +1039,8 @@ GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNA
 }
 
 static BOOL
-GetQueryTktCacheResponse( HANDLE LogonHandle, ULONG PackageId,
-                          PKERB_QUERY_TKT_CACHE_RESPONSE * ppResponse)
+GetQueryTktCacheResponseW2K( HANDLE LogonHandle, ULONG PackageId,
+                             PKERB_QUERY_TKT_CACHE_RESPONSE * ppResponse)
 {
     NTSTATUS Status = 0;
     NTSTATUS SubStatus = 0;
@@ -959,10 +1071,37 @@ GetQueryTktCacheResponse( HANDLE LogonHandle, ULONG PackageId,
     return FALSE;
 }
 
-static void
-FreeQueryResponse(PKERB_QUERY_TKT_CACHE_RESPONSE  pResponse)
+static BOOL
+GetQueryTktCacheResponseXP( HANDLE LogonHandle, ULONG PackageId,
+                            PKERB_QUERY_TKT_CACHE_EX_RESPONSE * ppResponse)
 {
-    LsaFreeReturnBuffer(pResponse);
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+
+    KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+    PKERB_QUERY_TKT_CACHE_EX_RESPONSE pQueryResponse = NULL;
+    ULONG ResponseSize;
+    
+    CacheRequest.MessageType = KerbQueryTicketCacheExMessage;
+    CacheRequest.LogonId.LowPart = 0;
+    CacheRequest.LogonId.HighPart = 0;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        &CacheRequest,
+        sizeof(CacheRequest),
+        &pQueryResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
+        *ppResponse = pQueryResponse;
+        return TRUE;
+    }
+
+    return FALSE;
 }
 
 
@@ -993,6 +1132,8 @@ GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
     MITPrincToMSPrinc(context, creds->server, &pTicketRequest->TargetName);
     pTicketRequest->CacheOptions = 0;
+    if ( does_retrieve_ticket_cache_ticket() )
+        pTicketRequest->CacheOptions |= KERB_RETRIEVE_TICKET_CACHE_TICKET;
     pTicketRequest->TicketFlags = creds->ticket_flags;
     pTicketRequest->EncryptionType = creds->keyblock.enctype;
 
@@ -1007,6 +1148,64 @@ GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
 
     LocalFree(pTicketRequest);
 
+    if (FAILED(Status) || FAILED(SubStatus))
+        return(FALSE);
+
+    /* otherwise return ticket */
+    *ticket = &(pTicketResponse->Ticket);
+    return(TRUE);
+}
+
+static BOOL
+GetMSCacheTicketFromCacheInfoW2K( HANDLE LogonHandle, ULONG PackageId,
+                  PKERB_TICKET_CACHE_INFO tktinfo, PKERB_EXTERNAL_TICKET *ticket)
+{
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+    ULONG RequestSize;
+    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+    PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+    ULONG ResponseSize;
+
+    RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
+
+    pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+    if (!pTicketRequest)
+        return FALSE;
+
+    pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+    pTicketRequest->LogonId.LowPart = 0;
+    pTicketRequest->LogonId.HighPart = 0;
+    pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
+    pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
+    pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+    memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
+    pTicketRequest->CacheOptions = 0;
+    if ( does_retrieve_ticket_cache_ticket() )
+        pTicketRequest->CacheOptions |= KERB_RETRIEVE_TICKET_CACHE_TICKET;
+    pTicketRequest->EncryptionType = tktinfo->EncryptionType;
+    pTicketRequest->TicketFlags = 0;
+    if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
+        pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
+    if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
+        pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
+    if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
+        pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
+    if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
+        pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        pTicketRequest,
+        RequestSize,
+        &pTicketResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    LocalFree(pTicketRequest);
+
     if (FAILED(Status) || FAILED(SubStatus))
         return(FALSE);
     
@@ -1017,8 +1216,8 @@ GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
 }
 
 static BOOL
-GetMSCacheTicketFromCacheInfo( HANDLE LogonHandle, ULONG PackageId,
-                  PKERB_TICKET_CACHE_INFO tktinfo, PKERB_EXTERNAL_TICKET *ticket)
+GetMSCacheTicketFromCacheInfoXP( HANDLE LogonHandle, ULONG PackageId,
+                  PKERB_TICKET_CACHE_INFO_EX tktinfo, PKERB_EXTERNAL_TICKET *ticket)
 {
     NTSTATUS Status = 0;
     NTSTATUS SubStatus = 0;
@@ -1041,6 +1240,8 @@ GetMSCacheTicketFromCacheInfo( HANDLE LogonHandle, ULONG PackageId,
     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
     memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
     pTicketRequest->CacheOptions = 0;
+    if ( does_retrieve_ticket_cache_ticket() )
+        pTicketRequest->CacheOptions |= KERB_RETRIEVE_TICKET_CACHE_TICKET;
     pTicketRequest->EncryptionType = tktinfo->EncryptionType;
     pTicketRequest->TicketFlags = 0;
     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
@@ -1131,8 +1332,11 @@ typedef struct _krb5_lcc_data {
 } krb5_lcc_data;
 
 typedef struct _krb5_lcc_cursor {
-    PKERB_QUERY_TKT_CACHE_RESPONSE  response;
-    int                             index;
+    union {
+        PKERB_QUERY_TKT_CACHE_RESPONSE w2k;
+        PKERB_QUERY_TKT_CACHE_EX_RESPONSE xp;
+    } response;
+    unsigned int index;
     PKERB_EXTERNAL_TICKET mstgt;
 } krb5_lcc_cursor;
 
@@ -1166,7 +1370,7 @@ krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
     ULONG  PackageId;
     KERB_EXTERNAL_TICKET *msticket;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
 #ifdef COMMENT
@@ -1181,7 +1385,7 @@ krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
         return KRB5_FCC_NOFILE;
 #endif
 
-    if(!PackageConnectLookup(&LogonHandle, &PackageId))
+    if (!PackageConnectLookup(&LogonHandle, &PackageId))
         return KRB5_FCC_NOFILE;
 
     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
@@ -1247,7 +1451,7 @@ krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
 static krb5_error_code KRB5_CALLCONV
 krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
 {
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     return KRB5_CC_READONLY;
@@ -1268,7 +1472,7 @@ krb5_lcc_close(krb5_context context, krb5_ccache id)
     register int closeval = KRB5_OK;
     register krb5_lcc_data *data;
     
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     if (id) {
@@ -1295,7 +1499,7 @@ krb5_lcc_destroy(krb5_context context, krb5_ccache id)
 {
     register krb5_lcc_data *data;
     
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     if (id) { 
@@ -1324,9 +1528,8 @@ krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cur
 {
     krb5_lcc_cursor *lcursor;
     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
-    KERB_EXTERNAL_TICKET *msticket;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor));
@@ -1344,11 +1547,20 @@ krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cur
         return KRB5_FCC_INTERNAL;
     }
 
-    if ( !GetQueryTktCacheResponse(data->LogonHandle, data->PackageId, &lcursor->response) ) {
-        LsaFreeReturnBuffer(lcursor->mstgt);
-        free(lcursor);
-        *cursor = 0;
-        return KRB5_FCC_INTERNAL;
+    if ( is_windows_xp() ) {
+        if ( !GetQueryTktCacheResponseXP(data->LogonHandle, data->PackageId, &lcursor->response.xp) ) {
+            LsaFreeReturnBuffer(lcursor->mstgt);
+            free(lcursor);
+            *cursor = 0;
+            return KRB5_FCC_INTERNAL;
+        }
+    } else {
+        if ( !GetQueryTktCacheResponseW2K(data->LogonHandle, data->PackageId, &lcursor->response.w2k) ) {
+            LsaFreeReturnBuffer(lcursor->mstgt);
+            free(lcursor);
+            *cursor = 0;
+            return KRB5_FCC_INTERNAL;
+        }
     }
     lcursor->index = 0;
     *cursor = (krb5_cc_cursor) lcursor;
@@ -1381,28 +1593,48 @@ krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
     KERB_EXTERNAL_TICKET *msticket;
     krb5_error_code  retval = KRB5_OK;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     data = (krb5_lcc_data *)id->data;
 
   next_cred:
-    if ( lcursor->index >= lcursor->response->CountOfTickets ) {
-        if (retval == KRB5_OK)
-            return KRB5_CC_END;
-        else {
-            LsaFreeReturnBuffer(lcursor->mstgt);
-            LsaFreeReturnBuffer(lcursor->response);
-            free(*cursor);
-            *cursor = 0;
-            return retval;
+    if ( is_windows_xp() ) {
+        if ( lcursor->index >= lcursor->response.xp->CountOfTickets ) {
+            if (retval == KRB5_OK)
+                return KRB5_CC_END;
+            else {
+                LsaFreeReturnBuffer(lcursor->mstgt);
+                LsaFreeReturnBuffer(lcursor->response.xp);
+                free(*cursor);
+                *cursor = 0;
+                return retval;
+            }
         }
-    }
 
-    if (!GetMSCacheTicketFromCacheInfo(data->LogonHandle, data->PackageId,
-                                        &lcursor->response->Tickets[lcursor->index++],&msticket)) {
-        retval = KRB5_FCC_INTERNAL;
-        goto next_cred;
+        if (!GetMSCacheTicketFromCacheInfoXP(data->LogonHandle, data->PackageId,
+                                            &lcursor->response.xp->Tickets[lcursor->index++],&msticket)) {
+            retval = KRB5_FCC_INTERNAL;
+            goto next_cred;
+        }
+    } else {
+        if ( lcursor->index >= lcursor->response.w2k->CountOfTickets ) {
+            if (retval == KRB5_OK)
+                return KRB5_CC_END;
+            else {
+                LsaFreeReturnBuffer(lcursor->mstgt);
+                LsaFreeReturnBuffer(lcursor->response.w2k);
+                free(*cursor);
+                *cursor = 0;
+                return retval;
+            }
+        }
+
+        if (!GetMSCacheTicketFromCacheInfoW2K(data->LogonHandle, data->PackageId,
+                                            &lcursor->response.w2k->Tickets[lcursor->index++],&msticket)) {
+            retval = KRB5_FCC_INTERNAL;
+            goto next_cred;
+        }
     }
 
     /* Don't return tickets with NULL Session Keys */
@@ -1412,7 +1644,10 @@ krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
     }
 
     /* convert the ticket */
-    MSCredToMITCred(msticket, lcursor->mstgt->DomainName, context, creds);
+    if ( is_windows_xp() )
+        MSCredToMITCred(msticket, lcursor->response.xp->Tickets[lcursor->index-1].ClientRealm, context, creds);
+    else
+        MSCredToMITCred(msticket, lcursor->mstgt->DomainName, context, creds);
     LsaFreeReturnBuffer(msticket);
     return KRB5_OK;
 }
@@ -1435,12 +1670,15 @@ krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *curso
 {
     krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     if ( lcursor ) {
         LsaFreeReturnBuffer(lcursor->mstgt);
-        LsaFreeReturnBuffer(lcursor->response);
+        if ( is_windows_xp() )
+            LsaFreeReturnBuffer(lcursor->response.xp);
+        else
+            LsaFreeReturnBuffer(lcursor->response.w2k);
         free(*cursor);
     }
     *cursor = 0;
@@ -1456,7 +1694,7 @@ krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *curso
 static krb5_error_code KRB5_CALLCONV
 krb5_lcc_generate_new (krb5_context context, krb5_ccache *id)
 {
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     return KRB5_CC_READONLY;
@@ -1473,7 +1711,7 @@ static const char * KRB5_CALLCONV
 krb5_lcc_get_name (krb5_context context, krb5_ccache id)
 {
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return "";
 
     if ( !id )
@@ -1500,7 +1738,7 @@ krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *pri
 {
     krb5_error_code kret = KRB5_OK;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     /* obtain principal */
@@ -1514,11 +1752,11 @@ krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
 {
     krb5_error_code kret = KRB5_OK;
     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
-    KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0;
+    KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0, *mstmp = 0;
     krb5_creds * mcreds_noflags;
     krb5_creds   fetchcreds;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     memset(&fetchcreds, 0, sizeof(krb5_creds));
@@ -1543,7 +1781,9 @@ krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
     if ( !kret )
         goto cleanup;
 
-    /* if not, obtain a ticket using the request flags and enctype even though it will not
+
+
+    /* if not, obtain a ticket using the request flags and enctype even though it may not
      * be stored in the LSA cache for future use.
      */
     if ( msticket ) {
@@ -1557,9 +1797,40 @@ krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
     }
 
     /* convert the ticket */
-    GetMSTGT(context, data->LogonHandle, data->PackageId, &mstgt, FALSE);
+    if ( !is_windows_xp() || !does_retrieve_ticket_cache_ticket() ) {
+        if ( PreserveInitialTicketIdentity() )
+            GetMSTGT(context, data->LogonHandle, data->PackageId, &mstgt, FALSE);
+
+        MSCredToMITCred(msticket, mstgt ? mstgt->DomainName : msticket->DomainName, context, &fetchcreds);
+    } else {
+        /* We can obtain the correct client realm for a ticket by walking the
+         * cache contents until we find the matching service ticket.
+         */
+        PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse = 0;
+        int i;
+
+        if (!GetQueryTktCacheResponseXP( data->LogonHandle, data->PackageId, &pResponse)) {
+            kret = KRB5_FCC_INTERNAL;
+            goto cleanup;
+        }
+
+        for ( i=0; i<pResponse->CountOfTickets; i++ ) {
+            if (!GetMSCacheTicketFromCacheInfoXP(data->LogonHandle, data->PackageId,
+                                                  &pResponse->Tickets[i],&mstmp)) {
+                continue;
+            }
+
+            if ( KerbExternalTicketMatch(msticket,mstmp) )
+                break;
+
+            LsaFreeReturnBuffer(mstmp);
+            mstmp = 0;
+        }
+
+        MSCredToMITCred(msticket, mstmp ? pResponse->Tickets[i].ClientRealm : msticket->DomainName, context, &fetchcreds);
+        LsaFreeReturnBuffer(pResponse);
+    }
 
-    MSCredToMITCred(msticket, mstgt ? mstgt->DomainName : msticket->DomainName, context, &fetchcreds);
 
     /* check to see if this ticket matches the request using logic from
      * krb5_cc_retrieve_cred_default()
@@ -1572,6 +1843,8 @@ krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
     }
 
   cleanup:
+    if ( mstmp )
+        LsaFreeReturnBuffer(mstmp);
     if ( mstgt )
         LsaFreeReturnBuffer(mstgt);
     if ( msticket )
@@ -1598,7 +1871,7 @@ krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
     KERB_EXTERNAL_TICKET *msticket = 0, *msticket2 = 0;
     krb5_creds * creds_noflags;
 
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     if ( creds->ticket_flags != 0 && creds->keyblock.enctype != 0 ) {
@@ -1632,7 +1905,7 @@ static krb5_error_code KRB5_CALLCONV
 krb5_lcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
                      krb5_creds *creds)
 {
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     return KRB5_CC_READONLY;
@@ -1646,7 +1919,7 @@ krb5_lcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
 static krb5_error_code KRB5_CALLCONV
 krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
 {
-    if (!IsWindows2000())
+    if (!is_windows_2000())
         return KRB5_FCC_NOFILE;
 
     return KRB5_OK;