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