Based on the code written by George Popoff.
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/kerberos/krb5_ap.c | 329 +++++++++++++++++++++++++++++++++++++++++++++++- include/ntsecapi.h | 105 ++++++++++++++++ 2 files changed, 431 insertions(+), 3 deletions(-)
diff --git a/dlls/kerberos/krb5_ap.c b/dlls/kerberos/krb5_ap.c index 0134b94f6d..3ee2cf05b7 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" @@ -48,8 +50,43 @@ static void *libkrb5_handle;
#define MAKE_FUNCPTR(f) static typeof(f) * p_##f MAKE_FUNCPTR(krb5_init_context); +MAKE_FUNCPTR(krb5_free_context); +MAKE_FUNCPTR(krb5_free_ticket); +MAKE_FUNCPTR(krb5_cccol_cursor_new); +MAKE_FUNCPTR(krb5_cccol_cursor_next); +MAKE_FUNCPTR(krb5_cccol_cursor_free); +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_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); +} + +struct ticket_info +{ + ULONG count, allocated; + KERB_TICKET_CACHE_INFO *info; +}; + static void load_krb5(void) { if (!(libkrb5_handle = wine_dlopen(SONAME_LIBKRB5, RTLD_NOW, NULL, 0))) @@ -66,6 +103,20 @@ static void load_krb5(void) }
LOAD_FUNCPTR(krb5_init_context) + LOAD_FUNCPTR(krb5_free_context) + LOAD_FUNCPTR(krb5_free_ticket) + LOAD_FUNCPTR(krb5_cccol_cursor_new) + LOAD_FUNCPTR(krb5_cccol_cursor_next) + LOAD_FUNCPTR(krb5_cccol_cursor_free) + 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_flags) + LOAD_FUNCPTR(krb5_free_unparsed_name) + LOAD_FUNCPTR(krb5_free_cred_contents) #undef LOAD_FUNCPTR
return; @@ -111,15 +162,287 @@ 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 len; + WCHAR *wstr; + + len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); + wstr = heap_alloc(len * sizeof(WCHAR)); + if (wstr) + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len); + + return wstr; +} + +static NTSTATUS copy_tickets_from_cache(krb5_context context, krb5_ccache cache, struct ticket_info *info) +{ + NTSTATUS status; + krb5_cc_cursor cursor; + krb5_error_code error; + krb5_creds credentials; + krb5_ticket *ticket; + char *name_with_realm, *name_without_realm, *realm_name; + WCHAR *realm_nameW, *name_without_realmW; + + error = p_krb5_cc_start_seq_get(context, cache, &cursor); + if (error) return krb5_error_to_status(error); + + for (;;) + { + error = p_krb5_cc_next_cred(context, cache, &cursor, &credentials); + if (error) + { + if (error == KRB5_CC_END) + status = STATUS_SUCCESS; + else + status = krb5_error_to_status(error); + break; + } + + if (p_krb5_is_config_principal(context, credentials.server)) + { + p_krb5_free_cred_contents(context, &credentials); + continue; + } + + if (info->count == info->allocated) + { + KERB_TICKET_CACHE_INFO *new_info; + ULONG new_allocated; + + if (info->allocated) + { + new_allocated = info->allocated * 2; + new_info = heap_realloc(info->info, sizeof(*new_info) * new_allocated); + } + else + { + new_allocated = 16; + new_info = heap_alloc(sizeof(*new_info) * new_allocated); + } + if (!new_info) + { + p_krb5_free_cred_contents(context, &credentials); + status = STATUS_NO_MEMORY; + break; + } + + info->info = new_info; + info->allocated = new_allocated; + } + + error = p_krb5_unparse_name_flags(context, credentials.server, 0, &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(&info->info[info->count].ServerName, name_without_realmW); + + realm_name = strchr(name_with_realm, '@'); + if (!realm_name) + { + ERR("wrong name with realm %s\n", debugstr_a(name_with_realm)); + realm_name = name_with_realm; + } + else + realm_name++; + + /* realm_name - now contains only realm! */ + + realm_nameW = utf8_to_wstr(realm_name); + RtlInitUnicodeString(&info->info[info->count].RealmName, realm_nameW); + + if (!credentials.times.starttime) + credentials.times.starttime = credentials.times.authtime; + + /* TODO: if krb5_is_config_principal = true */ + RtlSecondsSince1970ToTime(credentials.times.starttime, &info->info[info->count].StartTime); + RtlSecondsSince1970ToTime(credentials.times.endtime, &info->info[info->count].EndTime); + RtlSecondsSince1970ToTime(credentials.times.renew_till, &info->info[info->count].RenewTime); + + info->info[info->count].TicketFlags = credentials.ticket_flags; + + error = p_krb5_decode_ticket(&credentials.ticket, &ticket); + + 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); + + if (error) + { + status = krb5_error_to_status(error); + break; + } + + info->info[info->count].EncryptionType = ticket->enc_part.enctype; + + p_krb5_free_ticket(context, ticket); + + info->count++; + } + + 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; + struct ticket_info info; + krb5_error_code error; + krb5_context context = NULL; + krb5_cccol_cursor cursor = NULL; + 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; + + info.count = 0; + info.allocated = 0; + info.info = NULL; + + 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) + { + status = krb5_error_to_status(error); + goto done; + } + if (!cache) break; + + status = copy_tickets_from_cache(context, cache, &info); + + p_krb5_cc_close(context, cache); + + if (status != STATUS_SUCCESS) + 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(KERB_QUERY_TKT_CACHE_RESPONSE); + if (info.count > 1) + *out_len += sizeof(KERB_TICKET_CACHE_INFO) * (info.count - 1); + status = lsa_dispatch.AllocateClientBuffer(lsa_req, *out_len, out); + if (status == STATUS_SUCCESS) + { + KERB_QUERY_TKT_CACHE_RESPONSE resp; + + resp.MessageType = KerbQueryTicketCacheMessage; + resp.CountOfTickets = info.count; + + status = lsa_dispatch.CopyToClientBuffer(lsa_req, sizeof(ULONG) * 2, *out, &resp); + if (status == STATUS_SUCCESS) + status = lsa_dispatch.CopyToClientBuffer(lsa_req, + sizeof(KERB_TICKET_CACHE_INFO) * info.count, (char *)*out + sizeof(ULONG) * 2, info.info); + + if (status != STATUS_SUCCESS) + lsa_dispatch.FreeClientBuffer(lsa_req, *out); + } + +done: + if (cursor) + p_krb5_cccol_cursor_free(context, &cursor); + + if (context) + p_krb5_free_context(context); + + heap_free(info.info); + + 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..463bedf09a 100644 --- a/include/ntsecapi.h +++ b/include/ntsecapi.h @@ -169,6 +169,15 @@ typedef struct _OBJECT_ATTRIBUTES { } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; #endif
+#ifndef __SECHANDLE_DEFINED__ +#define __SECHANDLE_DEFINED__ +typedef struct _SecHandle +{ + ULONG_PTR dwLower; + ULONG_PTR dwUpper; +} SecHandle, *PSecHandle; +#endif + typedef UNICODE_STRING LSA_UNICODE_STRING, *PLSA_UNICODE_STRING; typedef STRING LSA_STRING, *PLSA_STRING; typedef OBJECT_ATTRIBUTES LSA_OBJECT_ATTRIBUTES, *PLSA_OBJECT_ATTRIBUTES; @@ -352,6 +361,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