>From 3564e5772c9b884ea6e8dbdd38e2d850cfbc20c8 Mon Sep 17 00:00:00 2001 From: Dmitry Timoshkov Date: Mon, 4 Sep 2017 15:54:34 +0300 Subject: [3/3] kerberos: Add support for KerbQueryTicketCacheMessage. Content-Type: text/plain; charset=UTF-8 To: wine-patches@winehq.org Based on the code written by George Popoff. Signed-off-by: Dmitry Timoshkov --- dlls/kerberos/krb5_ap.c | 299 +++++++++++++++++++++++++++++++++++++++++++++++- include/ntsecapi.h | 96 ++++++++++++++++ 2 files changed, 392 insertions(+), 3 deletions(-) diff --git a/dlls/kerberos/krb5_ap.c b/dlls/kerberos/krb5_ap.c index 39b6ee35b6..7ff8c2a850 100644 --- a/dlls/kerberos/krb5_ap.c +++ b/dlls/kerberos/krb5_ap.c @@ -1,5 +1,6 @@ /* * Copyright 2017 Dmitry Timoshkov + * Copyright 2017 George Popoff * * Kerberos5 Authentication Package * @@ -30,6 +31,7 @@ #define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" +#include "winnls.h" #include "sspi.h" #include "ntsecapi.h" #include "ntsecpkg.h" @@ -45,8 +47,36 @@ static LSA_DISPATCH_TABLE lsa_dispatch; #define MAKE_FUNCPTR(f) static typeof(f) * p_##f MAKE_FUNCPTR(krb5_init_context); +MAKE_FUNCPTR(krb5_free_context); +MAKE_FUNCPTR(krb5_cccol_cursor_new); +MAKE_FUNCPTR(krb5_cccol_cursor_next); +MAKE_FUNCPTR(krb5_cc_close); +MAKE_FUNCPTR(krb5_cc_start_seq_get); +MAKE_FUNCPTR(krb5_cc_end_seq_get); +MAKE_FUNCPTR(krb5_cc_next_cred); +MAKE_FUNCPTR(krb5_is_config_principal); +MAKE_FUNCPTR(krb5_decode_ticket); +MAKE_FUNCPTR(krb5_unparse_name); +MAKE_FUNCPTR(krb5_unparse_name_flags); +MAKE_FUNCPTR(krb5_free_unparsed_name); +MAKE_FUNCPTR(krb5_free_cred_contents); #undef MAKE_FUNCPTR +static void *heap_alloc(SIZE_T size) +{ + return HeapAlloc(GetProcessHeap(), 0, size); +} + +static void *heap_realloc(void *p, SIZE_T size) +{ + return HeapReAlloc(GetProcessHeap(), 0, p, size); +} + +static void heap_free(void *p) +{ + HeapFree(GetProcessHeap(), 0, p); +} + static BOOL load_krb5(void) { void *libkrb5_handle; @@ -65,6 +95,19 @@ static BOOL load_krb5(void) } LOAD_FUNCPTR(krb5_init_context) + LOAD_FUNCPTR(krb5_free_context) + LOAD_FUNCPTR(krb5_cccol_cursor_new) + LOAD_FUNCPTR(krb5_cccol_cursor_next) + LOAD_FUNCPTR(krb5_cc_close) + LOAD_FUNCPTR(krb5_cc_start_seq_get) + LOAD_FUNCPTR(krb5_cc_end_seq_get) + LOAD_FUNCPTR(krb5_cc_next_cred) + LOAD_FUNCPTR(krb5_is_config_principal) + LOAD_FUNCPTR(krb5_decode_ticket) + LOAD_FUNCPTR(krb5_unparse_name) + LOAD_FUNCPTR(krb5_unparse_name_flags) + LOAD_FUNCPTR(krb5_free_unparsed_name) + LOAD_FUNCPTR(krb5_free_cred_contents) #undef LOAD_FUNCPTR return TRUE; @@ -102,15 +145,265 @@ static NTSTATUS NTAPI krb5_LsaApInitializePackage(ULONG package_id, PLSA_DISPATC return STATUS_SUCCESS; } +static NTSTATUS krb5_error_to_status(krb5_error_code error) +{ + switch (error) + { + case 0: return STATUS_SUCCESS; + + default: + /* FIXME */ + return STATUS_UNSUCCESSFUL; + } +} + +static WCHAR *utf8_to_wstr(const char *utf8) +{ + int size; + WCHAR *wstr; + + size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + wstr = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)); + if (wstr) + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, size); + + return wstr; +} + +static NTSTATUS copy_ticket_info(krb5_context context, krb5_ccache cache, KERB_TICKET_CACHE_INFO *ticket_info) +{ + NTSTATUS status; + krb5_cc_cursor cursor; + krb5_error_code error; + krb5_creds credentials; + krb5_ticket *ticket; + char *name_with_realm; + char *name_without_realm; + char *realm_name; + WCHAR *realm_nameW; + WCHAR *name_without_realmW; + + error = p_krb5_cc_start_seq_get(context, cache, &cursor); + if (error) return krb5_error_to_status(error); + + status = STATUS_SUCCESS; + + for (;;) + { + error = p_krb5_cc_next_cred(context, cache, &cursor, &credentials); + if (error) + { + status = krb5_error_to_status(error); + break; + } + + if (p_krb5_is_config_principal(context, credentials.server)) + { + p_krb5_free_cred_contents(context, &credentials); + continue; + } + + error = p_krb5_unparse_name(context, credentials.server, &name_with_realm); + if (error) + { + p_krb5_free_cred_contents(context, &credentials); + status = krb5_error_to_status(error); + break; + } + + TRACE("name_with_realm: %s\n", debugstr_a(name_with_realm)); + + error = p_krb5_unparse_name_flags(context, credentials.server, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name_without_realm); + if (error) + { + p_krb5_free_unparsed_name(context, name_with_realm); + p_krb5_free_cred_contents(context, &credentials); + status = krb5_error_to_status(error); + break; + } + + TRACE("name_without_realm: %s\n", debugstr_a(name_without_realm)); + + name_without_realmW = utf8_to_wstr(name_without_realm); + RtlInitUnicodeString(&ticket_info->ServerName, name_without_realmW); + + realm_name = name_with_realm; + + /* Increment pointer while not contain realm name */ + do { + ++realm_name; + } while (*realm_name != '@' && *realm_name != '\0'); + ++realm_name; + + /* realm_name - now contains only realm! */ + + realm_nameW = utf8_to_wstr(realm_name); + RtlInitUnicodeString(&ticket_info->RealmName, realm_nameW); + + if (!credentials.times.starttime) + credentials.times.starttime = credentials.times.authtime; + + /* TODO: if krb5_is_config_principal = true */ + RtlSecondsSince1970ToTime(credentials.times.starttime, &ticket_info->StartTime); + RtlSecondsSince1970ToTime(credentials.times.endtime, &ticket_info->EndTime); + RtlSecondsSince1970ToTime(credentials.times.renew_till, &ticket_info->RenewTime); + + ticket_info->TicketFlags = credentials.ticket_flags; + + error = p_krb5_decode_ticket(&credentials.ticket, &ticket); + if (error) + status = krb5_error_to_status(error); + else + { + ticket_info->EncryptionType = ticket->enc_part.enctype; + status = STATUS_SUCCESS; + } + + p_krb5_free_unparsed_name(context, name_with_realm); + p_krb5_free_unparsed_name(context, name_without_realm); + p_krb5_free_cred_contents(context, &credentials); + + break; + } + + p_krb5_cc_end_seq_get(context, cache, &cursor); + + return status; +} + +static NTSTATUS query_ticket_cache(PLSA_CLIENT_REQUEST lsa_req, void *in, ULONG in_len, void **out, ULONG *out_len) +{ + NTSTATUS status; + KERB_QUERY_TKT_CACHE_REQUEST *query; + KERB_QUERY_TKT_CACHE_RESPONSE *resp; + ULONG tickets_allocated; + krb5_error_code error; + krb5_context context = NULL; + krb5_cccol_cursor cursor; + krb5_ccache cache; + + if (!in || in_len != sizeof(KERB_QUERY_TKT_CACHE_REQUEST) || !out || !out_len) + return STATUS_INVALID_PARAMETER; + + query = (KERB_QUERY_TKT_CACHE_REQUEST *)in; + + if (query->LogonId.HighPart != 0 || query->LogonId.LowPart != 0) + return STATUS_ACCESS_DENIED; + + tickets_allocated = 16; + resp = heap_alloc(sizeof(*resp) + sizeof(resp->Tickets[0]) * (tickets_allocated - 1)); + if (!resp) return STATUS_NO_MEMORY; + + resp->MessageType = KerbQueryTicketCacheMessage; + resp->CountOfTickets = 0; + + error = p_krb5_init_context(&context); + if (error) + { + status = krb5_error_to_status(error); + goto done; + } + + error = p_krb5_cccol_cursor_new(context, &cursor); + if (error) + { + status = krb5_error_to_status(error); + goto done; + } + + for (;;) + { + error = p_krb5_cccol_cursor_next(context, cursor, &cache); + if (error || !cache) + { + status = krb5_error_to_status(error); + break; + } + + if (resp->CountOfTickets == tickets_allocated) + { + KERB_QUERY_TKT_CACHE_RESPONSE *new_resp; + + tickets_allocated *= 2; + new_resp = heap_realloc(resp, sizeof(*resp) + sizeof(resp->Tickets[0]) * (tickets_allocated - 1)); + if (!new_resp) + { + status = STATUS_NO_MEMORY; + p_krb5_cc_close(context, cache); + break; + } + + resp = new_resp; + } + + status = copy_ticket_info(context, cache, &resp->Tickets[resp->CountOfTickets]); + if (status == STATUS_SUCCESS) + resp->CountOfTickets++; + + p_krb5_cc_close(context, cache); + } + + if (status) goto done; + + /* FIXME: copy server/realm names to client space as well. + * As long as LSA works in current process space it's OK. + */ + + *out_len = sizeof(*resp); + if (resp->CountOfTickets > 1) + *out_len += sizeof(resp->Tickets[0]) * (resp->CountOfTickets - 1); + status = lsa_dispatch.AllocateClientBuffer(lsa_req, *out_len, out); + if (status) goto done; + + status = lsa_dispatch.CopyToClientBuffer(lsa_req, *out_len, *out, resp); + if (status) + lsa_dispatch.FreeClientBuffer(lsa_req, *out); + +done: + if (context) + p_krb5_free_context(context); + heap_free(resp); + return status; +} + static NTSTATUS NTAPI krb5_LsaApCallPackageUntrusted(PLSA_CLIENT_REQUEST request, PVOID in_buffer, PVOID client_buffer_base, ULONG in_buffer_length, PVOID *out_buffer, PULONG out_buffer_length, PNTSTATUS status) { - FIXME("%p,%p,%p,%u,%p,%p,%p: stub\n", request, in_buffer, client_buffer_base, + KERB_PROTOCOL_MESSAGE_TYPE msg; + + TRACE("%p,%p,%p,%u,%p,%p,%p\n", request, in_buffer, client_buffer_base, in_buffer_length, out_buffer, out_buffer_length, status); - *status = STATUS_NOT_IMPLEMENTED; - return STATUS_NOT_IMPLEMENTED; + if (!in_buffer || in_buffer_length < sizeof(msg)) + return STATUS_INVALID_PARAMETER; + + msg = *(KERB_PROTOCOL_MESSAGE_TYPE *)in_buffer; + + switch(msg) + { + case KerbQueryTicketCacheMessage: + *status = query_ticket_cache(request, in_buffer, in_buffer_length, out_buffer, out_buffer_length); + break; + + case KerbRetrieveTicketMessage: + FIXME("KerbRetrieveTicketMessage stub\n"); + *status = STATUS_NOT_IMPLEMENTED; + break; + + case KerbPurgeTicketCacheMessage: + FIXME("KerbPurgeTicketCacheMessage stub\n"); + *status = STATUS_NOT_IMPLEMENTED; + break; + + default: /* All other requests should call LsaApCallPackage */ + WARN("%u => access denied\n", msg); + *status = STATUS_ACCESS_DENIED; + break; + } + + return *status; } static SECPKG_FUNCTION_TABLE krb5_table = diff --git a/include/ntsecapi.h b/include/ntsecapi.h index 36357c61b4..3193b10f42 100644 --- a/include/ntsecapi.h +++ b/include/ntsecapi.h @@ -352,6 +352,102 @@ static const WCHAR MICROSOFT_KERBEROS_NAME_W[] = { 'K','e','r','b','e','r','o',' #define MICROSOFT_KERBEROS_NAME_A "Kerberos" #endif +#define KERB_TICKET_FLAGS_reserved 0x80000000 +#define KERB_TICKET_FLAGS_forwardable 0x40000000 +#define KERB_TICKET_FLAGS_forwarded 0x20000000 +#define KERB_TICKET_FLAGS_proxiable 0x10000000 +#define KERB_TICKET_FLAGS_proxy 0x08000000 +#define KERB_TICKET_FLAGS_may_postdate 0x04000000 +#define KERB_TICKET_FLAGS_postdated 0x02000000 +#define KERB_TICKET_FLAGS_invalid 0x01000000 +#define KERB_TICKET_FLAGS_renewable 0x00800000 +#define KERB_TICKET_FLAGS_initial 0x00400000 +#define KERB_TICKET_FLAGS_pre_authent 0x00200000 +#define KERB_TICKET_FLAGS_hw_authent 0x00100000 +#define KERB_TICKET_FLAGS_ok_as_delegate 0x00040000 +#define KERB_TICKET_FLAGS_name_canonicalize 0x00010000 +#define KERB_TICKET_FLAGS_cname_in_pa_data 0x00040000 +#define KERB_TICKET_FLAGS_reserved1 0x00000001 + +typedef enum _KERB_PROTOCOL_MESSAGE_TYPE +{ + KerbDebugRequestMessage = 0, + KerbQueryTicketCacheMessage, + KerbChangeMachinePasswordMessage, + KerbVerifyPacMessage, + KerbRetrieveTicketMessage, + KerbUpdateAddressesMessage, + KerbPurgeTicketCacheMessage, + KerbChangePasswordMessage, + KerbRetrieveEncodedTicketMessage, + KerbDecryptDataMessage, + KerbAddBindingCacheEntryMessage, + KerbSetPasswordMessage, + KerbSetPasswordExMessage, + KerbVerifyCredentialsMessage, + KerbQueryTicketCacheExMessage, + KerbPurgeTicketCacheExMessage, + KerbRefreshSmartcardCredentialsMessage, + KerbAddExtraCredentialsMessage, + KerbQuerySupplementalCredentialsMessage, + KerbTransferCredentialsMessage, + KerbQueryTicketCacheEx2Message, + KerbSubmitTicketMessage, + KerbAddExtraCredentialsExMessage, + KerbQueryKdcProxyCacheMessage, + KerbPurgeKdcProxyCacheMessage, + KerbQueryTicketCacheEx3Message, + KerbCleanupMachinePkinitCredsMessage, + KerbAddBindingCacheEntryExMessage, + KerbQueryBindingCacheMessage, + KerbPurgeBindingCacheMessage, + KerbQueryDomainExtendedPoliciesMessage, + KerbQueryS4U2ProxyCacheMessage +} KERB_PROTOCOL_MESSAGE_TYPE, *PKERB_PROTOCOL_MESSAGE_TYPE; + +typedef struct _KERB_TICKET_CACHE_INFO +{ + UNICODE_STRING ServerName; + UNICODE_STRING RealmName; + LARGE_INTEGER StartTime; + LARGE_INTEGER EndTime; + LARGE_INTEGER RenewTime; + LONG EncryptionType; + ULONG TicketFlags; +} KERB_TICKET_CACHE_INFO, *PKERB_TICKET_CACHE_INFO; + +typedef struct _KERB_QUERY_TKT_CACHE_REQUEST +{ + KERB_PROTOCOL_MESSAGE_TYPE MessageType; + LUID LogonId; +} KERB_QUERY_TKT_CACHE_REQUEST, *PKERB_QUERY_TKT_CACHE_REQUEST; + +typedef struct _KERB_QUERY_TKT_CACHE_RESPONSE +{ + KERB_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG CountOfTickets; + KERB_TICKET_CACHE_INFO Tickets[ANYSIZE_ARRAY]; +} KERB_QUERY_TKT_CACHE_RESPONSE, *PKERB_QUERY_TKT_CACHE_RESPONSE; + +typedef struct _KERB_RETRIEVE_TKT_REQUEST +{ + KERB_PROTOCOL_MESSAGE_TYPE MessageType; + LUID LogonId; + UNICODE_STRING TargetName; + ULONG TicketFlags; + ULONG CacheOptions; + LONG EncryptionType; + SecHandle CredentialsHandle; +} KERB_RETRIEVE_TKT_REQUEST, *PKERB_RETRIEVE_TKT_REQUEST; + +typedef struct _KERB_PURGE_TKT_CACHE_REQUEST +{ + KERB_PROTOCOL_MESSAGE_TYPE MessageType; + LUID LogonId; + UNICODE_STRING ServerName; + UNICODE_STRING RealmName; +} KERB_PURGE_TKT_CACHE_REQUEST, *PKERB_PURGE_TKT_CACHE_REQUEST; + #define RtlGenRandom SystemFunction036 #define RtlEncryptMemory SystemFunction040 #define RtlDecryptMemory SystemFunction041 -- 2.14.1