From: Maxim Karasev mxkrsv@etersoft.ru
It's a clean-room reimplementation that mimics Windows 10 program's output format. It prints all the information that is available via KerbQueryTicketCacheMessage.
Also tested to work on Windows if dynamically linked + built with winegcc.
For further extension of the functonality, implementing KerbQueryTicketCacheEx{,2,3}Message is required. --- configure.ac | 1 + programs/klist/Makefile.in | 10 + programs/klist/klist.rc | 37 ++++ programs/klist/main.c | 397 +++++++++++++++++++++++++++++++++++++ programs/klist/resources.h | 30 +++ 5 files changed, 475 insertions(+) create mode 100644 programs/klist/Makefile.in create mode 100644 programs/klist/klist.rc create mode 100644 programs/klist/main.c create mode 100644 programs/klist/resources.h
diff --git a/configure.ac b/configure.ac index c9ed1c8c431..fc8a5ff03d5 100644 --- a/configure.ac +++ b/configure.ac @@ -3369,6 +3369,7 @@ WINE_CONFIG_MAKEFILE(programs/icacls) WINE_CONFIG_MAKEFILE(programs/icinfo) WINE_CONFIG_MAKEFILE(programs/iexplore) WINE_CONFIG_MAKEFILE(programs/ipconfig) +WINE_CONFIG_MAKEFILE(programs/klist) WINE_CONFIG_MAKEFILE(programs/lodctr) WINE_CONFIG_MAKEFILE(programs/mofcomp) WINE_CONFIG_MAKEFILE(programs/mshta) diff --git a/programs/klist/Makefile.in b/programs/klist/Makefile.in new file mode 100644 index 00000000000..d9777562091 --- /dev/null +++ b/programs/klist/Makefile.in @@ -0,0 +1,10 @@ +MODULE = klist.exe +IMPORTS = secur32 advapi32 user32 + +EXTRADLLFLAGS = -mconsole -municode + +C_SRCS = \ + main.c + +RC_SRCS = \ + klist.rc diff --git a/programs/klist/klist.rc b/programs/klist/klist.rc new file mode 100644 index 00000000000..e46753ed5fd --- /dev/null +++ b/programs/klist/klist.rc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Maxim Karasev mxkrsv@etersoft.ru + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "resources.h" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +STRINGTABLE +{ + STRING_USAGE, "Usage: klist <tickets | tgt | purge | get [service principal name]>\n\0" + STRING_UNKNOWN_ERROR, "Unknown error\n\0" + STRING_START_TIME, "Start Time\0" + STRING_END_TIME, "End Time\0" + STRING_RENEW_TIME, "Renew Time\0" + STRING_TICKET_FLAGS, "Ticket Flags\0" + STRING_CACHED_TICKETS, "Cached Tickets\0" + STRING_SERVER, "Server\0" + STRING_ENCRYPTION_TYPE, "KerbTicket Encryption Type\0" + STRING_LOGON_ID, "Current LogonId is\0" +} diff --git a/programs/klist/main.c b/programs/klist/main.c new file mode 100644 index 00000000000..a0aa4959347 --- /dev/null +++ b/programs/klist/main.c @@ -0,0 +1,397 @@ +/* + * Copyright 2023 Maxim Karasev mxkrsv@etersoft.ru + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define WIN32_LEAN_AND_MEAN +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include <windows.h> +#include <ntsecapi.h> + +#include "wine/debug.h" + +#include "resources.h" + +WINE_DEFAULT_DEBUG_CHANNEL(klist); + +static const WCHAR *resource_fallback = L"RESOURCELOADFAILED"; + +static const WCHAR *load_resource(UINT id) +{ + WCHAR *res = NULL; + + if (!LoadStringW(GetModuleHandleW(NULL), id, (LPWSTR)&res, 0)) + { + WINE_ERR("LoadString failed with %ld\n", GetLastError()); + return resource_fallback; + } + + return res; +} + +static const WCHAR *get_etype_text(const LONG encryption_type) +{ + switch (encryption_type) { +#define EXPAND_ETYPE(x) case KERB_ETYPE_##x: return L ## #x; + EXPAND_ETYPE(NULL) + EXPAND_ETYPE(DES_CBC_CRC) + EXPAND_ETYPE(DES_CBC_MD4) + EXPAND_ETYPE(DES_CBC_MD5) + EXPAND_ETYPE(AES128_CTS_HMAC_SHA1_96) + EXPAND_ETYPE(AES256_CTS_HMAC_SHA1_96) + EXPAND_ETYPE(RC4_MD4) + EXPAND_ETYPE(RC4_PLAIN2) + EXPAND_ETYPE(RC4_LM) + EXPAND_ETYPE(RC4_SHA) + EXPAND_ETYPE(DES_PLAIN) + EXPAND_ETYPE(RC4_HMAC_OLD) + EXPAND_ETYPE(RC4_PLAIN_OLD) + EXPAND_ETYPE(RC4_HMAC_OLD_EXP) + EXPAND_ETYPE(RC4_PLAIN_OLD_EXP) + EXPAND_ETYPE(RC4_PLAIN) + EXPAND_ETYPE(RC4_PLAIN_EXP) + EXPAND_ETYPE(AES128_CTS_HMAC_SHA1_96_PLAIN) + EXPAND_ETYPE(AES256_CTS_HMAC_SHA1_96_PLAIN) + EXPAND_ETYPE(DSA_SHA1_CMS) + EXPAND_ETYPE(RSA_MD5_CMS) + EXPAND_ETYPE(RSA_SHA1_CMS) + EXPAND_ETYPE(RC2_CBC_ENV) + EXPAND_ETYPE(RSA_ENV) + EXPAND_ETYPE(RSA_ES_OEAP_ENV) + EXPAND_ETYPE(DES_EDE3_CBC_ENV) + EXPAND_ETYPE(DSA_SIGN) + EXPAND_ETYPE(DES3_CBC_MD5) + EXPAND_ETYPE(DES3_CBC_SHA1) + EXPAND_ETYPE(DES3_CBC_SHA1_KD) + EXPAND_ETYPE(DES_CBC_MD5_NT) + EXPAND_ETYPE(RC4_HMAC_NT) + EXPAND_ETYPE(RC4_HMAC_NT_EXP) +#undef EXPAND_ETYPE + default: return NULL; + } +} + +static BOOL get_process_logon_id(LUID *const logon_id) +{ + HANDLE token_handle; + TOKEN_STATISTICS token_statistics; + DWORD token_statistics_len; + BOOL err; + + err = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle); + if (!err) + { + ERR("OpenProcessToken failed\n"); + return FALSE; + } + + err = GetTokenInformation(token_handle, TokenStatistics, &token_statistics, + sizeof(token_statistics), &token_statistics_len); + if (!err) + { + ERR("GetTokenInformation failed\n"); + return FALSE; + } + + *logon_id = token_statistics.AuthenticationId; + + return TRUE; +} + +static BOOL format_dates_and_times(const FILETIME *const filetimes[], + const WCHAR *output_strings[], const ULONG count) +{ + ULONG i; + + for (i = 0; i < count; i++) + { + SYSTEMTIME system_time; + BOOL status; + int date_len; + int time_len; + WCHAR *date; + WCHAR *time; + + status = FileTimeToSystemTime(filetimes[i], &system_time); + if (!status) + { + ERR("FileTimeToSystemTime failed\n"); + return FALSE; + } + status = SystemTimeToTzSpecificLocalTime(NULL, &system_time, &system_time); + if (!status) + { + ERR("SystemTimeToTzSpecificLocalTime failed\n"); + return FALSE; + } + + date_len = GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_SHORTDATE, &system_time, + NULL, NULL, 0, NULL); + if (date_len == 0) + { + ERR("GetDateFormatEx failed\n"); + return FALSE; + } + + time_len = GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, 0, &system_time, NULL, NULL, 0); + if (time_len == 0) + { + ERR("GetTimeFormatEx failed\n"); + return FALSE; + } + + date = HeapAlloc(GetProcessHeap(), 0, (date_len + time_len + 1) * sizeof(date[0])); + if (!date) + { + ERR("HeapAlloc failed\n"); + return FALSE; + } + + time = HeapAlloc(GetProcessHeap(), 0, time_len * sizeof(time[0])); + if (!time) + { + ERR("HeapAlloc failed\n"); + return FALSE; + } + + date_len = GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, DATE_SHORTDATE, &system_time, + NULL, date, date_len, NULL); + if (date_len == 0) + { + ERR("GetDateFormatEx failed\n"); + return FALSE; + } + + time_len = GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, 0, &system_time, NULL, time, + time_len); + if (time_len == 0) + { + ERR("GetTimeFormatEx failed\n"); + return FALSE; + } + + wcscat(date, L" "); + wcscat(date, time); + + output_strings[i] = date; + } + + return TRUE; +} + +static int tickets(void) +{ + LUID logon_id; + BOOL status; + HANDLE lsa_handle; + NTSTATUS err; + ULONG i; + LSA_STRING package_name = { + .Buffer = (char*)MICROSOFT_KERBEROS_NAME_A, + .Length = strlen(MICROSOFT_KERBEROS_NAME_A), + .MaximumLength = strlen(MICROSOFT_KERBEROS_NAME_A), + }; + ULONG kerberos_package; + + KERB_QUERY_TKT_CACHE_REQUEST kerberos_cache_request = { + .MessageType = KerbQueryTicketCacheMessage, + .LogonId = {0}, + }; + KERB_QUERY_TKT_CACHE_RESPONSE *kerberos_cache; + ULONG kerberos_cache_size; + NTSTATUS kerberos_call_status; + + status = get_process_logon_id(&logon_id); + if (!status) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("get_process_logon_id failed\n"); + return 1; + } + + wprintf(L"\n"); + wprintf(L"%ls %ld:0x%lx\n", load_resource(STRING_LOGON_ID), logon_id.HighPart, logon_id.LowPart); + wprintf(L"\n"); + + err = LsaConnectUntrusted(&lsa_handle); + if (err != STATUS_SUCCESS) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("LsaConnectUntrusted NTSTATUS %lX\n", err); + return 1; + } + + err = LsaLookupAuthenticationPackage(lsa_handle, &package_name, &kerberos_package); + if (err != STATUS_SUCCESS) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("LsaLookupAuthenticationPackage NTSTATUS %lX\n", err); + return 1; + } + + TRACE("Kerberos LSA package: %lu\n", kerberos_package); + + err = LsaCallAuthenticationPackage(lsa_handle, kerberos_package, &kerberos_cache_request, + sizeof(kerberos_cache_request), (PVOID*)&kerberos_cache, &kerberos_cache_size, + &kerberos_call_status); + if (err != STATUS_SUCCESS) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("LsaCallAuthenticationPackage NTSTATUS %lX\n", err); + return 1; + } + + wprintf(L"%ls: (%d)\n", load_resource(STRING_CACHED_TICKETS), kerberos_cache->CountOfTickets); + + for (i = 0; i < kerberos_cache->CountOfTickets; i++) + { + const KERB_TICKET_CACHE_INFO ticket = kerberos_cache->Tickets[i]; + const FILETIME *const filetimes[] = { (const FILETIME*)&ticket.StartTime, + (const FILETIME*)&ticket.EndTime, (const FILETIME*)&ticket.RenewTime }; + const WCHAR *dates[3]; + const WCHAR *etype_text; + + status = format_dates_and_times(filetimes, dates, 3); + if (!status) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("format_dates_and_times failed\n"); + return 1; + } + + wprintf(L"\n"); + wprintf(L"#%ld>", i); + + wprintf(L" %ls: %.*ls @ %.*ls\n", load_resource(STRING_SERVER), + ticket.ServerName.Length / sizeof(WCHAR), ticket.ServerName.Buffer, + ticket.RealmName.Length / sizeof(WCHAR), ticket.RealmName.Buffer); + + etype_text = get_etype_text(ticket.EncryptionType); + wprintf(L" %ls: ", load_resource(STRING_ENCRYPTION_TYPE)); + if (etype_text) + { + wprintf(L"%s\n", etype_text); + } + else + { + wprintf(L"%ld\n", ticket.EncryptionType); + } + + wprintf(L" %ls: 0x%lx ->", load_resource(STRING_TICKET_FLAGS), ticket.TicketFlags); +#define EXPAND_TICKET_FLAG(x) if (ticket.TicketFlags & KERB_TICKET_FLAGS_##x) { wprintf(L" %ls", L ## #x); } + EXPAND_TICKET_FLAG(reserved) + EXPAND_TICKET_FLAG(forwardable) + EXPAND_TICKET_FLAG(forwarded) + EXPAND_TICKET_FLAG(proxiable) + EXPAND_TICKET_FLAG(proxy) + EXPAND_TICKET_FLAG(may_postdate) + EXPAND_TICKET_FLAG(postdated) + EXPAND_TICKET_FLAG(invalid) + EXPAND_TICKET_FLAG(renewable) + EXPAND_TICKET_FLAG(initial) + EXPAND_TICKET_FLAG(pre_authent) + EXPAND_TICKET_FLAG(hw_authent) + EXPAND_TICKET_FLAG(ok_as_delegate) + EXPAND_TICKET_FLAG(name_canonicalize) + EXPAND_TICKET_FLAG(cname_in_pa_data) + EXPAND_TICKET_FLAG(reserved1) +#undef EXPAND_TICKET_FLAG + wprintf(L"\n"); + + wprintf(L" %ls: %ls (local)\n", load_resource(STRING_START_TIME), dates[0]); + wprintf(L" %ls: %ls (local)\n", load_resource(STRING_END_TIME), dates[1]); + wprintf(L" %ls: %ls (local)\n", load_resource(STRING_RENEW_TIME), dates[2]); + + for (int i = 0; i < 3; i++) + HeapFree(GetProcessHeap(), 0, (void*)dates[i]); + } + + err = LsaFreeReturnBuffer(kerberos_cache); + if (err != STATUS_SUCCESS) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("LsaFreeReturnBuffer NTSTATUS %lX\n", err); + return 1; + } + + err = LsaDeregisterLogonProcess(lsa_handle); + if (err != STATUS_SUCCESS) + { + wprintf(load_resource(STRING_UNKNOWN_ERROR)); + ERR("LsaDeregisterLogonProcess NTSTATUS %lX\n", err); + return 1; + } + + return 0; +} + +static int tgt(void) +{ + FIXME("stub\n"); + + return 0; +} + +static int purge(void) +{ + FIXME("stub\n"); + + return 0; +} + +static int get(const WCHAR *const principal) +{ + FIXME("stub\n"); + + return 0; +} + +int __cdecl wmain(int argc, WCHAR *argv[]) +{ + if (argc < 2) + { + return tickets(); + } + + if (!wcscmp(argv[1], L"tickets")) + { + return tickets(); + } + else if (!wcscmp(argv[1], L"tgt")) + { + return tgt(); + } + else if (!wcscmp(argv[1], L"purge")) + { + return purge(); + } + else if (!wcscmp(argv[1], L"get")) + { + if (argc < 3) + { + wprintf(load_resource(STRING_USAGE)); + return 1; + } + + return get(argv[2]); + } + else + { + wprintf(load_resource(STRING_USAGE)); + return 1; + } +} diff --git a/programs/klist/resources.h b/programs/klist/resources.h new file mode 100644 index 00000000000..5d1437cec40 --- /dev/null +++ b/programs/klist/resources.h @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Maxim Karasev mxkrsv@etersoft.ru + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <windef.h> + +#define STRING_USAGE 1000 +#define STRING_UNKNOWN_ERROR 1001 +#define STRING_START_TIME 1002 +#define STRING_END_TIME 1003 +#define STRING_RENEW_TIME 1004 +#define STRING_TICKET_FLAGS 1005 +#define STRING_CACHED_TICKETS 1006 +#define STRING_SERVER 1007 +#define STRING_ENCRYPTION_TYPE 1008 +#define STRING_LOGON_ID 1009