Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / lib / krb5 / ccache / cc_mslsa.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_mslsa.c */
3 /*
4  * Copyright 2007 Secure Endpoints Inc.
5  *
6  * Copyright 2003,2004 by the Massachusetts Institute of Technology.
7  * All Rights Reserved.
8  *
9  * Export of this software from the United States of America may
10  *   require a specific license from the United States Government.
11  *   It is the responsibility of any person or organization contemplating
12  *   export to obtain such a license before exporting.
13  *
14  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
15  * distribute this software and its documentation for any purpose and
16  * without fee is hereby granted, provided that the above copyright
17  * notice appear in all copies and that both that copyright notice and
18  * this permission notice appear in supporting documentation, and that
19  * the name of M.I.T. not be used in advertising or publicity pertaining
20  * to distribution of the software without specific, written prior
21  * permission.  Furthermore if you modify this software you must label
22  * your software as modified software and not distribute it in such a
23  * fashion that it might be confused with the original M.I.T. software.
24  * M.I.T. makes no representations about the suitability of
25  * this software for any purpose.  It is provided "as is" without express
26  * or implied warranty.
27  *
28  * Copyright 2000 by Carnegie Mellon University
29  *
30  * All Rights Reserved
31  *
32  * Permission to use, copy, modify, and distribute this software and its
33  * documentation for any purpose and without fee is hereby granted,
34  * provided that the above copyright notice appear in all copies and that
35  * both that copyright notice and this permission notice appear in
36  * supporting documentation, and that the name of Carnegie Mellon
37  * University not be used in advertising or publicity pertaining to
38  * distribution of the software without specific, written prior
39  * permission.
40  *
41  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
42  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
43  * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
44  * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
45  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
46  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
47  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
48  *
49  * Implementation of microsoft windows lsa credentials cache
50  */
51
52 #ifdef _WIN32
53 #define UNICODE
54 #define _UNICODE
55
56 #include <ntstatus.h>
57 #define WIN32_NO_STATUS
58 #include "k5-int.h"
59 #include "com_err.h"
60 #include "cc-int.h"
61
62 #include <stdio.h>
63 #include <errno.h>
64 #include <stdlib.h>
65 #include <conio.h>
66 #include <time.h>
67
68 #define SECURITY_WIN32
69 #include <security.h>
70 #ifdef _WIN32_WINNT
71 #undef _WIN32_WINNT
72 #endif
73 #define _WIN32_WINNT 0x0600
74 #include <ntsecapi.h>
75
76
77 #define MAX_MSG_SIZE 256
78 #define MAX_MSPRINC_SIZE 1024
79
80 /* THREAD SAFETY
81  * The function does_query_ticket_cache_ex2()
82  * contains static variables to cache the responses of the tests being
83  * performed.  There is no harm in the test being performed more than
84  * once since the result will always be the same.
85  */
86
87 typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
88
89 static VOID
90 ShowWinError(LPSTR szAPI, DWORD dwError)
91 {
92
93     // TODO - Write errors to event log so that scripts that don't
94     // check for errors will still get something in the event log
95
96     // This code is completely unsafe for use on non-English systems
97     // Any call to this function will result in the FormatMessage
98     // call failing and the program terminating.  This might have
99     // been acceptable when this code was part of ms2mit.exe as
100     // a standalone executable but it is not appropriate for a library
101
102 #ifdef COMMENT
103     WCHAR szMsgBuf[MAX_MSG_SIZE];
104     DWORD dwRes;
105
106     printf("Error calling function %s: %lu\n", szAPI, dwError);
107
108     dwRes = FormatMessage (
109         FORMAT_MESSAGE_FROM_SYSTEM,
110         NULL,
111         dwError,
112         MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US),
113         szMsgBuf,
114         MAX_MSG_SIZE,
115         NULL);
116     if (0 == dwRes) {
117         printf("FormatMessage failed with %d\n", GetLastError());
118         ExitProcess(EXIT_FAILURE);
119     }
120
121     printf("%S",szMsgBuf);
122 #endif /* COMMENT */
123 }
124
125 static VOID
126 ShowLsaError(LPSTR szAPI, NTSTATUS Status)
127 {
128     //
129     // Convert the NTSTATUS to Winerror. Then call ShowWinError().
130     //
131     ShowWinError(szAPI, LsaNtStatusToWinError(Status));
132 }
133
134 static BOOL
135 WINAPI
136 UnicodeToANSI(LPTSTR lpInputString, LPSTR lpszOutputString, int nOutStringLen)
137 {
138     CPINFO CodePageInfo;
139
140     GetCPInfo(CP_ACP, &CodePageInfo);
141
142     if (CodePageInfo.MaxCharSize > 1) {
143         // Only supporting non-Unicode strings
144         int reqLen = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) lpInputString, -1,
145                                          NULL, 0, NULL, NULL);
146         if ( reqLen > nOutStringLen)
147         {
148             return FALSE;
149         } else {
150             if (WideCharToMultiByte(CP_ACP,
151                                     /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK,
152                                     (LPCWSTR) lpInputString, -1,
153                                     lpszOutputString,
154                                     nOutStringLen, NULL, NULL) == 0)
155                 return FALSE;
156         }
157     }
158     else
159     {
160         // Looks like unicode, better translate it
161         if (WideCharToMultiByte(CP_ACP,
162                                 /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK,
163                                 (LPCWSTR) lpInputString, -1,
164                                 lpszOutputString,
165                                 nOutStringLen, NULL, NULL) == 0)
166             return FALSE;
167     }
168
169     return TRUE;
170 }  // UnicodeToANSI
171
172 static VOID
173 WINAPI
174 ANSIToUnicode(LPCSTR lpInputString, LPWSTR lpszOutputString, int nOutStringLen)
175 {
176
177     CPINFO CodePageInfo;
178
179     GetCPInfo(CP_ACP, &CodePageInfo);
180
181     MultiByteToWideChar(CP_ACP, 0, lpInputString, -1,
182                         lpszOutputString, nOutStringLen);
183 }  // ANSIToUnicode
184
185
186 static void
187 MITPrincToMSPrinc(krb5_context context, krb5_principal principal, UNICODE_STRING * msprinc)
188 {
189     char *aname = NULL;
190
191     if (!krb5_unparse_name(context, principal, &aname)) {
192         msprinc->Length = strlen(aname) * sizeof(WCHAR);
193         if ( msprinc->Length <= msprinc->MaximumLength )
194             ANSIToUnicode(aname, msprinc->Buffer, msprinc->MaximumLength);
195         else
196             msprinc->Length = 0;
197         krb5_free_unparsed_name(context,aname);
198     }
199 }
200
201 static BOOL
202 UnicodeStringToMITPrinc(UNICODE_STRING *service, UNICODE_STRING *realm,
203                         krb5_context context, krb5_principal *principal)
204 {
205     WCHAR princbuf[512];
206     WCHAR realmbuf[512];
207     char aname[512];
208
209     /* Convert the realm to a wchar string. */
210     realmbuf[0] = '\0';
211     wcsncpy(realmbuf, realm->Buffer, realm->Length / sizeof(WCHAR));
212     realmbuf[realm->Length / sizeof(WCHAR)] = 0;
213     /* Convert the principal components to a wchar string. */
214     princbuf[0]=0;
215     wcsncpy(princbuf, service->Buffer, service->Length/sizeof(WCHAR));
216     princbuf[service->Length/sizeof(WCHAR)]=0;
217     wcscat(princbuf, L"@");
218     wcscat(princbuf, realmbuf);
219     if (UnicodeToANSI(princbuf, aname, sizeof(aname))) {
220         if (krb5_parse_name(context, aname, principal) == 0)
221             return TRUE;
222     }
223     return FALSE;
224 }
225
226
227 static BOOL
228 KerbExternalNameToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context,
229                            krb5_principal *principal)
230 {
231     WCHAR princbuf[512],tmpbuf[128];
232     char aname[512];
233     USHORT i;
234     princbuf[0]=0;
235     for (i=0;i<msprinc->NameCount;i++) {
236         wcsncpy(tmpbuf, msprinc->Names[i].Buffer,
237                 msprinc->Names[i].Length/sizeof(WCHAR));
238         tmpbuf[msprinc->Names[i].Length/sizeof(WCHAR)]=0;
239         if (princbuf[0])
240             wcscat(princbuf, L"/");
241         wcscat(princbuf, tmpbuf);
242     }
243     wcscat(princbuf, L"@");
244     wcscat(princbuf, realm);
245     if (UnicodeToANSI(princbuf, aname, sizeof(aname))) {
246         if (krb5_parse_name(context, aname, principal) == 0)
247             return TRUE;
248     }
249     return FALSE;
250 }
251
252 static time_t
253 FileTimeToUnixTime(LARGE_INTEGER *ltime)
254 {
255     FILETIME filetime, localfiletime;
256     SYSTEMTIME systime;
257     struct tm utime;
258     filetime.dwLowDateTime=ltime->LowPart;
259     filetime.dwHighDateTime=ltime->HighPart;
260     FileTimeToLocalFileTime(&filetime, &localfiletime);
261     FileTimeToSystemTime(&localfiletime, &systime);
262     utime.tm_sec=systime.wSecond;
263     utime.tm_min=systime.wMinute;
264     utime.tm_hour=systime.wHour;
265     utime.tm_mday=systime.wDay;
266     utime.tm_mon=systime.wMonth-1;
267     utime.tm_year=systime.wYear-1900;
268     utime.tm_isdst=-1;
269     return(mktime(&utime));
270 }
271
272 static void
273 MSSessionKeyToMITKeyblock(KERB_CRYPTO_KEY *mskey, krb5_context context, krb5_keyblock *keyblock)
274 {
275     krb5_keyblock tmpblock;
276     tmpblock.magic=KV5M_KEYBLOCK;
277     tmpblock.enctype=mskey->KeyType;
278     tmpblock.length=mskey->Length;
279     tmpblock.contents=mskey->Value;
280     krb5_copy_keyblock_contents(context, &tmpblock, keyblock);
281 }
282
283 static BOOL
284 IsMSSessionKeyNull(KERB_CRYPTO_KEY *mskey)
285 {
286     DWORD i;
287
288     if (mskey->KeyType == KERB_ETYPE_NULL)
289         return TRUE;
290
291     for ( i=0; i<mskey->Length; i++ ) {
292         if (mskey->Value[i])
293             return FALSE;
294     }
295
296     return TRUE;
297 }
298
299 static void
300 MSFlagsToMITFlags(ULONG msflags, ULONG *mitflags)
301 {
302     *mitflags=msflags;
303 }
304
305 static BOOL
306 MSTicketToMITTicket(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_data *ticket)
307 {
308     krb5_data tmpdata, *newdata = 0;
309     krb5_error_code rc;
310
311     tmpdata.magic=KV5M_DATA;
312     tmpdata.length=msticket->EncodedTicketSize;
313     tmpdata.data=msticket->EncodedTicket;
314
315     // this is ugly and will break krb5_free_data()
316     // now that this is being done within the library it won't break krb5_free_data()
317     rc = krb5_copy_data(context, &tmpdata, &newdata);
318     if (rc)
319         return FALSE;
320
321     memcpy(ticket, newdata, sizeof(krb5_data));
322     free(newdata);
323     return TRUE;
324 }
325
326 static BOOL
327 MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING ClientRealm,
328                 krb5_context context, krb5_creds *creds)
329 {
330     WCHAR wrealm[128];
331     ZeroMemory(creds, sizeof(krb5_creds));
332     creds->magic=KV5M_CREDS;
333
334     // construct Client Principal
335     wcsncpy(wrealm, ClientRealm.Buffer, ClientRealm.Length/sizeof(WCHAR));
336     wrealm[ClientRealm.Length/sizeof(WCHAR)]=0;
337     if (!KerbExternalNameToMITPrinc(msticket->ClientName, wrealm, context, &creds->client))
338         return FALSE;
339
340     // construct Service Principal
341     wcsncpy(wrealm, msticket->DomainName.Buffer,
342             msticket->DomainName.Length/sizeof(WCHAR));
343     wrealm[msticket->DomainName.Length/sizeof(WCHAR)]=0;
344     if (!KerbExternalNameToMITPrinc(msticket->ServiceName, wrealm, context, &creds->server))
345         return FALSE;
346     MSSessionKeyToMITKeyblock(&msticket->SessionKey, context,
347                               &creds->keyblock);
348     MSFlagsToMITFlags(msticket->TicketFlags, &creds->ticket_flags);
349     creds->times.starttime=FileTimeToUnixTime(&msticket->StartTime);
350     creds->times.endtime=FileTimeToUnixTime(&msticket->EndTime);
351     creds->times.renew_till=FileTimeToUnixTime(&msticket->RenewUntil);
352
353     creds->addresses = NULL;
354
355     return MSTicketToMITTicket(msticket, context, &creds->ticket);
356 }
357
358 /* CacheInfoEx2ToMITCred is used when we do not need the real ticket */
359 static BOOL
360 CacheInfoEx2ToMITCred(KERB_TICKET_CACHE_INFO_EX2 *info,
361                       krb5_context context, krb5_creds *creds)
362 {
363     ZeroMemory(creds, sizeof(krb5_creds));
364     creds->magic=KV5M_CREDS;
365
366     // construct Client Principal
367     if (!UnicodeStringToMITPrinc(&info->ClientName, &info->ClientRealm,
368                                  context, &creds->client))
369         return FALSE;
370
371     // construct Service Principal
372     if (!UnicodeStringToMITPrinc(&info->ServerName, &info->ServerRealm,
373                                  context, &creds->server))
374         return FALSE;
375
376     creds->keyblock.magic = KV5M_KEYBLOCK;
377     creds->keyblock.enctype = info->SessionKeyType;
378     creds->ticket_flags = info->TicketFlags;
379     MSFlagsToMITFlags(info->TicketFlags, &creds->ticket_flags);
380     creds->times.starttime=FileTimeToUnixTime(&info->StartTime);
381     creds->times.endtime=FileTimeToUnixTime(&info->EndTime);
382     creds->times.renew_till=FileTimeToUnixTime(&info->RenewTime);
383
384     /* MS Tickets are addressless.  MIT requires an empty address
385      * not a NULL list of addresses.
386      */
387     creds->addresses = (krb5_address **)malloc(sizeof(krb5_address *));
388     if (creds->addresses == NULL)
389         return FALSE;
390     memset(creds->addresses, 0, sizeof(krb5_address *));
391
392     return TRUE;
393 }
394
395 static BOOL
396 PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId)
397 {
398     LSA_STRING Name;
399     NTSTATUS Status;
400
401     Status = LsaConnectUntrusted(
402         pLogonHandle
403     );
404
405     if (FAILED(Status))
406     {
407         ShowLsaError("LsaConnectUntrusted", Status);
408         return FALSE;
409     }
410
411     Name.Buffer = MICROSOFT_KERBEROS_NAME_A;
412     Name.Length = strlen(Name.Buffer);
413     Name.MaximumLength = Name.Length + 1;
414
415     Status = LsaLookupAuthenticationPackage(
416         *pLogonHandle,
417         &Name,
418         pPackageId
419     );
420
421     if (FAILED(Status))
422     {
423         ShowLsaError("LsaLookupAuthenticationPackage", Status);
424         return FALSE;
425     }
426
427     return TRUE;
428
429 }
430
431 /*
432  * This runtime check is only needed on Windows XP and Server 2003.
433  * It can safely be removed when we no longer wish to support any
434  * versions of those platforms.
435  */
436 static BOOL
437 does_query_ticket_cache_ex2 (void)
438 {
439     static BOOL fChecked = FALSE;
440     static BOOL fEx2Response = FALSE;
441
442     if (!fChecked)
443     {
444         NTSTATUS Status = 0;
445         NTSTATUS SubStatus = 0;
446         HANDLE LogonHandle;
447         ULONG  PackageId;
448         ULONG RequestSize;
449         PKERB_QUERY_TKT_CACHE_REQUEST pCacheRequest = NULL;
450         PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pCacheResponse = NULL;
451         ULONG ResponseSize;
452
453         RequestSize = sizeof(*pCacheRequest) + 1;
454
455         if (!PackageConnectLookup(&LogonHandle, &PackageId))
456             return FALSE;
457
458         pCacheRequest = (PKERB_QUERY_TKT_CACHE_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
459         if (!pCacheRequest) {
460             LsaDeregisterLogonProcess(LogonHandle);
461             return FALSE;
462         }
463
464         pCacheRequest->MessageType = KerbQueryTicketCacheEx2Message;
465         pCacheRequest->LogonId.LowPart = 0;
466         pCacheRequest->LogonId.HighPart = 0;
467
468         Status = LsaCallAuthenticationPackage( LogonHandle,
469                                                PackageId,
470                                                pCacheRequest,
471                                                RequestSize,
472                                                &pCacheResponse,
473                                                &ResponseSize,
474                                                &SubStatus
475         );
476
477         LocalFree(pCacheRequest);
478         LsaDeregisterLogonProcess(LogonHandle);
479
480         if (!(FAILED(Status) || FAILED(SubStatus))) {
481             LsaFreeReturnBuffer(pCacheResponse);
482             fEx2Response = TRUE;
483         }
484         fChecked = TRUE;
485     }
486
487     return fEx2Response;
488 }
489
490 static DWORD
491 ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2)
492 {
493     //
494     // The buffers for Source1 and Source2 cannot overlap pTarget's
495     // buffer.  Source1.Length + Source2.Length must be <= 0xFFFF,
496     // otherwise we overflow...
497     //
498
499     USHORT TotalSize = Source1.Length + Source2.Length;
500     PBYTE buffer = (PBYTE) pTarget->Buffer;
501
502     if (TotalSize > pTarget->MaximumLength)
503         return ERROR_INSUFFICIENT_BUFFER;
504
505     if ( pTarget->Buffer != Source1.Buffer )
506         memcpy(buffer, Source1.Buffer, Source1.Length);
507     memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length);
508
509     pTarget->Length = TotalSize;
510     return ERROR_SUCCESS;
511 }
512
513 static BOOL
514 get_STRING_from_registry(HKEY hBaseKey, char * key, char * value, char * outbuf, DWORD  outlen)
515 {
516     HKEY hKey;
517     DWORD dwCount;
518     LONG rc;
519
520     if (!outbuf || outlen == 0)
521         return FALSE;
522
523     rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey);
524     if (rc)
525         return FALSE;
526
527     dwCount = outlen;
528     rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount);
529     RegCloseKey(hKey);
530
531     return rc?FALSE:TRUE;
532 }
533
534 static BOOL
535 GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData)
536 {
537     NTSTATUS Status = 0;
538     HANDLE  TokenHandle;
539     TOKEN_STATISTICS Stats;
540     DWORD   ReqLen;
541     BOOL    Success;
542
543     if (!ppSessionData)
544         return FALSE;
545     *ppSessionData = NULL;
546
547     Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle );
548     if ( !Success )
549         return FALSE;
550
551     Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen );
552     CloseHandle( TokenHandle );
553     if ( !Success )
554         return FALSE;
555
556     Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData );
557     if ( FAILED(Status) || !ppSessionData )
558         return FALSE;
559
560     return TRUE;
561 }
562
563 static DWORD
564 ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest, ULONG * outSize)
565 {
566     DWORD Error;
567     UNICODE_STRING TargetPrefix;
568     USHORT TargetSize;
569     ULONG RequestSize;
570     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
571
572     *outRequest = NULL;
573     *outSize = 0;
574
575     //
576     // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we
577     // can easily concatenate it later.
578     //
579
580     TargetPrefix.Buffer = L"krbtgt/";
581     TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR);
582     TargetPrefix.MaximumLength = TargetPrefix.Length;
583
584     //
585     // We will need to concatenate the "krbtgt/" prefix and the
586     // Logon Session's DnsDomainName into our request's target name.
587     //
588     // Therefore, first compute the necessary buffer size for that.
589     //
590     // Note that we might theoretically have integer overflow.
591     //
592
593     TargetSize = TargetPrefix.Length + DomainName.Length;
594
595     //
596     // The ticket request buffer needs to be a single buffer.  That buffer
597     // needs to include the buffer for the target name.
598     //
599
600     RequestSize = sizeof(*pTicketRequest) + TargetSize;
601
602     //
603     // Allocate the request buffer and make sure it's zero-filled.
604     //
605
606     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
607     if (!pTicketRequest)
608         return GetLastError();
609
610     //
611     // Concatenate the target prefix with the previous reponse's
612     // target domain.
613     //
614
615     pTicketRequest->TargetName.Length = 0;
616     pTicketRequest->TargetName.MaximumLength = TargetSize;
617     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
618     Error = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName),
619                                       TargetPrefix,
620                                       DomainName);
621     *outRequest = pTicketRequest;
622     *outSize    = RequestSize;
623     return Error;
624 }
625
626 static BOOL
627 PurgeAllTickets(HANDLE LogonHandle, ULONG  PackageId)
628 {
629     NTSTATUS Status = 0;
630     NTSTATUS SubStatus = 0;
631     KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest;
632
633     PurgeRequest.MessageType = KerbPurgeTicketCacheMessage;
634     PurgeRequest.LogonId.LowPart = 0;
635     PurgeRequest.LogonId.HighPart = 0;
636     PurgeRequest.ServerName.Buffer = L"";
637     PurgeRequest.ServerName.Length = 0;
638     PurgeRequest.ServerName.MaximumLength = 0;
639     PurgeRequest.RealmName.Buffer = L"";
640     PurgeRequest.RealmName.Length = 0;
641     PurgeRequest.RealmName.MaximumLength = 0;
642     Status = LsaCallAuthenticationPackage(LogonHandle,
643                                           PackageId,
644                                           &PurgeRequest,
645                                           sizeof(PurgeRequest),
646                                           NULL,
647                                           NULL,
648                                           &SubStatus
649     );
650     if (FAILED(Status) || FAILED(SubStatus))
651         return FALSE;
652     return TRUE;
653 }
654
655 static BOOL
656 PurgeTicketEx(HANDLE LogonHandle, ULONG  PackageId,
657               krb5_context context, krb5_flags flags, krb5_creds *cred)
658 {
659     NTSTATUS Status = 0;
660     NTSTATUS SubStatus = 0;
661     KERB_PURGE_TKT_CACHE_EX_REQUEST * pPurgeRequest;
662     DWORD dwRequestLen = sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 4096;
663     char * cname = NULL, * crealm = NULL;
664     char * sname = NULL, * srealm = NULL;
665
666     if (krb5_unparse_name(context, cred->client, &cname))
667         return FALSE;
668
669     if (krb5_unparse_name(context, cred->server, &sname)) {
670         krb5_free_unparsed_name(context, cname);
671         return FALSE;
672     }
673
674     pPurgeRequest = malloc(dwRequestLen);
675     if ( pPurgeRequest == NULL )
676         return FALSE;
677     memset(pPurgeRequest, 0, dwRequestLen);
678
679     crealm = strrchr(cname, '@');
680     *crealm = '\0';
681     crealm++;
682
683     srealm = strrchr(sname, '@');
684     *srealm = '\0';
685     srealm++;
686
687     pPurgeRequest->MessageType = KerbPurgeTicketCacheExMessage;
688     pPurgeRequest->LogonId.LowPart = 0;
689     pPurgeRequest->LogonId.HighPart = 0;
690     pPurgeRequest->Flags = 0;
691     pPurgeRequest->TicketTemplate.ClientName.Buffer = (PWSTR)((CHAR *)pPurgeRequest + sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST));
692     pPurgeRequest->TicketTemplate.ClientName.Length = strlen(cname)*sizeof(WCHAR);
693     pPurgeRequest->TicketTemplate.ClientName.MaximumLength = 256;
694     ANSIToUnicode(cname, pPurgeRequest->TicketTemplate.ClientName.Buffer,
695                   pPurgeRequest->TicketTemplate.ClientName.MaximumLength);
696
697     pPurgeRequest->TicketTemplate.ClientRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 512);
698     pPurgeRequest->TicketTemplate.ClientRealm.Length = strlen(crealm)*sizeof(WCHAR);
699     pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength = 256;
700     ANSIToUnicode(crealm, pPurgeRequest->TicketTemplate.ClientRealm.Buffer,
701                   pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength);
702
703     pPurgeRequest->TicketTemplate.ServerName.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1024);
704     pPurgeRequest->TicketTemplate.ServerName.Length = strlen(sname)*sizeof(WCHAR);
705     pPurgeRequest->TicketTemplate.ServerName.MaximumLength = 256;
706     ANSIToUnicode(sname, pPurgeRequest->TicketTemplate.ServerName.Buffer,
707                   pPurgeRequest->TicketTemplate.ServerName.MaximumLength);
708
709     pPurgeRequest->TicketTemplate.ServerRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1536);
710     pPurgeRequest->TicketTemplate.ServerRealm.Length = strlen(srealm)*sizeof(WCHAR);
711     pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength = 256;
712     ANSIToUnicode(srealm, pPurgeRequest->TicketTemplate.ServerRealm.Buffer,
713                   pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength);
714
715     pPurgeRequest->TicketTemplate.StartTime;
716     pPurgeRequest->TicketTemplate.EndTime;
717     pPurgeRequest->TicketTemplate.RenewTime;
718     pPurgeRequest->TicketTemplate.EncryptionType = cred->keyblock.enctype;
719     pPurgeRequest->TicketTemplate.TicketFlags = flags;
720
721     Status = LsaCallAuthenticationPackage( LogonHandle,
722                                            PackageId,
723                                            pPurgeRequest,
724                                            dwRequestLen,
725                                            NULL,
726                                            NULL,
727                                            &SubStatus
728     );
729     free(pPurgeRequest);
730     krb5_free_unparsed_name(context,cname);
731     krb5_free_unparsed_name(context,sname);
732
733     if (FAILED(Status) || FAILED(SubStatus))
734         return FALSE;
735     return TRUE;
736 }
737
738 static BOOL
739 KerbSubmitTicket( HANDLE LogonHandle, ULONG  PackageId,
740                   krb5_context context, krb5_creds *cred)
741 {
742     NTSTATUS Status = 0;
743     NTSTATUS SubStatus = 0;
744     KERB_SUBMIT_TKT_REQUEST * pSubmitRequest = NULL;
745     DWORD dwRequestLen;
746     krb5_auth_context auth_context = NULL;
747     krb5_keyblock * keyblock = 0;
748     krb5_replay_data replaydata;
749     krb5_data * krb_cred = 0;
750     krb5_error_code rc;
751     BOOL rv = FALSE;
752
753     if (krb5_auth_con_init(context, &auth_context)) {
754         return FALSE;
755     }
756
757     if (krb5_auth_con_setflags(context, auth_context,
758                                KRB5_AUTH_CONTEXT_RET_TIME)) {
759         return FALSE;
760     }
761
762     krb5_auth_con_getsendsubkey(context, auth_context, &keyblock);
763     if (keyblock == NULL)
764         krb5_auth_con_getkey(context, auth_context, &keyblock);
765
766     /* make up a key, any key, that can be used to generate the
767      * encrypted KRB_CRED pdu.  The Vista release LSA requires
768      * that an enctype other than NULL be used. */
769     if (keyblock == NULL) {
770         keyblock = (krb5_keyblock *)malloc(sizeof(krb5_keyblock));
771         if (keyblock == NULL)
772             return FALSE;
773         keyblock->enctype = ENCTYPE_ARCFOUR_HMAC;
774         keyblock->length = 16;
775         keyblock->contents = (krb5_octet *)malloc(16);
776         if (keyblock->contents == NULL)
777             goto cleanup;
778         keyblock->contents[0] = 0xde;
779         keyblock->contents[1] = 0xad;
780         keyblock->contents[2] = 0xbe;
781         keyblock->contents[3] = 0xef;
782         keyblock->contents[4] = 0xfe;
783         keyblock->contents[5] = 0xed;
784         keyblock->contents[6] = 0xf0;
785         keyblock->contents[7] = 0xd;
786         keyblock->contents[8] = 0xde;
787         keyblock->contents[9] = 0xad;
788         keyblock->contents[10] = 0xbe;
789         keyblock->contents[11] = 0xef;
790         keyblock->contents[12] = 0xfe;
791         keyblock->contents[13] = 0xed;
792         keyblock->contents[14] = 0xf0;
793         keyblock->contents[15] = 0xd;
794         krb5_auth_con_setsendsubkey(context, auth_context, keyblock);
795     }
796     rc = krb5_mk_1cred(context, auth_context, cred, &krb_cred, &replaydata);
797     if (rc)
798         goto cleanup;
799
800     dwRequestLen = sizeof(KERB_SUBMIT_TKT_REQUEST) + krb_cred->length + (keyblock ? keyblock->length : 0);
801
802     pSubmitRequest = (PKERB_SUBMIT_TKT_REQUEST)malloc(dwRequestLen);
803     if (pSubmitRequest == NULL)
804         goto cleanup;
805     memset(pSubmitRequest, 0, dwRequestLen);
806
807     pSubmitRequest->MessageType = KerbSubmitTicketMessage;
808     pSubmitRequest->LogonId.LowPart = 0;
809     pSubmitRequest->LogonId.HighPart = 0;
810     pSubmitRequest->Flags = 0;
811
812     if (keyblock) {
813         pSubmitRequest->Key.KeyType = keyblock->enctype;
814         pSubmitRequest->Key.Length = keyblock->length;
815         pSubmitRequest->Key.Offset = sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length;
816     } else {
817         pSubmitRequest->Key.KeyType = ENCTYPE_NULL;
818         pSubmitRequest->Key.Length = 0;
819         pSubmitRequest->Key.Offset = 0;
820     }
821     pSubmitRequest->KerbCredSize = krb_cred->length;
822     pSubmitRequest->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
823     memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST),
824            krb_cred->data, krb_cred->length);
825     if (keyblock)
826         memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length,
827                keyblock->contents, keyblock->length);
828     Status = LsaCallAuthenticationPackage( LogonHandle,
829                                            PackageId,
830                                            pSubmitRequest,
831                                            dwRequestLen,
832                                            NULL,
833                                            NULL,
834                                            &SubStatus
835     );
836
837     rv = (!FAILED(Status) && !FAILED(SubStatus));
838
839 cleanup:
840     free(pSubmitRequest);
841     krb5_free_keyblock(context, keyblock);
842     krb5_free_data(context, krb_cred);
843     krb5_auth_con_free(context, auth_context);
844
845     return rv;
846 }
847
848 /*
849  * A simple function to determine if there is an exact match between two tickets
850  * We rely on the fact that the external tickets contain the raw Kerberos ticket.
851  * If the EncodedTicket fields match, the KERB_EXTERNAL_TICKETs must be the same.
852  */
853 static BOOL
854 KerbExternalTicketMatch( PKERB_EXTERNAL_TICKET one, PKERB_EXTERNAL_TICKET two )
855 {
856     if ( one->EncodedTicketSize != two->EncodedTicketSize )
857         return FALSE;
858
859     if ( memcmp(one->EncodedTicket, two->EncodedTicket, one->EncodedTicketSize) )
860         return FALSE;
861
862     return TRUE;
863 }
864
865 krb5_boolean
866 krb5_is_permitted_tgs_enctype(krb5_context context, krb5_const_principal princ, krb5_enctype etype)
867 {
868     krb5_enctype *list, *ptr;
869     krb5_boolean ret;
870
871     if (krb5_get_tgs_ktypes(context, princ, &list))
872         return(0);
873
874     ret = 0;
875
876     for (ptr = list; *ptr; ptr++)
877         if (*ptr == etype)
878             ret = 1;
879
880     krb5_free_enctypes(context, list);
881
882     return(ret);
883 }
884
885 // to allow the purging of expired tickets from LSA cache.  This is necessary
886 // to force the retrieval of new TGTs.  Microsoft does not appear to retrieve
887 // new tickets when they expire.  Instead they continue to accept the expired
888 // tickets.  This is safe to do because the LSA purges its cache when it
889 // retrieves a new TGT (ms calls this renew) but not when it renews the TGT
890 // (ms calls this refresh).
891 // UAC-limited processes are not allowed to obtain a copy of the MSTGT
892 // session key.  We used to check for UAC-limited processes and refuse all
893 // access to the TGT, but this makes the MSLSA ccache completely unusable.
894 // Instead we ought to just flag that the tgt session key is not valid.
895
896 static BOOL
897 GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNAL_TICKET **ticket, BOOL enforce_tgs_enctypes)
898 {
899     //
900     // INVARIANTS:
901     //
902     //   (FAILED(Status) || FAILED(SubStatus)) ==> error
903     //   bIsLsaError ==> LsaCallAuthenticationPackage() error
904     //
905
906     BOOL bIsLsaError = FALSE;
907     NTSTATUS Status = 0;
908     NTSTATUS SubStatus = 0;
909     DWORD   Error;
910
911     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
912     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
913     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
914     ULONG RequestSize;
915     ULONG ResponseSize;
916     int    purge_cache = 0;
917     int    ignore_cache = 0;
918     krb5_enctype *etype_list = NULL, *ptr = NULL, etype = 0;
919
920     memset(&CacheRequest, 0, sizeof(KERB_QUERY_TKT_CACHE_REQUEST));
921     CacheRequest.MessageType = KerbRetrieveTicketMessage;
922     CacheRequest.LogonId.LowPart = 0;
923     CacheRequest.LogonId.HighPart = 0;
924
925     Status = LsaCallAuthenticationPackage(
926         LogonHandle,
927         PackageId,
928         &CacheRequest,
929         sizeof(CacheRequest),
930         &pTicketResponse,
931         &ResponseSize,
932         &SubStatus
933     );
934
935     if (FAILED(Status))
936     {
937         // if the call to LsaCallAuthenticationPackage failed we cannot
938         // perform any queries most likely because the Kerberos package
939         // is not available or we do not have access
940         bIsLsaError = TRUE;
941         goto cleanup;
942     }
943
944     if (FAILED(SubStatus)) {
945         PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
946         BOOL    Success = FALSE;
947         OSVERSIONINFOEX verinfo;
948         int supported = 0;
949
950         // SubStatus 0x8009030E is not documented.  However, it appears
951         // to mean there is no TGT
952         if (SubStatus != 0x8009030E) {
953             bIsLsaError = TRUE;
954             goto cleanup;
955         }
956
957         verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
958         GetVersionEx((OSVERSIONINFO *)&verinfo);
959         supported = (verinfo.dwMajorVersion > 5) ||
960             (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1);
961
962         // If we could not get a TGT from the cache we won't know what the
963         // Kerberos Domain should have been.  On Windows XP and 2003 Server
964         // we can extract it from the Security Logon Session Data.  However,
965         // the required fields are not supported on Windows 2000.  :(
966         if ( supported && GetSecurityLogonSessionData(&pSessionData) ) {
967             if ( pSessionData->DnsDomainName.Buffer ) {
968                 Error = ConstructTicketRequest(pSessionData->DnsDomainName,
969                                                &pTicketRequest, &RequestSize);
970                 LsaFreeReturnBuffer(pSessionData);
971                 if ( Error )
972                     goto cleanup;
973             } else {
974                 LsaFreeReturnBuffer(pSessionData);
975                 bIsLsaError = TRUE;
976                 goto cleanup;
977             }
978         } else {
979             CHAR  UserDnsDomain[256];
980             WCHAR UnicodeUserDnsDomain[256];
981             UNICODE_STRING wrapper;
982             if ( !get_STRING_from_registry(HKEY_CURRENT_USER,
983                                            "Volatile Environment",
984                                            "USERDNSDOMAIN",
985                                            UserDnsDomain,
986                                            sizeof(UserDnsDomain)
987                  ) )
988             {
989                 goto cleanup;
990             }
991
992             ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256);
993             wrapper.Buffer = UnicodeUserDnsDomain;
994             wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR);
995             wrapper.MaximumLength = 256;
996
997             Error = ConstructTicketRequest(wrapper,
998                                            &pTicketRequest, &RequestSize);
999             if ( Error )
1000                 goto cleanup;
1001         }
1002     } else {
1003         /* We have succeeded in obtaining a credential from the cache.
1004          * Assuming the enctype is one that we support and the ticket
1005          * has not expired and is not marked invalid we will use it.
1006          * Otherwise, we must create a new ticket request and obtain
1007          * a credential we can use.
1008          */
1009
1010         /* Check Supported Enctypes */
1011         if ( !enforce_tgs_enctypes ||
1012              IsMSSessionKeyNull(&pTicketResponse->Ticket.SessionKey) ||
1013              krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
1014             FILETIME Now, MinLife, EndTime, LocalEndTime;
1015             __int64  temp;
1016             // FILETIME is in units of 100 nano-seconds
1017             // If obtained tickets are either expired or have a lifetime
1018             // less than 20 minutes, retry ...
1019             GetSystemTimeAsFileTime(&Now);
1020             EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart;
1021             EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart;
1022             FileTimeToLocalFileTime(&EndTime, &LocalEndTime);
1023             temp = Now.dwHighDateTime;
1024             temp <<= 32;
1025             temp = Now.dwLowDateTime;
1026             temp += 1200 * 10000;
1027             MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF);
1028             MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
1029             if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) {
1030                 purge_cache = 1;
1031             }
1032             if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) {
1033                 ignore_cache = 1;   // invalid, need to attempt a TGT request
1034             }
1035             goto cleanup;           // we have a valid ticket, all done
1036         } else {
1037             // not supported
1038             ignore_cache = 1;
1039         }
1040
1041         Error = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName,
1042                                        &pTicketRequest, &RequestSize);
1043         if ( Error ) {
1044             goto cleanup;
1045         }
1046
1047         //
1048         // Free the previous response buffer so we can get the new response.
1049         //
1050
1051         if ( pTicketResponse ) {
1052             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1053             LsaFreeReturnBuffer(pTicketResponse);
1054             pTicketResponse = NULL;
1055         }
1056
1057         if ( purge_cache ) {
1058             //
1059             // Purge the existing tickets which we cannot use so new ones can
1060             // be requested.  It is not possible to purge just the TGT.  All
1061             // service tickets must be purged.
1062             //
1063             PurgeAllTickets(LogonHandle, PackageId);
1064         }
1065     }
1066
1067     //
1068     // Intialize the request of the request.
1069     //
1070
1071     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1072     pTicketRequest->LogonId.LowPart = 0;
1073     pTicketRequest->LogonId.HighPart = 0;
1074     // Note: pTicketRequest->TargetName set up above
1075     pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ?
1076                                     KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
1077     pTicketRequest->TicketFlags = 0L;
1078     pTicketRequest->EncryptionType = 0L;
1079
1080     Status = LsaCallAuthenticationPackage( LogonHandle,
1081                                            PackageId,
1082                                            pTicketRequest,
1083                                            RequestSize,
1084                                            &pTicketResponse,
1085                                            &ResponseSize,
1086                                            &SubStatus
1087     );
1088
1089     if (FAILED(Status) || FAILED(SubStatus))
1090     {
1091         bIsLsaError = TRUE;
1092         goto cleanup;
1093     }
1094
1095     //
1096     // Check to make sure the new tickets we received are of a type we support
1097     //
1098
1099     /* Check Supported Enctypes */
1100     if ( !enforce_tgs_enctypes ||
1101          krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
1102         goto cleanup;       // we have a valid ticket, all done
1103     }
1104
1105     if (krb5_get_tgs_ktypes(context, NULL, &etype_list)) {
1106         ptr = etype_list = NULL;
1107         etype = ENCTYPE_DES_CBC_CRC;
1108     } else {
1109         ptr = etype_list + 1;
1110         etype = *etype_list;
1111     }
1112
1113     while ( etype ) {
1114         // Try once more but this time specify the Encryption Type
1115         // (This will not store the retrieved tickets in the LSA cache unless
1116         // 0 is supported.)
1117         pTicketRequest->EncryptionType = etype;
1118         pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1119
1120         if ( pTicketResponse ) {
1121             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1122             LsaFreeReturnBuffer(pTicketResponse);
1123             pTicketResponse = NULL;
1124         }
1125
1126         Status = LsaCallAuthenticationPackage( LogonHandle,
1127                                                PackageId,
1128                                                pTicketRequest,
1129                                                RequestSize,
1130                                                &pTicketResponse,
1131                                                &ResponseSize,
1132                                                &SubStatus
1133         );
1134
1135         if (FAILED(Status) || FAILED(SubStatus))
1136         {
1137             bIsLsaError = TRUE;
1138             goto cleanup;
1139         }
1140
1141         if ( pTicketResponse->Ticket.SessionKey.KeyType == etype &&
1142              (!enforce_tgs_enctypes ||
1143               krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType)) ) {
1144             goto cleanup;       // we have a valid ticket, all done
1145         }
1146
1147         if ( ptr ) {
1148             etype = *ptr++;
1149         } else {
1150             etype = 0;
1151         }
1152     }
1153
1154 cleanup:
1155     if ( etype_list )
1156         krb5_free_enctypes(context, etype_list);
1157
1158     if ( pTicketRequest )
1159         LocalFree(pTicketRequest);
1160
1161     if (FAILED(Status) || FAILED(SubStatus))
1162     {
1163         if (bIsLsaError)
1164         {
1165             // XXX - Will be fixed later
1166             if (FAILED(Status))
1167                 ShowLsaError("LsaCallAuthenticationPackage", Status);
1168             if (FAILED(SubStatus))
1169                 ShowLsaError("LsaCallAuthenticationPackage", SubStatus);
1170         }
1171         else
1172         {
1173             ShowWinError("GetMSTGT", Status);
1174         }
1175
1176         if (pTicketResponse) {
1177             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1178             LsaFreeReturnBuffer(pTicketResponse);
1179             pTicketResponse = NULL;
1180         }
1181         return(FALSE);
1182     }
1183
1184     *ticket = &(pTicketResponse->Ticket);
1185     return(TRUE);
1186 }
1187
1188 static BOOL
1189 GetQueryTktCacheResponseEx(HANDLE LogonHandle, ULONG PackageId,
1190                            PKERB_QUERY_TKT_CACHE_EX_RESPONSE * ppResponse)
1191 {
1192     NTSTATUS Status = 0;
1193     NTSTATUS SubStatus = 0;
1194
1195     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
1196     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pQueryResponse = NULL;
1197     ULONG ResponseSize;
1198
1199     CacheRequest.MessageType = KerbQueryTicketCacheExMessage;
1200     CacheRequest.LogonId.LowPart = 0;
1201     CacheRequest.LogonId.HighPart = 0;
1202
1203     Status = LsaCallAuthenticationPackage(
1204         LogonHandle,
1205         PackageId,
1206         &CacheRequest,
1207         sizeof(CacheRequest),
1208         &pQueryResponse,
1209         &ResponseSize,
1210         &SubStatus
1211     );
1212
1213     if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
1214         *ppResponse = pQueryResponse;
1215         return TRUE;
1216     }
1217
1218     return FALSE;
1219 }
1220
1221 static BOOL
1222 GetQueryTktCacheResponseEx2(HANDLE LogonHandle, ULONG PackageId,
1223                             PKERB_QUERY_TKT_CACHE_EX2_RESPONSE * ppResponse)
1224 {
1225     NTSTATUS Status = 0;
1226     NTSTATUS SubStatus = 0;
1227
1228     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
1229     PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pQueryResponse = NULL;
1230     ULONG ResponseSize;
1231
1232     CacheRequest.MessageType = KerbQueryTicketCacheEx2Message;
1233     CacheRequest.LogonId.LowPart = 0;
1234     CacheRequest.LogonId.HighPart = 0;
1235
1236     Status = LsaCallAuthenticationPackage(
1237         LogonHandle,
1238         PackageId,
1239         &CacheRequest,
1240         sizeof(CacheRequest),
1241         &pQueryResponse,
1242         &ResponseSize,
1243         &SubStatus
1244     );
1245
1246     if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
1247         *ppResponse = pQueryResponse;
1248         return TRUE;
1249     }
1250
1251     return FALSE;
1252 }
1253
1254 static BOOL
1255 GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
1256                              krb5_context context, krb5_creds *creds,
1257                              PKERB_EXTERNAL_TICKET *ticket)
1258 {
1259     NTSTATUS Status = 0;
1260     NTSTATUS SubStatus = 0;
1261     ULONG RequestSize;
1262     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1263     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1264     ULONG ResponseSize;
1265
1266     RequestSize = sizeof(*pTicketRequest) + MAX_MSPRINC_SIZE;
1267
1268     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1269     if (!pTicketRequest)
1270         return FALSE;
1271
1272     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1273     pTicketRequest->LogonId.LowPart = 0;
1274     pTicketRequest->LogonId.HighPart = 0;
1275
1276     pTicketRequest->TargetName.Length = 0;
1277     pTicketRequest->TargetName.MaximumLength = MAX_MSPRINC_SIZE;
1278     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1279     MITPrincToMSPrinc(context, creds->server, &pTicketRequest->TargetName);
1280     pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1281     pTicketRequest->TicketFlags = creds->ticket_flags;
1282     pTicketRequest->EncryptionType = creds->keyblock.enctype;
1283
1284     Status = LsaCallAuthenticationPackage( LogonHandle,
1285                                            PackageId,
1286                                            pTicketRequest,
1287                                            RequestSize,
1288                                            &pTicketResponse,
1289                                            &ResponseSize,
1290                                            &SubStatus
1291     );
1292
1293     LocalFree(pTicketRequest);
1294
1295     if (FAILED(Status) || FAILED(SubStatus))
1296         return(FALSE);
1297
1298     /* otherwise return ticket */
1299     *ticket = &(pTicketResponse->Ticket);
1300     return(TRUE);
1301 }
1302
1303 static BOOL
1304 GetMSCacheTicketFromCacheInfoEx(HANDLE LogonHandle, ULONG PackageId,
1305                                 PKERB_TICKET_CACHE_INFO_EX tktinfo,
1306                                 PKERB_EXTERNAL_TICKET *ticket)
1307 {
1308     NTSTATUS Status = 0;
1309     NTSTATUS SubStatus = 0;
1310     ULONG RequestSize;
1311     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1312     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1313     ULONG ResponseSize;
1314
1315     RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
1316
1317     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1318     if (!pTicketRequest)
1319         return FALSE;
1320
1321     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1322     pTicketRequest->LogonId.LowPart = 0;
1323     pTicketRequest->LogonId.HighPart = 0;
1324     pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
1325     pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
1326     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1327     memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
1328     pTicketRequest->CacheOptions = 0;
1329     pTicketRequest->EncryptionType = tktinfo->EncryptionType;
1330     pTicketRequest->TicketFlags = 0;
1331     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
1332         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
1333     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
1334         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
1335     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
1336         pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
1337     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
1338         pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
1339
1340     Status = LsaCallAuthenticationPackage(
1341         LogonHandle,
1342         PackageId,
1343         pTicketRequest,
1344         RequestSize,
1345         &pTicketResponse,
1346         &ResponseSize,
1347         &SubStatus
1348     );
1349
1350     LocalFree(pTicketRequest);
1351
1352     if (FAILED(Status) || FAILED(SubStatus))
1353         return(FALSE);
1354
1355     /* otherwise return ticket */
1356     *ticket = &(pTicketResponse->Ticket);
1357
1358     /* set the initial flag if we were attempting to retrieve one
1359      * because Windows won't necessarily return the initial ticket
1360      * to us.
1361      */
1362     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
1363         (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
1364
1365     return(TRUE);
1366 }
1367
1368 static BOOL
1369 GetMSCacheTicketFromCacheInfoEx2(HANDLE LogonHandle, ULONG PackageId,
1370                                  PKERB_TICKET_CACHE_INFO_EX2 tktinfo,
1371                                  PKERB_EXTERNAL_TICKET *ticket)
1372 {
1373     NTSTATUS Status = 0;
1374     NTSTATUS SubStatus = 0;
1375     ULONG RequestSize;
1376     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1377     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1378     ULONG ResponseSize;
1379
1380     RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
1381
1382     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1383     if (!pTicketRequest)
1384         return FALSE;
1385
1386     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1387     pTicketRequest->LogonId.LowPart = 0;
1388     pTicketRequest->LogonId.HighPart = 0;
1389     pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
1390     pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
1391     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1392     memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
1393     pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1394     pTicketRequest->EncryptionType = tktinfo->SessionKeyType;
1395     pTicketRequest->TicketFlags = 0;
1396     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
1397         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
1398     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
1399         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
1400     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
1401         pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
1402     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
1403         pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
1404
1405     Status = LsaCallAuthenticationPackage(
1406         LogonHandle,
1407         PackageId,
1408         pTicketRequest,
1409         RequestSize,
1410         &pTicketResponse,
1411         &ResponseSize,
1412         &SubStatus
1413     );
1414
1415     LocalFree(pTicketRequest);
1416
1417     if (FAILED(Status) || FAILED(SubStatus))
1418         return(FALSE);
1419
1420     /* otherwise return ticket */
1421     *ticket = &(pTicketResponse->Ticket);
1422
1423
1424     /* set the initial flag if we were attempting to retrieve one
1425      * because Windows won't necessarily return the initial ticket
1426      * to us.
1427      */
1428     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
1429         (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
1430
1431     return(TRUE);
1432 }
1433
1434 static krb5_error_code KRB5_CALLCONV krb5_lcc_close
1435 (krb5_context, krb5_ccache id);
1436
1437 static krb5_error_code KRB5_CALLCONV krb5_lcc_destroy
1438 (krb5_context, krb5_ccache id);
1439
1440 static krb5_error_code KRB5_CALLCONV krb5_lcc_end_seq_get
1441 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
1442
1443 static krb5_error_code KRB5_CALLCONV krb5_lcc_generate_new
1444 (krb5_context, krb5_ccache *id);
1445
1446 static const char * KRB5_CALLCONV krb5_lcc_get_name
1447 (krb5_context, krb5_ccache id);
1448
1449 static krb5_error_code KRB5_CALLCONV krb5_lcc_get_principal
1450 (krb5_context, krb5_ccache id, krb5_principal *princ);
1451
1452 static krb5_error_code KRB5_CALLCONV krb5_lcc_initialize
1453 (krb5_context, krb5_ccache id, krb5_principal princ);
1454
1455 static krb5_error_code KRB5_CALLCONV krb5_lcc_next_cred
1456 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor,
1457  krb5_creds *creds);
1458
1459 static krb5_error_code KRB5_CALLCONV krb5_lcc_resolve
1460 (krb5_context, krb5_ccache *id, const char *residual);
1461
1462 static krb5_error_code KRB5_CALLCONV krb5_lcc_retrieve
1463 (krb5_context, krb5_ccache id, krb5_flags whichfields,
1464  krb5_creds *mcreds, krb5_creds *creds);
1465
1466 static krb5_error_code KRB5_CALLCONV krb5_lcc_start_seq_get
1467 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
1468
1469 static krb5_error_code KRB5_CALLCONV krb5_lcc_store
1470 (krb5_context, krb5_ccache id, krb5_creds *creds);
1471
1472 static krb5_error_code KRB5_CALLCONV krb5_lcc_set_flags
1473 (krb5_context, krb5_ccache id, krb5_flags flags);
1474
1475 static krb5_error_code KRB5_CALLCONV krb5_lcc_get_flags
1476 (krb5_context, krb5_ccache id, krb5_flags *flags);
1477
1478 extern const krb5_cc_ops krb5_lcc_ops;
1479
1480 krb5_error_code krb5_change_cache (void);
1481
1482 krb5_boolean
1483 krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds);
1484
1485 #define KRB5_OK 0
1486
1487 typedef struct _krb5_lcc_data {
1488     HANDLE LogonHandle;
1489     ULONG  PackageId;
1490     char * cc_name;
1491     krb5_principal princ;
1492     krb5_flags flags;
1493 } krb5_lcc_data;
1494
1495 typedef struct _krb5_lcc_cursor {
1496     union {
1497         PKERB_QUERY_TKT_CACHE_RESPONSE w2k;
1498         PKERB_QUERY_TKT_CACHE_EX_RESPONSE xp;
1499         PKERB_QUERY_TKT_CACHE_EX2_RESPONSE ex2;
1500     } response;
1501     unsigned int index;
1502     PKERB_EXTERNAL_TICKET mstgt;
1503 } krb5_lcc_cursor;
1504
1505
1506 /*
1507  * Requires:
1508  * residual is ignored
1509  *
1510  * Modifies:
1511  * id
1512  *
1513  * Effects:
1514  * Acccess the MS Kerberos LSA cache in the current logon session
1515  * Ignore the residual.
1516  *
1517  * Returns:
1518  * A filled in krb5_ccache structure "id".
1519  *
1520  * Errors:
1521  * KRB5_CC_NOMEM - there was insufficient memory to allocate the
1522  *
1523  *              krb5_ccache.  id is undefined.
1524  * permission errors
1525  */
1526 static krb5_error_code KRB5_CALLCONV
1527 krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
1528 {
1529     krb5_ccache lid;
1530     krb5_lcc_data *data;
1531     HANDLE LogonHandle;
1532     ULONG  PackageId, i;
1533     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
1534
1535     if (!PackageConnectLookup(&LogonHandle, &PackageId))
1536         return KRB5_FCC_NOFILE;
1537
1538     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
1539     if (lid == NULL) {
1540         LsaDeregisterLogonProcess(LogonHandle);
1541         return KRB5_CC_NOMEM;
1542     }
1543
1544     lid->ops = &krb5_lcc_ops;
1545
1546     lid->data = (krb5_pointer) malloc(sizeof(krb5_lcc_data));
1547     if (lid->data == NULL) {
1548         free(lid);
1549         LsaDeregisterLogonProcess(LogonHandle);
1550         return KRB5_CC_NOMEM;
1551     }
1552
1553     lid->magic = KV5M_CCACHE;
1554     data = (krb5_lcc_data *)lid->data;
1555     data->LogonHandle = LogonHandle;
1556     data->PackageId = PackageId;
1557     data->princ = NULL;
1558     data->flags = 0;
1559
1560     data->cc_name = (char *)malloc(strlen(residual)+1);
1561     if (data->cc_name == NULL) {
1562         free(lid->data);
1563         free(lid);
1564         LsaDeregisterLogonProcess(LogonHandle);
1565         return KRB5_CC_NOMEM;
1566     }
1567     strcpy(data->cc_name, residual);
1568
1569     /* If there are already tickets present, grab a client principal name. */
1570     if (GetQueryTktCacheResponseEx(LogonHandle, PackageId, &pResponse)) {
1571         /* Take the first client principal we find; they should all be the
1572          * same anyway. */
1573         for (i = 0; i < pResponse->CountOfTickets; i++) {
1574             if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
1575                                         &pResponse->Tickets[i].ClientRealm,
1576                                         context, &data->princ))
1577                 break;
1578
1579         }
1580         LsaFreeReturnBuffer(pResponse);
1581     }
1582
1583     /*
1584      * other routines will get errors on open, and callers must expect them,
1585      * if cache is non-existent/unusable
1586      */
1587     *id = lid;
1588     return KRB5_OK;
1589 }
1590
1591 /*
1592  *  return success although we do not do anything
1593  *  We should delete all tickets belonging to the specified principal
1594  */
1595
1596 static krb5_error_code KRB5_CALLCONV
1597 krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
1598                      krb5_creds *creds);
1599
1600 static krb5_error_code KRB5_CALLCONV
1601 krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
1602 {
1603     krb5_cc_cursor cursor;
1604     krb5_error_code code;
1605     krb5_creds cred;
1606
1607     code = krb5_cc_start_seq_get(context, id, &cursor);
1608     if (code) {
1609         if (code == KRB5_CC_NOTFOUND)
1610             return KRB5_OK;
1611         return code;
1612     }
1613
1614     while ( !(code = krb5_cc_next_cred(context, id, &cursor, &cred)) )
1615     {
1616         if ( krb5_principal_compare(context, princ, cred.client) ) {
1617             code = krb5_lcc_remove_cred(context, id, 0, &cred);
1618         }
1619         krb5_free_cred_contents(context, &cred);
1620     }
1621
1622     if (code == KRB5_CC_END || code == KRB5_CC_NOTFOUND)
1623     {
1624         krb5_cc_end_seq_get(context, id, &cursor);
1625         return KRB5_OK;
1626     }
1627     return code;
1628 }
1629
1630 /*
1631  * Modifies:
1632  * id
1633  *
1634  * Effects:
1635  * Closes the microsoft lsa cache, invalidates the id, and frees any resources
1636  * associated with the cache.
1637  */
1638 static krb5_error_code KRB5_CALLCONV
1639 krb5_lcc_close(krb5_context context, krb5_ccache id)
1640 {
1641     int closeval = KRB5_OK;
1642     krb5_lcc_data *data;
1643
1644     if (id) {
1645         data = (krb5_lcc_data *) id->data;
1646
1647         if (data) {
1648             LsaDeregisterLogonProcess(data->LogonHandle);
1649             if (data->cc_name)
1650                 free(data->cc_name);
1651             free(data);
1652         }
1653         free(id);
1654     }
1655     return closeval;
1656 }
1657
1658 /*
1659  * Effects:
1660  * Destroys the contents of id.
1661  *
1662  * Errors:
1663  * system errors
1664  */
1665 static krb5_error_code KRB5_CALLCONV
1666 krb5_lcc_destroy(krb5_context context, krb5_ccache id)
1667 {
1668     krb5_lcc_data *data;
1669
1670     if (id) {
1671         data = (krb5_lcc_data *) id->data;
1672
1673         return PurgeAllTickets(data->LogonHandle, data->PackageId) ? KRB5_OK : KRB5_FCC_INTERNAL;
1674     }
1675     return KRB5_FCC_INTERNAL;
1676 }
1677
1678 /*
1679  * Effects:
1680  * Prepares for a sequential search of the credentials cache.
1681  * Returns a krb5_cc_cursor to be used with krb5_lcc_next_cred and
1682  * krb5_lcc_end_seq_get.
1683  *
1684  * If the cache is modified between the time of this call and the time
1685  * of the final krb5_lcc_end_seq_get, the results are undefined.
1686  *
1687  * Errors:
1688  * KRB5_CC_NOMEM
1689  * KRB5_FCC_INTERNAL - system errors
1690  */
1691 static krb5_error_code KRB5_CALLCONV
1692 krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
1693 {
1694     krb5_lcc_cursor *lcursor;
1695     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1696
1697     lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor));
1698     if (lcursor == NULL) {
1699         *cursor = 0;
1700         return KRB5_CC_NOMEM;
1701     }
1702
1703     /*
1704      * obtain a tgt to refresh the ccache in case the ticket is expired
1705      */
1706     if (!GetMSTGT(context, data->LogonHandle, data->PackageId, &lcursor->mstgt, TRUE)) {
1707         free(lcursor);
1708         *cursor = 0;
1709         return KRB5_CC_NOTFOUND;
1710     }
1711
1712     if ( does_query_ticket_cache_ex2() ) {
1713         if (!GetQueryTktCacheResponseEx2(data->LogonHandle, data->PackageId,
1714                                          &lcursor->response.ex2)) {
1715             LsaFreeReturnBuffer(lcursor->mstgt);
1716             free(lcursor);
1717             *cursor = 0;
1718             return KRB5_FCC_INTERNAL;
1719         }
1720     } else
1721         if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1722                                         &lcursor->response.xp)) {
1723             LsaFreeReturnBuffer(lcursor->mstgt);
1724             free(lcursor);
1725             *cursor = 0;
1726             return KRB5_FCC_INTERNAL;
1727         }
1728     lcursor->index = 0;
1729     *cursor = (krb5_cc_cursor) lcursor;
1730     return KRB5_OK;
1731 }
1732
1733
1734 /*
1735  * Requires:
1736  * cursor is a krb5_cc_cursor originally obtained from
1737  * krb5_lcc_start_seq_get.
1738  *
1739  * Modifes:
1740  * cursor
1741  *
1742  * Effects:
1743  * Fills in creds with the TGT obtained from the MS LSA
1744  *
1745  * The cursor is updated to indicate TGT retrieval
1746  *
1747  * Errors:
1748  * KRB5_CC_END
1749  * KRB5_FCC_INTERNAL - system errors
1750  */
1751 static krb5_error_code KRB5_CALLCONV
1752 krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, krb5_creds *creds)
1753 {
1754     krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
1755     krb5_lcc_data *data;
1756     KERB_EXTERNAL_TICKET *msticket;
1757     krb5_error_code  retval = KRB5_OK;
1758
1759     data = (krb5_lcc_data *)id->data;
1760
1761 next_cred:
1762     if ( does_query_ticket_cache_ex2() ) {
1763         if ( lcursor->index >= lcursor->response.ex2->CountOfTickets ) {
1764             if (retval == KRB5_OK)
1765                 return KRB5_CC_END;
1766             else {
1767                 LsaFreeReturnBuffer(lcursor->mstgt);
1768                 LsaFreeReturnBuffer(lcursor->response.ex2);
1769                 free(*cursor);
1770                 *cursor = 0;
1771                 return retval;
1772             }
1773         }
1774
1775         if ( data->flags & KRB5_TC_NOTICKET ) {
1776             if (!CacheInfoEx2ToMITCred( &lcursor->response.ex2->Tickets[lcursor->index++],
1777                                         context, creds)) {
1778                 retval = KRB5_FCC_INTERNAL;
1779                 goto next_cred;
1780             }
1781             return KRB5_OK;
1782         } else {
1783             if (!GetMSCacheTicketFromCacheInfoEx2(data->LogonHandle,
1784                                                   data->PackageId,
1785                                                   &lcursor->response.ex2->Tickets[lcursor->index++],&msticket)) {
1786                 retval = KRB5_FCC_INTERNAL;
1787                 goto next_cred;
1788             }
1789         }
1790     } else {
1791         if (lcursor->index >= lcursor->response.xp->CountOfTickets) {
1792             if (retval == KRB5_OK) {
1793                 return KRB5_CC_END;
1794             } else {
1795                 LsaFreeReturnBuffer(lcursor->mstgt);
1796                 LsaFreeReturnBuffer(lcursor->response.xp);
1797                 free(*cursor);
1798                 *cursor = 0;
1799                 return retval;
1800             }
1801         }
1802
1803         if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
1804                                              data->PackageId,
1805                                              &lcursor->response.xp->Tickets[lcursor->index++],
1806                                              &msticket)) {
1807             retval = KRB5_FCC_INTERNAL;
1808             goto next_cred;
1809         }
1810     }
1811
1812     /* Don't return tickets with NULL Session Keys */
1813     if ( IsMSSessionKeyNull(&msticket->SessionKey) ) {
1814         LsaFreeReturnBuffer(msticket);
1815         goto next_cred;
1816     }
1817
1818     /* convert the ticket */
1819     if ( does_query_ticket_cache_ex2() ) {
1820         if (!MSCredToMITCred(msticket, lcursor->response.ex2->Tickets[lcursor->index-1].ClientRealm, context, creds))
1821             retval = KRB5_FCC_INTERNAL;
1822     } else {
1823         if (!MSCredToMITCred(msticket,
1824                              lcursor->response.xp->Tickets[lcursor->index -
1825                                  1].ClientRealm,
1826                              context, creds))
1827             retval = KRB5_FCC_INTERNAL;
1828     }
1829     LsaFreeReturnBuffer(msticket);
1830     return retval;
1831 }
1832
1833 /*
1834  * Requires:
1835  * cursor is a krb5_cc_cursor originally obtained from
1836  * krb5_lcc_start_seq_get.
1837  *
1838  * Modifies:
1839  * id, cursor
1840  *
1841  * Effects:
1842  * Finishes sequential processing of the file credentials ccache id,
1843  * and invalidates the cursor (it must never be used after this call).
1844  */
1845 /* ARGSUSED */
1846 static krb5_error_code KRB5_CALLCONV
1847 krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
1848 {
1849     krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
1850
1851     if ( lcursor ) {
1852         LsaFreeReturnBuffer(lcursor->mstgt);
1853         if ( does_query_ticket_cache_ex2() )
1854             LsaFreeReturnBuffer(lcursor->response.ex2);
1855         else
1856             LsaFreeReturnBuffer(lcursor->response.xp);
1857         free(*cursor);
1858     }
1859     *cursor = 0;
1860
1861     return KRB5_OK;
1862 }
1863
1864
1865 /*
1866  * Errors:
1867  * KRB5_CC_READONLY - not supported
1868  */
1869 static krb5_error_code KRB5_CALLCONV
1870 krb5_lcc_generate_new (krb5_context context, krb5_ccache *id)
1871 {
1872     return KRB5_CC_READONLY;
1873 }
1874
1875 /*
1876  * Requires:
1877  * id is a ms lsa credential cache
1878  *
1879  * Returns:
1880  *   The ccname specified during the krb5_lcc_resolve call
1881  */
1882 static const char * KRB5_CALLCONV
1883 krb5_lcc_get_name (krb5_context context, krb5_ccache id)
1884 {
1885
1886     if ( !id )
1887         return "";
1888
1889     return (char *) ((krb5_lcc_data *) id->data)->cc_name;
1890 }
1891
1892 /*
1893  * Modifies:
1894  * id, princ
1895  *
1896  * Effects:
1897  * Retrieves the primary principal from id, as set with
1898  * krb5_lcc_initialize.  The principal is returned is allocated
1899  * storage that must be freed by the caller via krb5_free_principal.
1900  *
1901  * Errors:
1902  * system errors
1903  * KRB5_CC_NOT_KTYPE
1904  */
1905 static krb5_error_code KRB5_CALLCONV
1906 krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
1907 {
1908     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
1909     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1910     ULONG  i;
1911
1912     /* obtain principal */
1913     if (data->princ)
1914         return krb5_copy_principal(context, data->princ, princ);
1915     else {
1916         if (GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1917                                        &pResponse)) {
1918             /* Take the first client principal we find; they should all be the
1919              * same anyway. */
1920             for (i = 0; i < pResponse->CountOfTickets; i++) {
1921                 if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
1922                                             &pResponse->Tickets[i].ClientRealm,
1923                                             context, &data->princ))
1924                     break;
1925             }
1926             LsaFreeReturnBuffer(pResponse);
1927             if (data->princ)
1928                 return krb5_copy_principal(context, data->princ, princ);
1929         }
1930     }
1931     return KRB5_CC_NOTFOUND;
1932 }
1933
1934
1935 static krb5_error_code KRB5_CALLCONV
1936 krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
1937                   krb5_creds *mcreds, krb5_creds *creds)
1938 {
1939     krb5_error_code kret = KRB5_OK;
1940     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1941     KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0, *mstmp = 0;
1942     krb5_creds * mcreds_noflags = 0;
1943     krb5_creds   fetchcreds;
1944     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse = 0;
1945     unsigned int i;
1946
1947     memset(&fetchcreds, 0, sizeof(krb5_creds));
1948
1949     /* first try to find out if we have an existing ticket which meets the requirements */
1950     kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1951                                        creds);
1952     /* This sometimes returns a zero-length ticket; work around it. */
1953     if ( !kret && creds->ticket.length > 0 )
1954         return KRB5_OK;
1955
1956     /* if not, we must try to get a ticket without specifying any flags or etypes */
1957     kret = krb5_copy_creds(context, mcreds, &mcreds_noflags);
1958     if (kret)
1959         goto cleanup;
1960     mcreds_noflags->ticket_flags = 0;
1961     mcreds_noflags->keyblock.enctype = 0;
1962
1963     if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds_noflags, &msticket)) {
1964         kret = KRB5_CC_NOTFOUND;
1965         goto cleanup;
1966     }
1967
1968     /* try again to find out if we have an existing ticket which meets the requirements */
1969     kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1970                                        creds);
1971     /* This sometimes returns a zero-length ticket; work around it. */
1972     if ( !kret && creds->ticket.length > 0 )
1973         goto cleanup;
1974
1975     /* if not, obtain a ticket using the request flags and enctype even though it may not
1976      * be stored in the LSA cache for future use.
1977      */
1978     if ( msticket ) {
1979         LsaFreeReturnBuffer(msticket);
1980         msticket = 0;
1981     }
1982
1983     if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds, &msticket)) {
1984         kret = KRB5_CC_NOTFOUND;
1985         goto cleanup;
1986     }
1987
1988     /* convert the ticket */
1989     /*
1990      * We can obtain the correct client realm for a ticket by walking the
1991      * cache contents until we find the matching service ticket.
1992      */
1993
1994     if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1995         &pResponse)) {
1996         kret = KRB5_FCC_INTERNAL;
1997         goto cleanup;
1998     }
1999
2000     for (i = 0; i < pResponse->CountOfTickets; i++) {
2001         if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
2002                                              data->PackageId,
2003                                              &pResponse->Tickets[i], &mstmp)) {
2004             continue;
2005         }
2006
2007         if (KerbExternalTicketMatch(msticket,mstmp))
2008             break;
2009
2010         LsaFreeReturnBuffer(mstmp);
2011         mstmp = 0;
2012     }
2013
2014     if (!MSCredToMITCred(msticket, mstmp ?
2015                          pResponse->Tickets[i].ClientRealm :
2016                          msticket->DomainName, context, &fetchcreds)) {
2017         LsaFreeReturnBuffer(pResponse);
2018         kret = KRB5_FCC_INTERNAL;
2019         goto cleanup;
2020     }
2021     LsaFreeReturnBuffer(pResponse);
2022
2023
2024     /* check to see if this ticket matches the request using logic from
2025      * k5_cc_retrieve_cred_default()
2026      */
2027     if ( krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds) ) {
2028         *creds = fetchcreds;
2029     } else {
2030         krb5_free_cred_contents(context, &fetchcreds);
2031         kret = KRB5_CC_NOTFOUND;
2032     }
2033
2034 cleanup:
2035     if ( mstmp )
2036         LsaFreeReturnBuffer(mstmp);
2037     if ( mstgt )
2038         LsaFreeReturnBuffer(mstgt);
2039     if ( msticket )
2040         LsaFreeReturnBuffer(msticket);
2041     if ( mcreds_noflags )
2042         krb5_free_creds(context, mcreds_noflags);
2043     return kret;
2044 }
2045
2046
2047 /*
2048  * We can't write to the MS LSA cache.  So we request the cache to obtain a ticket for the same
2049  * principal in the hope that next time the application requires a ticket for the service it
2050  * is attempt to store, the retrieved ticket will be good enough.
2051  *
2052  * Errors:
2053  * KRB5_CC_READONLY - not supported
2054  */
2055 static krb5_error_code KRB5_CALLCONV
2056 krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
2057 {
2058     krb5_error_code kret = KRB5_OK;
2059     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2060     KERB_EXTERNAL_TICKET *msticket = 0, *msticket2 = 0;
2061     krb5_creds * creds_noflags = 0;
2062
2063     if (krb5_is_config_principal(context, creds->server)) {
2064         /* mslsa cannot store config creds, so we have to bail.
2065          * The 'right' thing to do would be to return an appropriate error,
2066          * but that would require modifying the calling code to check
2067          * for that error and ignore it.
2068          */
2069         return KRB5_OK;
2070     }
2071
2072     if (KerbSubmitTicket( data->LogonHandle, data->PackageId, context, creds ))
2073         return KRB5_OK;
2074
2075     /* If not, lets try to obtain a matching ticket from the KDC */
2076     if ( creds->ticket_flags != 0 && creds->keyblock.enctype != 0 ) {
2077         /* if not, we must try to get a ticket without specifying any flags or etypes */
2078         kret = krb5_copy_creds(context, creds, &creds_noflags);
2079         if (kret == 0) {
2080             creds_noflags->ticket_flags = 0;
2081             creds_noflags->keyblock.enctype = 0;
2082
2083             GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds_noflags, &msticket2);
2084             krb5_free_creds(context, creds_noflags);
2085         }
2086     }
2087
2088     GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds, &msticket);
2089     if (msticket || msticket2) {
2090         if (msticket)
2091             LsaFreeReturnBuffer(msticket);
2092         if (msticket2)
2093             LsaFreeReturnBuffer(msticket2);
2094         return KRB5_OK;
2095     }
2096     return KRB5_CC_READONLY;
2097 }
2098
2099 /*
2100  * Individual credentials can be implemented differently depending
2101  * on the operating system version.  (undocumented.)
2102  *
2103  * Errors:
2104  *    KRB5_CC_READONLY:
2105  */
2106 static krb5_error_code KRB5_CALLCONV
2107 krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
2108                      krb5_creds *creds)
2109 {
2110     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2111
2112     if (PurgeTicketEx(data->LogonHandle, data->PackageId, context, flags,
2113                       creds))
2114         return KRB5_OK;
2115
2116     return KRB5_CC_READONLY;
2117 }
2118
2119
2120 /*
2121  * Effects:
2122  *   Set
2123  */
2124 static krb5_error_code KRB5_CALLCONV
2125 krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
2126 {
2127     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2128
2129     data->flags = flags;
2130     return KRB5_OK;
2131 }
2132
2133 static krb5_error_code KRB5_CALLCONV
2134 krb5_lcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
2135 {
2136     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2137
2138     *flags = data->flags;
2139     return KRB5_OK;
2140 }
2141
2142 static krb5_error_code KRB5_CALLCONV
2143 krb5_lcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
2144 {
2145     krb5_cc_ptcursor new_cursor = (krb5_cc_ptcursor )malloc(sizeof(*new_cursor));
2146     if (!new_cursor)
2147         return ENOMEM;
2148     new_cursor->ops = &krb5_lcc_ops;
2149     new_cursor->data = (krb5_pointer)(1);
2150     *cursor = new_cursor;
2151     new_cursor = NULL;
2152     return 0;
2153 }
2154
2155 static krb5_error_code KRB5_CALLCONV
2156 krb5_lcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *ccache)
2157 {
2158     krb5_error_code code = 0;
2159     *ccache = 0;
2160     if (cursor->data == NULL)
2161         return 0;
2162
2163     cursor->data = NULL;
2164     if ((code = krb5_lcc_resolve(context, ccache, ""))) {
2165         if (code != KRB5_FCC_NOFILE)
2166             /* Note that we only want to return serious errors.
2167              * Any non-zero return code will prevent the cccol iterator
2168              * from advancing to the next ccache collection. */
2169             return code;
2170     }
2171     return 0;
2172 }
2173
2174 static krb5_error_code KRB5_CALLCONV
2175 krb5_lcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
2176 {
2177     if (*cursor) {
2178         free(*cursor);
2179         *cursor = NULL;
2180     }
2181     return 0;
2182 }
2183
2184 const krb5_cc_ops krb5_lcc_ops = {
2185     0,
2186     "MSLSA",
2187     krb5_lcc_get_name,
2188     krb5_lcc_resolve,
2189     krb5_lcc_generate_new,
2190     krb5_lcc_initialize,
2191     krb5_lcc_destroy,
2192     krb5_lcc_close,
2193     krb5_lcc_store,
2194     krb5_lcc_retrieve,
2195     krb5_lcc_get_principal,
2196     krb5_lcc_start_seq_get,
2197     krb5_lcc_next_cred,
2198     krb5_lcc_end_seq_get,
2199     krb5_lcc_remove_cred,
2200     krb5_lcc_set_flags,
2201     krb5_lcc_get_flags,
2202     krb5_lcc_ptcursor_new,
2203     krb5_lcc_ptcursor_next,
2204     krb5_lcc_ptcursor_free,
2205     NULL, /* move */
2206     NULL, /* lastchange */
2207     NULL, /* wasdefault */
2208     NULL, /* lock */
2209     NULL, /* unlock */
2210     NULL, /* switch_to */
2211 };
2212 #endif /* _WIN32 */