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