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