Based on the code written by George Popoff.
Signed-off-by: Dmitry Timoshkov <dmitry(a)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
--
2.15.1