Imported Upstream version 1.15.1
[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     memset(creds->addresses, 0, sizeof(krb5_address *));
389
390     return TRUE;
391 }
392
393 static BOOL
394 PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId)
395 {
396     LSA_STRING Name;
397     NTSTATUS Status;
398
399     Status = LsaConnectUntrusted(
400         pLogonHandle
401     );
402
403     if (FAILED(Status))
404     {
405         ShowLsaError("LsaConnectUntrusted", Status);
406         return FALSE;
407     }
408
409     Name.Buffer = MICROSOFT_KERBEROS_NAME_A;
410     Name.Length = strlen(Name.Buffer);
411     Name.MaximumLength = Name.Length + 1;
412
413     Status = LsaLookupAuthenticationPackage(
414         *pLogonHandle,
415         &Name,
416         pPackageId
417     );
418
419     if (FAILED(Status))
420     {
421         ShowLsaError("LsaLookupAuthenticationPackage", Status);
422         return FALSE;
423     }
424
425     return TRUE;
426
427 }
428
429 /*
430  * This runtime check is only needed on Windows XP and Server 2003.
431  * It can safely be removed when we no longer wish to support any
432  * versions of those platforms.
433  */
434 static BOOL
435 does_query_ticket_cache_ex2 (void)
436 {
437     static BOOL fChecked = FALSE;
438     static BOOL fEx2Response = FALSE;
439
440     if (!fChecked)
441     {
442         NTSTATUS Status = 0;
443         NTSTATUS SubStatus = 0;
444         HANDLE LogonHandle;
445         ULONG  PackageId;
446         ULONG RequestSize;
447         PKERB_QUERY_TKT_CACHE_REQUEST pCacheRequest = NULL;
448         PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pCacheResponse = NULL;
449         ULONG ResponseSize;
450
451         RequestSize = sizeof(*pCacheRequest) + 1;
452
453         if (!PackageConnectLookup(&LogonHandle, &PackageId))
454             return FALSE;
455
456         pCacheRequest = (PKERB_QUERY_TKT_CACHE_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
457         if (!pCacheRequest) {
458             LsaDeregisterLogonProcess(LogonHandle);
459             return FALSE;
460         }
461
462         pCacheRequest->MessageType = KerbQueryTicketCacheEx2Message;
463         pCacheRequest->LogonId.LowPart = 0;
464         pCacheRequest->LogonId.HighPart = 0;
465
466         Status = LsaCallAuthenticationPackage( LogonHandle,
467                                                PackageId,
468                                                pCacheRequest,
469                                                RequestSize,
470                                                &pCacheResponse,
471                                                &ResponseSize,
472                                                &SubStatus
473         );
474
475         LocalFree(pCacheRequest);
476         LsaDeregisterLogonProcess(LogonHandle);
477
478         if (!(FAILED(Status) || FAILED(SubStatus))) {
479             LsaFreeReturnBuffer(pCacheResponse);
480             fEx2Response = TRUE;
481         }
482         fChecked = TRUE;
483     }
484
485     return fEx2Response;
486 }
487
488 static DWORD
489 ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2)
490 {
491     //
492     // The buffers for Source1 and Source2 cannot overlap pTarget's
493     // buffer.  Source1.Length + Source2.Length must be <= 0xFFFF,
494     // otherwise we overflow...
495     //
496
497     USHORT TotalSize = Source1.Length + Source2.Length;
498     PBYTE buffer = (PBYTE) pTarget->Buffer;
499
500     if (TotalSize > pTarget->MaximumLength)
501         return ERROR_INSUFFICIENT_BUFFER;
502
503     if ( pTarget->Buffer != Source1.Buffer )
504         memcpy(buffer, Source1.Buffer, Source1.Length);
505     memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length);
506
507     pTarget->Length = TotalSize;
508     return ERROR_SUCCESS;
509 }
510
511 static BOOL
512 get_STRING_from_registry(HKEY hBaseKey, char * key, char * value, char * outbuf, DWORD  outlen)
513 {
514     HKEY hKey;
515     DWORD dwCount;
516     LONG rc;
517
518     if (!outbuf || outlen == 0)
519         return FALSE;
520
521     rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey);
522     if (rc)
523         return FALSE;
524
525     dwCount = outlen;
526     rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount);
527     RegCloseKey(hKey);
528
529     return rc?FALSE:TRUE;
530 }
531
532 static BOOL
533 GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData)
534 {
535     NTSTATUS Status = 0;
536     HANDLE  TokenHandle;
537     TOKEN_STATISTICS Stats;
538     DWORD   ReqLen;
539     BOOL    Success;
540
541     if (!ppSessionData)
542         return FALSE;
543     *ppSessionData = NULL;
544
545     Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle );
546     if ( !Success )
547         return FALSE;
548
549     Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen );
550     CloseHandle( TokenHandle );
551     if ( !Success )
552         return FALSE;
553
554     Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData );
555     if ( FAILED(Status) || !ppSessionData )
556         return FALSE;
557
558     return TRUE;
559 }
560
561 static DWORD
562 ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest, ULONG * outSize)
563 {
564     DWORD Error;
565     UNICODE_STRING TargetPrefix;
566     USHORT TargetSize;
567     ULONG RequestSize;
568     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
569
570     *outRequest = NULL;
571     *outSize = 0;
572
573     //
574     // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we
575     // can easily concatenate it later.
576     //
577
578     TargetPrefix.Buffer = L"krbtgt/";
579     TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR);
580     TargetPrefix.MaximumLength = TargetPrefix.Length;
581
582     //
583     // We will need to concatenate the "krbtgt/" prefix and the
584     // Logon Session's DnsDomainName into our request's target name.
585     //
586     // Therefore, first compute the necessary buffer size for that.
587     //
588     // Note that we might theoretically have integer overflow.
589     //
590
591     TargetSize = TargetPrefix.Length + DomainName.Length;
592
593     //
594     // The ticket request buffer needs to be a single buffer.  That buffer
595     // needs to include the buffer for the target name.
596     //
597
598     RequestSize = sizeof(*pTicketRequest) + TargetSize;
599
600     //
601     // Allocate the request buffer and make sure it's zero-filled.
602     //
603
604     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
605     if (!pTicketRequest)
606         return GetLastError();
607
608     //
609     // Concatenate the target prefix with the previous reponse's
610     // target domain.
611     //
612
613     pTicketRequest->TargetName.Length = 0;
614     pTicketRequest->TargetName.MaximumLength = TargetSize;
615     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
616     Error = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName),
617                                       TargetPrefix,
618                                       DomainName);
619     *outRequest = pTicketRequest;
620     *outSize    = RequestSize;
621     return Error;
622 }
623
624 static BOOL
625 PurgeAllTickets(HANDLE LogonHandle, ULONG  PackageId)
626 {
627     NTSTATUS Status = 0;
628     NTSTATUS SubStatus = 0;
629     KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest;
630
631     PurgeRequest.MessageType = KerbPurgeTicketCacheMessage;
632     PurgeRequest.LogonId.LowPart = 0;
633     PurgeRequest.LogonId.HighPart = 0;
634     PurgeRequest.ServerName.Buffer = L"";
635     PurgeRequest.ServerName.Length = 0;
636     PurgeRequest.ServerName.MaximumLength = 0;
637     PurgeRequest.RealmName.Buffer = L"";
638     PurgeRequest.RealmName.Length = 0;
639     PurgeRequest.RealmName.MaximumLength = 0;
640     Status = LsaCallAuthenticationPackage(LogonHandle,
641                                           PackageId,
642                                           &PurgeRequest,
643                                           sizeof(PurgeRequest),
644                                           NULL,
645                                           NULL,
646                                           &SubStatus
647     );
648     if (FAILED(Status) || FAILED(SubStatus))
649         return FALSE;
650     return TRUE;
651 }
652
653 static BOOL
654 PurgeTicketEx(HANDLE LogonHandle, ULONG  PackageId,
655               krb5_context context, krb5_flags flags, krb5_creds *cred)
656 {
657     NTSTATUS Status = 0;
658     NTSTATUS SubStatus = 0;
659     KERB_PURGE_TKT_CACHE_EX_REQUEST * pPurgeRequest;
660     DWORD dwRequestLen = sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 4096;
661     char * cname = NULL, * crealm = NULL;
662     char * sname = NULL, * srealm = NULL;
663
664     if (krb5_unparse_name(context, cred->client, &cname))
665         return FALSE;
666
667     if (krb5_unparse_name(context, cred->server, &sname)) {
668         krb5_free_unparsed_name(context, cname);
669         return FALSE;
670     }
671
672     pPurgeRequest = malloc(dwRequestLen);
673     if ( pPurgeRequest == NULL )
674         return FALSE;
675     memset(pPurgeRequest, 0, dwRequestLen);
676
677     crealm = strrchr(cname, '@');
678     *crealm = '\0';
679     crealm++;
680
681     srealm = strrchr(sname, '@');
682     *srealm = '\0';
683     srealm++;
684
685     pPurgeRequest->MessageType = KerbPurgeTicketCacheExMessage;
686     pPurgeRequest->LogonId.LowPart = 0;
687     pPurgeRequest->LogonId.HighPart = 0;
688     pPurgeRequest->Flags = 0;
689     pPurgeRequest->TicketTemplate.ClientName.Buffer = (PWSTR)((CHAR *)pPurgeRequest + sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST));
690     pPurgeRequest->TicketTemplate.ClientName.Length = strlen(cname)*sizeof(WCHAR);
691     pPurgeRequest->TicketTemplate.ClientName.MaximumLength = 256;
692     ANSIToUnicode(cname, pPurgeRequest->TicketTemplate.ClientName.Buffer,
693                   pPurgeRequest->TicketTemplate.ClientName.MaximumLength);
694
695     pPurgeRequest->TicketTemplate.ClientRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 512);
696     pPurgeRequest->TicketTemplate.ClientRealm.Length = strlen(crealm)*sizeof(WCHAR);
697     pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength = 256;
698     ANSIToUnicode(crealm, pPurgeRequest->TicketTemplate.ClientRealm.Buffer,
699                   pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength);
700
701     pPurgeRequest->TicketTemplate.ServerName.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1024);
702     pPurgeRequest->TicketTemplate.ServerName.Length = strlen(sname)*sizeof(WCHAR);
703     pPurgeRequest->TicketTemplate.ServerName.MaximumLength = 256;
704     ANSIToUnicode(sname, pPurgeRequest->TicketTemplate.ServerName.Buffer,
705                   pPurgeRequest->TicketTemplate.ServerName.MaximumLength);
706
707     pPurgeRequest->TicketTemplate.ServerRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1536);
708     pPurgeRequest->TicketTemplate.ServerRealm.Length = strlen(srealm)*sizeof(WCHAR);
709     pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength = 256;
710     ANSIToUnicode(srealm, pPurgeRequest->TicketTemplate.ServerRealm.Buffer,
711                   pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength);
712
713     pPurgeRequest->TicketTemplate.StartTime;
714     pPurgeRequest->TicketTemplate.EndTime;
715     pPurgeRequest->TicketTemplate.RenewTime;
716     pPurgeRequest->TicketTemplate.EncryptionType = cred->keyblock.enctype;
717     pPurgeRequest->TicketTemplate.TicketFlags = flags;
718
719     Status = LsaCallAuthenticationPackage( LogonHandle,
720                                            PackageId,
721                                            pPurgeRequest,
722                                            dwRequestLen,
723                                            NULL,
724                                            NULL,
725                                            &SubStatus
726     );
727     free(pPurgeRequest);
728     krb5_free_unparsed_name(context,cname);
729     krb5_free_unparsed_name(context,sname);
730
731     if (FAILED(Status) || FAILED(SubStatus))
732         return FALSE;
733     return TRUE;
734 }
735
736 static BOOL
737 KerbSubmitTicket( HANDLE LogonHandle, ULONG  PackageId,
738                   krb5_context context, krb5_creds *cred)
739 {
740     NTSTATUS Status = 0;
741     NTSTATUS SubStatus = 0;
742     KERB_SUBMIT_TKT_REQUEST * pSubmitRequest;
743     DWORD dwRequestLen;
744     krb5_auth_context auth_context;
745     krb5_keyblock * keyblock = 0;
746     krb5_replay_data replaydata;
747     krb5_data * krb_cred = 0;
748     krb5_error_code rc;
749
750     if (krb5_auth_con_init(context, &auth_context)) {
751         return FALSE;
752     }
753
754     if (krb5_auth_con_setflags(context, auth_context,
755                                KRB5_AUTH_CONTEXT_RET_TIME)) {
756         return FALSE;
757     }
758
759     krb5_auth_con_getsendsubkey(context, auth_context, &keyblock);
760     if (keyblock == NULL)
761         krb5_auth_con_getkey(context, auth_context, &keyblock);
762
763     /* make up a key, any key, that can be used to generate the
764      * encrypted KRB_CRED pdu.  The Vista release LSA requires
765      * that an enctype other than NULL be used. */
766     if (keyblock == NULL) {
767         keyblock = (krb5_keyblock *)malloc(sizeof(krb5_keyblock));
768         keyblock->enctype = ENCTYPE_ARCFOUR_HMAC;
769         keyblock->length = 16;
770         keyblock->contents = (krb5_octet *)malloc(16);
771         keyblock->contents[0] = 0xde;
772         keyblock->contents[1] = 0xad;
773         keyblock->contents[2] = 0xbe;
774         keyblock->contents[3] = 0xef;
775         keyblock->contents[4] = 0xfe;
776         keyblock->contents[5] = 0xed;
777         keyblock->contents[6] = 0xf0;
778         keyblock->contents[7] = 0xd;
779         keyblock->contents[8] = 0xde;
780         keyblock->contents[9] = 0xad;
781         keyblock->contents[10] = 0xbe;
782         keyblock->contents[11] = 0xef;
783         keyblock->contents[12] = 0xfe;
784         keyblock->contents[13] = 0xed;
785         keyblock->contents[14] = 0xf0;
786         keyblock->contents[15] = 0xd;
787         krb5_auth_con_setsendsubkey(context, auth_context, keyblock);
788     }
789     rc = krb5_mk_1cred(context, auth_context, cred, &krb_cred, &replaydata);
790     if (rc) {
791         krb5_auth_con_free(context, auth_context);
792         if (keyblock)
793             krb5_free_keyblock(context, keyblock);
794         if (krb_cred)
795             krb5_free_data(context, krb_cred);
796         return FALSE;
797     }
798
799     dwRequestLen = sizeof(KERB_SUBMIT_TKT_REQUEST) + krb_cred->length + (keyblock ? keyblock->length : 0);
800
801     pSubmitRequest = (PKERB_SUBMIT_TKT_REQUEST)malloc(dwRequestLen);
802     memset(pSubmitRequest, 0, dwRequestLen);
803
804     pSubmitRequest->MessageType = KerbSubmitTicketMessage;
805     pSubmitRequest->LogonId.LowPart = 0;
806     pSubmitRequest->LogonId.HighPart = 0;
807     pSubmitRequest->Flags = 0;
808
809     if (keyblock) {
810         pSubmitRequest->Key.KeyType = keyblock->enctype;
811         pSubmitRequest->Key.Length = keyblock->length;
812         pSubmitRequest->Key.Offset = sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length;
813     } else {
814         pSubmitRequest->Key.KeyType = ENCTYPE_NULL;
815         pSubmitRequest->Key.Length = 0;
816         pSubmitRequest->Key.Offset = 0;
817     }
818     pSubmitRequest->KerbCredSize = krb_cred->length;
819     pSubmitRequest->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
820     memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST),
821            krb_cred->data, krb_cred->length);
822     if (keyblock)
823         memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length,
824                keyblock->contents, keyblock->length);
825     krb5_free_data(context, krb_cred);
826
827     Status = LsaCallAuthenticationPackage( LogonHandle,
828                                            PackageId,
829                                            pSubmitRequest,
830                                            dwRequestLen,
831                                            NULL,
832                                            NULL,
833                                            &SubStatus
834     );
835     free(pSubmitRequest);
836     if (keyblock)
837         krb5_free_keyblock(context, keyblock);
838     krb5_auth_con_free(context, auth_context);
839
840     if (FAILED(Status) || FAILED(SubStatus)) {
841         return FALSE;
842     }
843     return TRUE;
844 }
845
846 /*
847  * A simple function to determine if there is an exact match between two tickets
848  * We rely on the fact that the external tickets contain the raw Kerberos ticket.
849  * If the EncodedTicket fields match, the KERB_EXTERNAL_TICKETs must be the same.
850  */
851 static BOOL
852 KerbExternalTicketMatch( PKERB_EXTERNAL_TICKET one, PKERB_EXTERNAL_TICKET two )
853 {
854     if ( one->EncodedTicketSize != two->EncodedTicketSize )
855         return FALSE;
856
857     if ( memcmp(one->EncodedTicket, two->EncodedTicket, one->EncodedTicketSize) )
858         return FALSE;
859
860     return TRUE;
861 }
862
863 krb5_boolean
864 krb5_is_permitted_tgs_enctype(krb5_context context, krb5_const_principal princ, krb5_enctype etype)
865 {
866     krb5_enctype *list, *ptr;
867     krb5_boolean ret;
868
869     if (krb5_get_tgs_ktypes(context, princ, &list))
870         return(0);
871
872     ret = 0;
873
874     for (ptr = list; *ptr; ptr++)
875         if (*ptr == etype)
876             ret = 1;
877
878     krb5_free_enctypes(context, list);
879
880     return(ret);
881 }
882
883 // to allow the purging of expired tickets from LSA cache.  This is necessary
884 // to force the retrieval of new TGTs.  Microsoft does not appear to retrieve
885 // new tickets when they expire.  Instead they continue to accept the expired
886 // tickets.  This is safe to do because the LSA purges its cache when it
887 // retrieves a new TGT (ms calls this renew) but not when it renews the TGT
888 // (ms calls this refresh).
889 // UAC-limited processes are not allowed to obtain a copy of the MSTGT
890 // session key.  We used to check for UAC-limited processes and refuse all
891 // access to the TGT, but this makes the MSLSA ccache completely unusable.
892 // Instead we ought to just flag that the tgt session key is not valid.
893
894 static BOOL
895 GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNAL_TICKET **ticket, BOOL enforce_tgs_enctypes)
896 {
897     //
898     // INVARIANTS:
899     //
900     //   (FAILED(Status) || FAILED(SubStatus)) ==> error
901     //   bIsLsaError ==> LsaCallAuthenticationPackage() error
902     //
903
904     BOOL bIsLsaError = FALSE;
905     NTSTATUS Status = 0;
906     NTSTATUS SubStatus = 0;
907     DWORD   Error;
908
909     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
910     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
911     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
912     ULONG RequestSize;
913     ULONG ResponseSize;
914     int    purge_cache = 0;
915     int    ignore_cache = 0;
916     krb5_enctype *etype_list = NULL, *ptr = NULL, etype = 0;
917
918     memset(&CacheRequest, 0, sizeof(KERB_QUERY_TKT_CACHE_REQUEST));
919     CacheRequest.MessageType = KerbRetrieveTicketMessage;
920     CacheRequest.LogonId.LowPart = 0;
921     CacheRequest.LogonId.HighPart = 0;
922
923     Status = LsaCallAuthenticationPackage(
924         LogonHandle,
925         PackageId,
926         &CacheRequest,
927         sizeof(CacheRequest),
928         &pTicketResponse,
929         &ResponseSize,
930         &SubStatus
931     );
932
933     if (FAILED(Status))
934     {
935         // if the call to LsaCallAuthenticationPackage failed we cannot
936         // perform any queries most likely because the Kerberos package
937         // is not available or we do not have access
938         bIsLsaError = TRUE;
939         goto cleanup;
940     }
941
942     if (FAILED(SubStatus)) {
943         PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
944         BOOL    Success = FALSE;
945         OSVERSIONINFOEX verinfo;
946         int supported = 0;
947
948         // SubStatus 0x8009030E is not documented.  However, it appears
949         // to mean there is no TGT
950         if (SubStatus != 0x8009030E) {
951             bIsLsaError = TRUE;
952             goto cleanup;
953         }
954
955         verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
956         GetVersionEx((OSVERSIONINFO *)&verinfo);
957         supported = (verinfo.dwMajorVersion > 5) ||
958             (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1);
959
960         // If we could not get a TGT from the cache we won't know what the
961         // Kerberos Domain should have been.  On Windows XP and 2003 Server
962         // we can extract it from the Security Logon Session Data.  However,
963         // the required fields are not supported on Windows 2000.  :(
964         if ( supported && GetSecurityLogonSessionData(&pSessionData) ) {
965             if ( pSessionData->DnsDomainName.Buffer ) {
966                 Error = ConstructTicketRequest(pSessionData->DnsDomainName,
967                                                &pTicketRequest, &RequestSize);
968                 LsaFreeReturnBuffer(pSessionData);
969                 if ( Error )
970                     goto cleanup;
971             } else {
972                 LsaFreeReturnBuffer(pSessionData);
973                 bIsLsaError = TRUE;
974                 goto cleanup;
975             }
976         } else {
977             CHAR  UserDnsDomain[256];
978             WCHAR UnicodeUserDnsDomain[256];
979             UNICODE_STRING wrapper;
980             if ( !get_STRING_from_registry(HKEY_CURRENT_USER,
981                                            "Volatile Environment",
982                                            "USERDNSDOMAIN",
983                                            UserDnsDomain,
984                                            sizeof(UserDnsDomain)
985                  ) )
986             {
987                 goto cleanup;
988             }
989
990             ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256);
991             wrapper.Buffer = UnicodeUserDnsDomain;
992             wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR);
993             wrapper.MaximumLength = 256;
994
995             Error = ConstructTicketRequest(wrapper,
996                                            &pTicketRequest, &RequestSize);
997             if ( Error )
998                 goto cleanup;
999         }
1000     } else {
1001         /* We have succeeded in obtaining a credential from the cache.
1002          * Assuming the enctype is one that we support and the ticket
1003          * has not expired and is not marked invalid we will use it.
1004          * Otherwise, we must create a new ticket request and obtain
1005          * a credential we can use.
1006          */
1007
1008         /* Check Supported Enctypes */
1009         if ( !enforce_tgs_enctypes ||
1010              IsMSSessionKeyNull(&pTicketResponse->Ticket.SessionKey) ||
1011              krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
1012             FILETIME Now, MinLife, EndTime, LocalEndTime;
1013             __int64  temp;
1014             // FILETIME is in units of 100 nano-seconds
1015             // If obtained tickets are either expired or have a lifetime
1016             // less than 20 minutes, retry ...
1017             GetSystemTimeAsFileTime(&Now);
1018             EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart;
1019             EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart;
1020             FileTimeToLocalFileTime(&EndTime, &LocalEndTime);
1021             temp = Now.dwHighDateTime;
1022             temp <<= 32;
1023             temp = Now.dwLowDateTime;
1024             temp += 1200 * 10000;
1025             MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF);
1026             MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
1027             if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) {
1028                 purge_cache = 1;
1029             }
1030             if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) {
1031                 ignore_cache = 1;   // invalid, need to attempt a TGT request
1032             }
1033             goto cleanup;           // we have a valid ticket, all done
1034         } else {
1035             // not supported
1036             ignore_cache = 1;
1037         }
1038
1039         Error = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName,
1040                                        &pTicketRequest, &RequestSize);
1041         if ( Error ) {
1042             goto cleanup;
1043         }
1044
1045         //
1046         // Free the previous response buffer so we can get the new response.
1047         //
1048
1049         if ( pTicketResponse ) {
1050             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1051             LsaFreeReturnBuffer(pTicketResponse);
1052             pTicketResponse = NULL;
1053         }
1054
1055         if ( purge_cache ) {
1056             //
1057             // Purge the existing tickets which we cannot use so new ones can
1058             // be requested.  It is not possible to purge just the TGT.  All
1059             // service tickets must be purged.
1060             //
1061             PurgeAllTickets(LogonHandle, PackageId);
1062         }
1063     }
1064
1065     //
1066     // Intialize the request of the request.
1067     //
1068
1069     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1070     pTicketRequest->LogonId.LowPart = 0;
1071     pTicketRequest->LogonId.HighPart = 0;
1072     // Note: pTicketRequest->TargetName set up above
1073     pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ?
1074                                     KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
1075     pTicketRequest->TicketFlags = 0L;
1076     pTicketRequest->EncryptionType = 0L;
1077
1078     Status = LsaCallAuthenticationPackage( LogonHandle,
1079                                            PackageId,
1080                                            pTicketRequest,
1081                                            RequestSize,
1082                                            &pTicketResponse,
1083                                            &ResponseSize,
1084                                            &SubStatus
1085     );
1086
1087     if (FAILED(Status) || FAILED(SubStatus))
1088     {
1089         bIsLsaError = TRUE;
1090         goto cleanup;
1091     }
1092
1093     //
1094     // Check to make sure the new tickets we received are of a type we support
1095     //
1096
1097     /* Check Supported Enctypes */
1098     if ( !enforce_tgs_enctypes ||
1099          krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) {
1100         goto cleanup;       // we have a valid ticket, all done
1101     }
1102
1103     if (krb5_get_tgs_ktypes(context, NULL, &etype_list)) {
1104         ptr = etype_list = NULL;
1105         etype = ENCTYPE_DES_CBC_CRC;
1106     } else {
1107         ptr = etype_list + 1;
1108         etype = *etype_list;
1109     }
1110
1111     while ( etype ) {
1112         // Try once more but this time specify the Encryption Type
1113         // (This will not store the retrieved tickets in the LSA cache unless
1114         // 0 is supported.)
1115         pTicketRequest->EncryptionType = etype;
1116         pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1117
1118         if ( pTicketResponse ) {
1119             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1120             LsaFreeReturnBuffer(pTicketResponse);
1121             pTicketResponse = NULL;
1122         }
1123
1124         Status = LsaCallAuthenticationPackage( LogonHandle,
1125                                                PackageId,
1126                                                pTicketRequest,
1127                                                RequestSize,
1128                                                &pTicketResponse,
1129                                                &ResponseSize,
1130                                                &SubStatus
1131         );
1132
1133         if (FAILED(Status) || FAILED(SubStatus))
1134         {
1135             bIsLsaError = TRUE;
1136             goto cleanup;
1137         }
1138
1139         if ( pTicketResponse->Ticket.SessionKey.KeyType == etype &&
1140              (!enforce_tgs_enctypes ||
1141               krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType)) ) {
1142             goto cleanup;       // we have a valid ticket, all done
1143         }
1144
1145         if ( ptr ) {
1146             etype = *ptr++;
1147         } else {
1148             etype = 0;
1149         }
1150     }
1151
1152 cleanup:
1153     if ( etype_list )
1154         krb5_free_enctypes(context, etype_list);
1155
1156     if ( pTicketRequest )
1157         LocalFree(pTicketRequest);
1158
1159     if (FAILED(Status) || FAILED(SubStatus))
1160     {
1161         if (bIsLsaError)
1162         {
1163             // XXX - Will be fixed later
1164             if (FAILED(Status))
1165                 ShowLsaError("LsaCallAuthenticationPackage", Status);
1166             if (FAILED(SubStatus))
1167                 ShowLsaError("LsaCallAuthenticationPackage", SubStatus);
1168         }
1169         else
1170         {
1171             ShowWinError("GetMSTGT", Status);
1172         }
1173
1174         if (pTicketResponse) {
1175             memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
1176             LsaFreeReturnBuffer(pTicketResponse);
1177             pTicketResponse = NULL;
1178         }
1179         return(FALSE);
1180     }
1181
1182     *ticket = &(pTicketResponse->Ticket);
1183     return(TRUE);
1184 }
1185
1186 static BOOL
1187 GetQueryTktCacheResponseEx(HANDLE LogonHandle, ULONG PackageId,
1188                            PKERB_QUERY_TKT_CACHE_EX_RESPONSE * ppResponse)
1189 {
1190     NTSTATUS Status = 0;
1191     NTSTATUS SubStatus = 0;
1192
1193     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
1194     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pQueryResponse = NULL;
1195     ULONG ResponseSize;
1196
1197     CacheRequest.MessageType = KerbQueryTicketCacheExMessage;
1198     CacheRequest.LogonId.LowPart = 0;
1199     CacheRequest.LogonId.HighPart = 0;
1200
1201     Status = LsaCallAuthenticationPackage(
1202         LogonHandle,
1203         PackageId,
1204         &CacheRequest,
1205         sizeof(CacheRequest),
1206         &pQueryResponse,
1207         &ResponseSize,
1208         &SubStatus
1209     );
1210
1211     if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
1212         *ppResponse = pQueryResponse;
1213         return TRUE;
1214     }
1215
1216     return FALSE;
1217 }
1218
1219 static BOOL
1220 GetQueryTktCacheResponseEx2(HANDLE LogonHandle, ULONG PackageId,
1221                             PKERB_QUERY_TKT_CACHE_EX2_RESPONSE * ppResponse)
1222 {
1223     NTSTATUS Status = 0;
1224     NTSTATUS SubStatus = 0;
1225
1226     KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
1227     PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pQueryResponse = NULL;
1228     ULONG ResponseSize;
1229
1230     CacheRequest.MessageType = KerbQueryTicketCacheEx2Message;
1231     CacheRequest.LogonId.LowPart = 0;
1232     CacheRequest.LogonId.HighPart = 0;
1233
1234     Status = LsaCallAuthenticationPackage(
1235         LogonHandle,
1236         PackageId,
1237         &CacheRequest,
1238         sizeof(CacheRequest),
1239         &pQueryResponse,
1240         &ResponseSize,
1241         &SubStatus
1242     );
1243
1244     if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
1245         *ppResponse = pQueryResponse;
1246         return TRUE;
1247     }
1248
1249     return FALSE;
1250 }
1251
1252 static BOOL
1253 GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId,
1254                              krb5_context context, krb5_creds *creds,
1255                              PKERB_EXTERNAL_TICKET *ticket)
1256 {
1257     NTSTATUS Status = 0;
1258     NTSTATUS SubStatus = 0;
1259     ULONG RequestSize;
1260     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1261     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1262     ULONG ResponseSize;
1263
1264     RequestSize = sizeof(*pTicketRequest) + MAX_MSPRINC_SIZE;
1265
1266     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1267     if (!pTicketRequest)
1268         return FALSE;
1269
1270     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1271     pTicketRequest->LogonId.LowPart = 0;
1272     pTicketRequest->LogonId.HighPart = 0;
1273
1274     pTicketRequest->TargetName.Length = 0;
1275     pTicketRequest->TargetName.MaximumLength = MAX_MSPRINC_SIZE;
1276     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1277     MITPrincToMSPrinc(context, creds->server, &pTicketRequest->TargetName);
1278     pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1279     pTicketRequest->TicketFlags = creds->ticket_flags;
1280     pTicketRequest->EncryptionType = creds->keyblock.enctype;
1281
1282     Status = LsaCallAuthenticationPackage( LogonHandle,
1283                                            PackageId,
1284                                            pTicketRequest,
1285                                            RequestSize,
1286                                            &pTicketResponse,
1287                                            &ResponseSize,
1288                                            &SubStatus
1289     );
1290
1291     LocalFree(pTicketRequest);
1292
1293     if (FAILED(Status) || FAILED(SubStatus))
1294         return(FALSE);
1295
1296     /* otherwise return ticket */
1297     *ticket = &(pTicketResponse->Ticket);
1298     return(TRUE);
1299 }
1300
1301 static BOOL
1302 GetMSCacheTicketFromCacheInfoEx(HANDLE LogonHandle, ULONG PackageId,
1303                                 PKERB_TICKET_CACHE_INFO_EX tktinfo,
1304                                 PKERB_EXTERNAL_TICKET *ticket)
1305 {
1306     NTSTATUS Status = 0;
1307     NTSTATUS SubStatus = 0;
1308     ULONG RequestSize;
1309     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1310     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1311     ULONG ResponseSize;
1312
1313     RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
1314
1315     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1316     if (!pTicketRequest)
1317         return FALSE;
1318
1319     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1320     pTicketRequest->LogonId.LowPart = 0;
1321     pTicketRequest->LogonId.HighPart = 0;
1322     pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
1323     pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
1324     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1325     memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
1326     pTicketRequest->CacheOptions = 0;
1327     pTicketRequest->EncryptionType = tktinfo->EncryptionType;
1328     pTicketRequest->TicketFlags = 0;
1329     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
1330         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
1331     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
1332         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
1333     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
1334         pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
1335     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
1336         pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
1337
1338     Status = LsaCallAuthenticationPackage(
1339         LogonHandle,
1340         PackageId,
1341         pTicketRequest,
1342         RequestSize,
1343         &pTicketResponse,
1344         &ResponseSize,
1345         &SubStatus
1346     );
1347
1348     LocalFree(pTicketRequest);
1349
1350     if (FAILED(Status) || FAILED(SubStatus))
1351         return(FALSE);
1352
1353     /* otherwise return ticket */
1354     *ticket = &(pTicketResponse->Ticket);
1355
1356     /* set the initial flag if we were attempting to retrieve one
1357      * because Windows won't necessarily return the initial ticket
1358      * to us.
1359      */
1360     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
1361         (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
1362
1363     return(TRUE);
1364 }
1365
1366 static BOOL
1367 GetMSCacheTicketFromCacheInfoEx2(HANDLE LogonHandle, ULONG PackageId,
1368                                  PKERB_TICKET_CACHE_INFO_EX2 tktinfo,
1369                                  PKERB_EXTERNAL_TICKET *ticket)
1370 {
1371     NTSTATUS Status = 0;
1372     NTSTATUS SubStatus = 0;
1373     ULONG RequestSize;
1374     PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
1375     PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
1376     ULONG ResponseSize;
1377
1378     RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
1379
1380     pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
1381     if (!pTicketRequest)
1382         return FALSE;
1383
1384     pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
1385     pTicketRequest->LogonId.LowPart = 0;
1386     pTicketRequest->LogonId.HighPart = 0;
1387     pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
1388     pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
1389     pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
1390     memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
1391     pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET;
1392     pTicketRequest->EncryptionType = tktinfo->SessionKeyType;
1393     pTicketRequest->TicketFlags = 0;
1394     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable )
1395         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE;
1396     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded )
1397         pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED;
1398     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable )
1399         pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE;
1400     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable )
1401         pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE;
1402
1403     Status = LsaCallAuthenticationPackage(
1404         LogonHandle,
1405         PackageId,
1406         pTicketRequest,
1407         RequestSize,
1408         &pTicketResponse,
1409         &ResponseSize,
1410         &SubStatus
1411     );
1412
1413     LocalFree(pTicketRequest);
1414
1415     if (FAILED(Status) || FAILED(SubStatus))
1416         return(FALSE);
1417
1418     /* otherwise return ticket */
1419     *ticket = &(pTicketResponse->Ticket);
1420
1421
1422     /* set the initial flag if we were attempting to retrieve one
1423      * because Windows won't necessarily return the initial ticket
1424      * to us.
1425      */
1426     if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial )
1427         (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial;
1428
1429     return(TRUE);
1430 }
1431
1432 static krb5_error_code KRB5_CALLCONV krb5_lcc_close
1433 (krb5_context, krb5_ccache id);
1434
1435 static krb5_error_code KRB5_CALLCONV krb5_lcc_destroy
1436 (krb5_context, krb5_ccache id);
1437
1438 static krb5_error_code KRB5_CALLCONV krb5_lcc_end_seq_get
1439 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
1440
1441 static krb5_error_code KRB5_CALLCONV krb5_lcc_generate_new
1442 (krb5_context, krb5_ccache *id);
1443
1444 static const char * KRB5_CALLCONV krb5_lcc_get_name
1445 (krb5_context, krb5_ccache id);
1446
1447 static krb5_error_code KRB5_CALLCONV krb5_lcc_get_principal
1448 (krb5_context, krb5_ccache id, krb5_principal *princ);
1449
1450 static krb5_error_code KRB5_CALLCONV krb5_lcc_initialize
1451 (krb5_context, krb5_ccache id, krb5_principal princ);
1452
1453 static krb5_error_code KRB5_CALLCONV krb5_lcc_next_cred
1454 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor,
1455  krb5_creds *creds);
1456
1457 static krb5_error_code KRB5_CALLCONV krb5_lcc_resolve
1458 (krb5_context, krb5_ccache *id, const char *residual);
1459
1460 static krb5_error_code KRB5_CALLCONV krb5_lcc_retrieve
1461 (krb5_context, krb5_ccache id, krb5_flags whichfields,
1462  krb5_creds *mcreds, krb5_creds *creds);
1463
1464 static krb5_error_code KRB5_CALLCONV krb5_lcc_start_seq_get
1465 (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
1466
1467 static krb5_error_code KRB5_CALLCONV krb5_lcc_store
1468 (krb5_context, krb5_ccache id, krb5_creds *creds);
1469
1470 static krb5_error_code KRB5_CALLCONV krb5_lcc_set_flags
1471 (krb5_context, krb5_ccache id, krb5_flags flags);
1472
1473 static krb5_error_code KRB5_CALLCONV krb5_lcc_get_flags
1474 (krb5_context, krb5_ccache id, krb5_flags *flags);
1475
1476 extern const krb5_cc_ops krb5_lcc_ops;
1477
1478 krb5_error_code krb5_change_cache (void);
1479
1480 krb5_boolean
1481 krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds);
1482
1483 #define KRB5_OK 0
1484
1485 typedef struct _krb5_lcc_data {
1486     HANDLE LogonHandle;
1487     ULONG  PackageId;
1488     char * cc_name;
1489     krb5_principal princ;
1490     krb5_flags flags;
1491 } krb5_lcc_data;
1492
1493 typedef struct _krb5_lcc_cursor {
1494     union {
1495         PKERB_QUERY_TKT_CACHE_RESPONSE w2k;
1496         PKERB_QUERY_TKT_CACHE_EX_RESPONSE xp;
1497         PKERB_QUERY_TKT_CACHE_EX2_RESPONSE ex2;
1498     } response;
1499     unsigned int index;
1500     PKERB_EXTERNAL_TICKET mstgt;
1501 } krb5_lcc_cursor;
1502
1503
1504 /*
1505  * Requires:
1506  * residual is ignored
1507  *
1508  * Modifies:
1509  * id
1510  *
1511  * Effects:
1512  * Acccess the MS Kerberos LSA cache in the current logon session
1513  * Ignore the residual.
1514  *
1515  * Returns:
1516  * A filled in krb5_ccache structure "id".
1517  *
1518  * Errors:
1519  * KRB5_CC_NOMEM - there was insufficient memory to allocate the
1520  *
1521  *              krb5_ccache.  id is undefined.
1522  * permission errors
1523  */
1524 static krb5_error_code KRB5_CALLCONV
1525 krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
1526 {
1527     krb5_ccache lid;
1528     krb5_lcc_data *data;
1529     HANDLE LogonHandle;
1530     ULONG  PackageId, i;
1531     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
1532
1533     if (!PackageConnectLookup(&LogonHandle, &PackageId))
1534         return KRB5_FCC_NOFILE;
1535
1536     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
1537     if (lid == NULL) {
1538         LsaDeregisterLogonProcess(LogonHandle);
1539         return KRB5_CC_NOMEM;
1540     }
1541
1542     lid->ops = &krb5_lcc_ops;
1543
1544     lid->data = (krb5_pointer) malloc(sizeof(krb5_lcc_data));
1545     if (lid->data == NULL) {
1546         free(lid);
1547         LsaDeregisterLogonProcess(LogonHandle);
1548         return KRB5_CC_NOMEM;
1549     }
1550
1551     lid->magic = KV5M_CCACHE;
1552     data = (krb5_lcc_data *)lid->data;
1553     data->LogonHandle = LogonHandle;
1554     data->PackageId = PackageId;
1555     data->princ = NULL;
1556
1557     data->cc_name = (char *)malloc(strlen(residual)+1);
1558     if (data->cc_name == NULL) {
1559         free(lid->data);
1560         free(lid);
1561         LsaDeregisterLogonProcess(LogonHandle);
1562         return KRB5_CC_NOMEM;
1563     }
1564     strcpy(data->cc_name, residual);
1565
1566     /* If there are already tickets present, grab a client principal name. */
1567     if (GetQueryTktCacheResponseEx(LogonHandle, PackageId, &pResponse)) {
1568         /* Take the first client principal we find; they should all be the
1569          * same anyway. */
1570         for (i = 0; i < pResponse->CountOfTickets; i++) {
1571             if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
1572                                         &pResponse->Tickets[i].ClientRealm,
1573                                         context, &data->princ))
1574                 break;
1575
1576         }
1577         LsaFreeReturnBuffer(pResponse);
1578     }
1579
1580     /*
1581      * other routines will get errors on open, and callers must expect them,
1582      * if cache is non-existent/unusable
1583      */
1584     *id = lid;
1585     return KRB5_OK;
1586 }
1587
1588 /*
1589  *  return success although we do not do anything
1590  *  We should delete all tickets belonging to the specified principal
1591  */
1592
1593 static krb5_error_code KRB5_CALLCONV
1594 krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
1595                      krb5_creds *creds);
1596
1597 static krb5_error_code KRB5_CALLCONV
1598 krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
1599 {
1600     krb5_cc_cursor cursor;
1601     krb5_error_code code;
1602     krb5_creds cred;
1603
1604     code = krb5_cc_start_seq_get(context, id, &cursor);
1605     if (code) {
1606         if (code == KRB5_CC_NOTFOUND)
1607             return KRB5_OK;
1608         return code;
1609     }
1610
1611     while ( !(code = krb5_cc_next_cred(context, id, &cursor, &cred)) )
1612     {
1613         if ( krb5_principal_compare(context, princ, cred.client) ) {
1614             code = krb5_lcc_remove_cred(context, id, 0, &cred);
1615         }
1616         krb5_free_cred_contents(context, &cred);
1617     }
1618
1619     if (code == KRB5_CC_END || code == KRB5_CC_NOTFOUND)
1620     {
1621         krb5_cc_end_seq_get(context, id, &cursor);
1622         return KRB5_OK;
1623     }
1624     return code;
1625 }
1626
1627 /*
1628  * Modifies:
1629  * id
1630  *
1631  * Effects:
1632  * Closes the microsoft lsa cache, invalidates the id, and frees any resources
1633  * associated with the cache.
1634  */
1635 static krb5_error_code KRB5_CALLCONV
1636 krb5_lcc_close(krb5_context context, krb5_ccache id)
1637 {
1638     register int closeval = KRB5_OK;
1639     register krb5_lcc_data *data;
1640
1641     if (id) {
1642         data = (krb5_lcc_data *) id->data;
1643
1644         if (data) {
1645             LsaDeregisterLogonProcess(data->LogonHandle);
1646             if (data->cc_name)
1647                 free(data->cc_name);
1648             free(data);
1649         }
1650         free(id);
1651     }
1652     return closeval;
1653 }
1654
1655 /*
1656  * Effects:
1657  * Destroys the contents of id.
1658  *
1659  * Errors:
1660  * system errors
1661  */
1662 static krb5_error_code KRB5_CALLCONV
1663 krb5_lcc_destroy(krb5_context context, krb5_ccache id)
1664 {
1665     register krb5_lcc_data *data;
1666
1667     if (id) {
1668         data = (krb5_lcc_data *) id->data;
1669
1670         return PurgeAllTickets(data->LogonHandle, data->PackageId) ? KRB5_OK : KRB5_FCC_INTERNAL;
1671     }
1672     return KRB5_FCC_INTERNAL;
1673 }
1674
1675 /*
1676  * Effects:
1677  * Prepares for a sequential search of the credentials cache.
1678  * Returns a krb5_cc_cursor to be used with krb5_lcc_next_cred and
1679  * krb5_lcc_end_seq_get.
1680  *
1681  * If the cache is modified between the time of this call and the time
1682  * of the final krb5_lcc_end_seq_get, the results are undefined.
1683  *
1684  * Errors:
1685  * KRB5_CC_NOMEM
1686  * KRB5_FCC_INTERNAL - system errors
1687  */
1688 static krb5_error_code KRB5_CALLCONV
1689 krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
1690 {
1691     krb5_lcc_cursor *lcursor;
1692     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1693
1694     lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor));
1695     if (lcursor == NULL) {
1696         *cursor = 0;
1697         return KRB5_CC_NOMEM;
1698     }
1699
1700     /*
1701      * obtain a tgt to refresh the ccache in case the ticket is expired
1702      */
1703     if (!GetMSTGT(context, data->LogonHandle, data->PackageId, &lcursor->mstgt, TRUE)) {
1704         free(lcursor);
1705         *cursor = 0;
1706         return KRB5_CC_NOTFOUND;
1707     }
1708
1709     if ( does_query_ticket_cache_ex2() ) {
1710         if (!GetQueryTktCacheResponseEx2(data->LogonHandle, data->PackageId,
1711                                          &lcursor->response.ex2)) {
1712             LsaFreeReturnBuffer(lcursor->mstgt);
1713             free(lcursor);
1714             *cursor = 0;
1715             return KRB5_FCC_INTERNAL;
1716         }
1717     } else
1718         if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1719                                         &lcursor->response.xp)) {
1720             LsaFreeReturnBuffer(lcursor->mstgt);
1721             free(lcursor);
1722             *cursor = 0;
1723             return KRB5_FCC_INTERNAL;
1724         }
1725     lcursor->index = 0;
1726     *cursor = (krb5_cc_cursor) lcursor;
1727     return KRB5_OK;
1728 }
1729
1730
1731 /*
1732  * Requires:
1733  * cursor is a krb5_cc_cursor originally obtained from
1734  * krb5_lcc_start_seq_get.
1735  *
1736  * Modifes:
1737  * cursor
1738  *
1739  * Effects:
1740  * Fills in creds with the TGT obtained from the MS LSA
1741  *
1742  * The cursor is updated to indicate TGT retrieval
1743  *
1744  * Errors:
1745  * KRB5_CC_END
1746  * KRB5_FCC_INTERNAL - system errors
1747  */
1748 static krb5_error_code KRB5_CALLCONV
1749 krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, krb5_creds *creds)
1750 {
1751     krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
1752     krb5_lcc_data *data;
1753     KERB_EXTERNAL_TICKET *msticket;
1754     krb5_error_code  retval = KRB5_OK;
1755
1756     data = (krb5_lcc_data *)id->data;
1757
1758 next_cred:
1759     if ( does_query_ticket_cache_ex2() ) {
1760         if ( lcursor->index >= lcursor->response.ex2->CountOfTickets ) {
1761             if (retval == KRB5_OK)
1762                 return KRB5_CC_END;
1763             else {
1764                 LsaFreeReturnBuffer(lcursor->mstgt);
1765                 LsaFreeReturnBuffer(lcursor->response.ex2);
1766                 free(*cursor);
1767                 *cursor = 0;
1768                 return retval;
1769             }
1770         }
1771
1772         if ( data->flags & KRB5_TC_NOTICKET ) {
1773             if (!CacheInfoEx2ToMITCred( &lcursor->response.ex2->Tickets[lcursor->index++],
1774                                         context, creds)) {
1775                 retval = KRB5_FCC_INTERNAL;
1776                 goto next_cred;
1777             }
1778             return KRB5_OK;
1779         } else {
1780             if (!GetMSCacheTicketFromCacheInfoEx2(data->LogonHandle,
1781                                                   data->PackageId,
1782                                                   &lcursor->response.ex2->Tickets[lcursor->index++],&msticket)) {
1783                 retval = KRB5_FCC_INTERNAL;
1784                 goto next_cred;
1785             }
1786         }
1787     } else {
1788         if (lcursor->index >= lcursor->response.xp->CountOfTickets) {
1789             if (retval == KRB5_OK) {
1790                 return KRB5_CC_END;
1791             } else {
1792                 LsaFreeReturnBuffer(lcursor->mstgt);
1793                 LsaFreeReturnBuffer(lcursor->response.xp);
1794                 free(*cursor);
1795                 *cursor = 0;
1796                 return retval;
1797             }
1798         }
1799
1800         if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
1801                                              data->PackageId,
1802                                              &lcursor->response.xp->Tickets[lcursor->index++],
1803                                              &msticket)) {
1804             retval = KRB5_FCC_INTERNAL;
1805             goto next_cred;
1806         }
1807     }
1808
1809     /* Don't return tickets with NULL Session Keys */
1810     if ( IsMSSessionKeyNull(&msticket->SessionKey) ) {
1811         LsaFreeReturnBuffer(msticket);
1812         goto next_cred;
1813     }
1814
1815     /* convert the ticket */
1816     if ( does_query_ticket_cache_ex2() ) {
1817         if (!MSCredToMITCred(msticket, lcursor->response.ex2->Tickets[lcursor->index-1].ClientRealm, context, creds))
1818             retval = KRB5_FCC_INTERNAL;
1819     } else {
1820         if (!MSCredToMITCred(msticket,
1821                              lcursor->response.xp->Tickets[lcursor->index -
1822                                  1].ClientRealm,
1823                              context, creds))
1824             retval = KRB5_FCC_INTERNAL;
1825     }
1826     LsaFreeReturnBuffer(msticket);
1827     return retval;
1828 }
1829
1830 /*
1831  * Requires:
1832  * cursor is a krb5_cc_cursor originally obtained from
1833  * krb5_lcc_start_seq_get.
1834  *
1835  * Modifies:
1836  * id, cursor
1837  *
1838  * Effects:
1839  * Finishes sequential processing of the file credentials ccache id,
1840  * and invalidates the cursor (it must never be used after this call).
1841  */
1842 /* ARGSUSED */
1843 static krb5_error_code KRB5_CALLCONV
1844 krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
1845 {
1846     krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
1847
1848     if ( lcursor ) {
1849         LsaFreeReturnBuffer(lcursor->mstgt);
1850         if ( does_query_ticket_cache_ex2() )
1851             LsaFreeReturnBuffer(lcursor->response.ex2);
1852         else
1853             LsaFreeReturnBuffer(lcursor->response.xp);
1854         free(*cursor);
1855     }
1856     *cursor = 0;
1857
1858     return KRB5_OK;
1859 }
1860
1861
1862 /*
1863  * Errors:
1864  * KRB5_CC_READONLY - not supported
1865  */
1866 static krb5_error_code KRB5_CALLCONV
1867 krb5_lcc_generate_new (krb5_context context, krb5_ccache *id)
1868 {
1869     return KRB5_CC_READONLY;
1870 }
1871
1872 /*
1873  * Requires:
1874  * id is a ms lsa credential cache
1875  *
1876  * Returns:
1877  *   The ccname specified during the krb5_lcc_resolve call
1878  */
1879 static const char * KRB5_CALLCONV
1880 krb5_lcc_get_name (krb5_context context, krb5_ccache id)
1881 {
1882
1883     if ( !id )
1884         return "";
1885
1886     return (char *) ((krb5_lcc_data *) id->data)->cc_name;
1887 }
1888
1889 /*
1890  * Modifies:
1891  * id, princ
1892  *
1893  * Effects:
1894  * Retrieves the primary principal from id, as set with
1895  * krb5_lcc_initialize.  The principal is returned is allocated
1896  * storage that must be freed by the caller via krb5_free_principal.
1897  *
1898  * Errors:
1899  * system errors
1900  * KRB5_CC_NOT_KTYPE
1901  */
1902 static krb5_error_code KRB5_CALLCONV
1903 krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
1904 {
1905     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse;
1906     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1907     ULONG  i;
1908
1909     /* obtain principal */
1910     if (data->princ)
1911         return krb5_copy_principal(context, data->princ, princ);
1912     else {
1913         if (GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1914                                        &pResponse)) {
1915             /* Take the first client principal we find; they should all be the
1916              * same anyway. */
1917             for (i = 0; i < pResponse->CountOfTickets; i++) {
1918                 if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName,
1919                                             &pResponse->Tickets[i].ClientRealm,
1920                                             context, &data->princ))
1921                     break;
1922             }
1923             LsaFreeReturnBuffer(pResponse);
1924             if (data->princ)
1925                 return krb5_copy_principal(context, data->princ, princ);
1926         }
1927     }
1928     return KRB5_CC_NOTFOUND;
1929 }
1930
1931
1932 static krb5_error_code KRB5_CALLCONV
1933 krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
1934                   krb5_creds *mcreds, krb5_creds *creds)
1935 {
1936     krb5_error_code kret = KRB5_OK;
1937     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
1938     KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0, *mstmp = 0;
1939     krb5_creds * mcreds_noflags = 0;
1940     krb5_creds   fetchcreds;
1941     PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse = 0;
1942     unsigned int i;
1943
1944     memset(&fetchcreds, 0, sizeof(krb5_creds));
1945
1946     /* first try to find out if we have an existing ticket which meets the requirements */
1947     kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1948                                        creds);
1949     /* This sometimes returns a zero-length ticket; work around it. */
1950     if ( !kret && creds->ticket.length > 0 )
1951         return KRB5_OK;
1952
1953     /* if not, we must try to get a ticket without specifying any flags or etypes */
1954     kret = krb5_copy_creds(context, mcreds, &mcreds_noflags);
1955     if (kret)
1956         goto cleanup;
1957     mcreds_noflags->ticket_flags = 0;
1958     mcreds_noflags->keyblock.enctype = 0;
1959
1960     if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds_noflags, &msticket)) {
1961         kret = KRB5_CC_NOTFOUND;
1962         goto cleanup;
1963     }
1964
1965     /* try again to find out if we have an existing ticket which meets the requirements */
1966     kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
1967                                        creds);
1968     /* This sometimes returns a zero-length ticket; work around it. */
1969     if ( !kret && creds->ticket.length > 0 )
1970         goto cleanup;
1971
1972     /* if not, obtain a ticket using the request flags and enctype even though it may not
1973      * be stored in the LSA cache for future use.
1974      */
1975     if ( msticket ) {
1976         LsaFreeReturnBuffer(msticket);
1977         msticket = 0;
1978     }
1979
1980     if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds, &msticket)) {
1981         kret = KRB5_CC_NOTFOUND;
1982         goto cleanup;
1983     }
1984
1985     /* convert the ticket */
1986     /*
1987      * We can obtain the correct client realm for a ticket by walking the
1988      * cache contents until we find the matching service ticket.
1989      */
1990
1991     if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId,
1992         &pResponse)) {
1993         kret = KRB5_FCC_INTERNAL;
1994         goto cleanup;
1995     }
1996
1997     for (i = 0; i < pResponse->CountOfTickets; i++) {
1998         if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle,
1999                                              data->PackageId,
2000                                              &pResponse->Tickets[i], &mstmp)) {
2001             continue;
2002         }
2003
2004         if (KerbExternalTicketMatch(msticket,mstmp))
2005             break;
2006
2007         LsaFreeReturnBuffer(mstmp);
2008         mstmp = 0;
2009     }
2010
2011     if (!MSCredToMITCred(msticket, mstmp ?
2012                          pResponse->Tickets[i].ClientRealm :
2013                          msticket->DomainName, context, &fetchcreds)) {
2014         LsaFreeReturnBuffer(pResponse);
2015         kret = KRB5_FCC_INTERNAL;
2016         goto cleanup;
2017     }
2018     LsaFreeReturnBuffer(pResponse);
2019
2020
2021     /* check to see if this ticket matches the request using logic from
2022      * k5_cc_retrieve_cred_default()
2023      */
2024     if ( krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds) ) {
2025         *creds = fetchcreds;
2026     } else {
2027         krb5_free_cred_contents(context, &fetchcreds);
2028         kret = KRB5_CC_NOTFOUND;
2029     }
2030
2031 cleanup:
2032     if ( mstmp )
2033         LsaFreeReturnBuffer(mstmp);
2034     if ( mstgt )
2035         LsaFreeReturnBuffer(mstgt);
2036     if ( msticket )
2037         LsaFreeReturnBuffer(msticket);
2038     if ( mcreds_noflags )
2039         krb5_free_creds(context, mcreds_noflags);
2040     return kret;
2041 }
2042
2043
2044 /*
2045  * We can't write to the MS LSA cache.  So we request the cache to obtain a ticket for the same
2046  * principal in the hope that next time the application requires a ticket for the service it
2047  * is attempt to store, the retrieved ticket will be good enough.
2048  *
2049  * Errors:
2050  * KRB5_CC_READONLY - not supported
2051  */
2052 static krb5_error_code KRB5_CALLCONV
2053 krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
2054 {
2055     krb5_error_code kret = KRB5_OK;
2056     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2057     KERB_EXTERNAL_TICKET *msticket = 0, *msticket2 = 0;
2058     krb5_creds * creds_noflags = 0;
2059
2060     if (krb5_is_config_principal(context, creds->server)) {
2061         /* mslsa cannot store config creds, so we have to bail.
2062          * The 'right' thing to do would be to return an appropriate error,
2063          * but that would require modifying the calling code to check
2064          * for that error and ignore it.
2065          */
2066         return KRB5_OK;
2067     }
2068
2069     if (KerbSubmitTicket( data->LogonHandle, data->PackageId, context, creds ))
2070         return KRB5_OK;
2071
2072     /* If not, lets try to obtain a matching ticket from the KDC */
2073     if ( creds->ticket_flags != 0 && creds->keyblock.enctype != 0 ) {
2074         /* if not, we must try to get a ticket without specifying any flags or etypes */
2075         kret = krb5_copy_creds(context, creds, &creds_noflags);
2076         if (kret == 0) {
2077             creds_noflags->ticket_flags = 0;
2078             creds_noflags->keyblock.enctype = 0;
2079
2080             GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds_noflags, &msticket2);
2081             krb5_free_creds(context, creds_noflags);
2082         }
2083     }
2084
2085     GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds, &msticket);
2086     if (msticket || msticket2) {
2087         if (msticket)
2088             LsaFreeReturnBuffer(msticket);
2089         if (msticket2)
2090             LsaFreeReturnBuffer(msticket2);
2091         return KRB5_OK;
2092     }
2093     return KRB5_CC_READONLY;
2094 }
2095
2096 /*
2097  * Individual credentials can be implemented differently depending
2098  * on the operating system version.  (undocumented.)
2099  *
2100  * Errors:
2101  *    KRB5_CC_READONLY:
2102  */
2103 static krb5_error_code KRB5_CALLCONV
2104 krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
2105                      krb5_creds *creds)
2106 {
2107     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2108
2109     if (PurgeTicketEx(data->LogonHandle, data->PackageId, context, flags,
2110                       creds))
2111         return KRB5_OK;
2112
2113     return KRB5_CC_READONLY;
2114 }
2115
2116
2117 /*
2118  * Effects:
2119  *   Set
2120  */
2121 static krb5_error_code KRB5_CALLCONV
2122 krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
2123 {
2124     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2125
2126     data->flags = flags;
2127     return KRB5_OK;
2128 }
2129
2130 static krb5_error_code KRB5_CALLCONV
2131 krb5_lcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
2132 {
2133     krb5_lcc_data *data = (krb5_lcc_data *)id->data;
2134
2135     *flags = data->flags;
2136     return KRB5_OK;
2137 }
2138
2139 static krb5_error_code KRB5_CALLCONV
2140 krb5_lcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
2141 {
2142     krb5_cc_ptcursor new_cursor = (krb5_cc_ptcursor )malloc(sizeof(*new_cursor));
2143     if (!new_cursor)
2144         return ENOMEM;
2145     new_cursor->ops = &krb5_lcc_ops;
2146     new_cursor->data = (krb5_pointer)(1);
2147     *cursor = new_cursor;
2148     new_cursor = NULL;
2149     return 0;
2150 }
2151
2152 static krb5_error_code KRB5_CALLCONV
2153 krb5_lcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *ccache)
2154 {
2155     krb5_error_code code = 0;
2156     *ccache = 0;
2157     if (cursor->data == NULL)
2158         return 0;
2159
2160     cursor->data = NULL;
2161     if ((code = krb5_lcc_resolve(context, ccache, ""))) {
2162         if (code != KRB5_FCC_NOFILE)
2163             /* Note that we only want to return serious errors.
2164              * Any non-zero return code will prevent the cccol iterator
2165              * from advancing to the next ccache collection. */
2166             return code;
2167     }
2168     return 0;
2169 }
2170
2171 static krb5_error_code KRB5_CALLCONV
2172 krb5_lcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
2173 {
2174     if (*cursor) {
2175         free(*cursor);
2176         *cursor = NULL;
2177     }
2178     return 0;
2179 }
2180
2181 const krb5_cc_ops krb5_lcc_ops = {
2182     0,
2183     "MSLSA",
2184     krb5_lcc_get_name,
2185     krb5_lcc_resolve,
2186     krb5_lcc_generate_new,
2187     krb5_lcc_initialize,
2188     krb5_lcc_destroy,
2189     krb5_lcc_close,
2190     krb5_lcc_store,
2191     krb5_lcc_retrieve,
2192     krb5_lcc_get_principal,
2193     krb5_lcc_start_seq_get,
2194     krb5_lcc_next_cred,
2195     krb5_lcc_end_seq_get,
2196     krb5_lcc_remove_cred,
2197     krb5_lcc_set_flags,
2198     krb5_lcc_get_flags,
2199     krb5_lcc_ptcursor_new,
2200     krb5_lcc_ptcursor_next,
2201     krb5_lcc_ptcursor_free,
2202     NULL, /* move */
2203     NULL, /* lastchange */
2204     NULL, /* wasdefault */
2205     NULL, /* lock */
2206     NULL, /* unlock */
2207     NULL, /* switch_to */
2208 };
2209 #endif /* _WIN32 */