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