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.
-- v4: klist: Add a program that lists Kerberos tickets.
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 | 2 + configure.ac | 1 + programs/klist/Makefile.in | 7 + programs/klist/main.c | 389 +++++++++++++++++++++++++++++++++++++ 4 files changed, 399 insertions(+) create mode 100644 programs/klist/Makefile.in create mode 100644 programs/klist/main.c
diff --git a/configure b/configure index 4d3108303b8..be4790eb75f 100755 --- a/configure +++ b/configure @@ -1635,6 +1635,7 @@ enable_icacls enable_icinfo enable_iexplore enable_ipconfig +enable_klist enable_lodctr enable_mofcomp enable_mshta @@ -22175,6 +22176,7 @@ wine_fn_config_makefile programs/icacls enable_icacls wine_fn_config_makefile programs/icinfo enable_icinfo wine_fn_config_makefile programs/iexplore enable_iexplore wine_fn_config_makefile programs/ipconfig enable_ipconfig +wine_fn_config_makefile programs/klist enable_klist wine_fn_config_makefile programs/lodctr enable_lodctr wine_fn_config_makefile programs/mofcomp enable_mofcomp wine_fn_config_makefile programs/mshta enable_mshta 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..cd0b644ca8d --- /dev/null +++ b/programs/klist/Makefile.in @@ -0,0 +1,7 @@ +MODULE = klist.exe +IMPORTS = secur32 advapi32 + +EXTRADLLFLAGS = -mconsole -municode + +C_SRCS = \ + main.c diff --git a/programs/klist/main.c b/programs/klist/main.c new file mode 100644 index 00000000000..93451faf70a --- /dev/null +++ b/programs/klist/main.c @@ -0,0 +1,389 @@ +/* + * 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 SECURITY_WIN32 +#define UNICODE +#include <windows.h> +#include <security.h> +#include <ntsecapi.h> +#include <ntstatus.h> +#include <winnls.h> +#include <winnt.h> + +#include "wine/debug.h" + +#define USAGE L"Usage: klist <tickets | tgt | purge | get [service principal name]>\n" + +#define EXPAND_KERB_ETYPE(x) case KERB_ETYPE_##x: return L ## #x + +#define EXPAND_KERB_ETYPES(x) switch (x) { \ + EXPAND_KERB_ETYPE(NULL); \ + EXPAND_KERB_ETYPE(DES_CBC_CRC); \ + EXPAND_KERB_ETYPE(DES_CBC_MD4); \ + EXPAND_KERB_ETYPE(DES_CBC_MD5); \ + EXPAND_KERB_ETYPE(AES128_CTS_HMAC_SHA1_96); \ + EXPAND_KERB_ETYPE(AES256_CTS_HMAC_SHA1_96); \ + EXPAND_KERB_ETYPE(RC4_MD4); \ + EXPAND_KERB_ETYPE(RC4_PLAIN2); \ + EXPAND_KERB_ETYPE(RC4_LM); \ + EXPAND_KERB_ETYPE(RC4_SHA); \ + EXPAND_KERB_ETYPE(DES_PLAIN); \ + EXPAND_KERB_ETYPE(RC4_HMAC_OLD); \ + EXPAND_KERB_ETYPE(RC4_PLAIN_OLD); \ + EXPAND_KERB_ETYPE(RC4_HMAC_OLD_EXP); \ + EXPAND_KERB_ETYPE(RC4_PLAIN_OLD_EXP); \ + EXPAND_KERB_ETYPE(RC4_PLAIN); \ + EXPAND_KERB_ETYPE(RC4_PLAIN_EXP); \ + EXPAND_KERB_ETYPE(AES128_CTS_HMAC_SHA1_96_PLAIN); \ + EXPAND_KERB_ETYPE(AES256_CTS_HMAC_SHA1_96_PLAIN); \ + EXPAND_KERB_ETYPE(DSA_SHA1_CMS); \ + EXPAND_KERB_ETYPE(RSA_MD5_CMS); \ + EXPAND_KERB_ETYPE(RSA_SHA1_CMS); \ + EXPAND_KERB_ETYPE(RC2_CBC_ENV); \ + EXPAND_KERB_ETYPE(RSA_ENV); \ + EXPAND_KERB_ETYPE(RSA_ES_OEAP_ENV); \ + EXPAND_KERB_ETYPE(DES_EDE3_CBC_ENV); \ + EXPAND_KERB_ETYPE(DSA_SIGN); \ + EXPAND_KERB_ETYPE(DES3_CBC_MD5); \ + EXPAND_KERB_ETYPE(DES3_CBC_SHA1); \ + EXPAND_KERB_ETYPE(DES3_CBC_SHA1_KD); \ + EXPAND_KERB_ETYPE(DES_CBC_MD5_NT); \ + EXPAND_KERB_ETYPE(RC4_HMAC_NT); \ + EXPAND_KERB_ETYPE(RC4_HMAC_NT_EXP); \ + default: return NULL; } + +#define EXPAND_KERB_TICKET_FLAG(x, y) if (x & KERB_TICKET_FLAGS_##y) { wprintf(L" %ls", L ## #y); } + +#define EXPAND_KERB_TICKET_FLAGS(x) EXPAND_KERB_TICKET_FLAG(x, reserved) \ + EXPAND_KERB_TICKET_FLAG(x, forwardable) \ + EXPAND_KERB_TICKET_FLAG(x, forwarded) \ + EXPAND_KERB_TICKET_FLAG(x, proxiable) \ + EXPAND_KERB_TICKET_FLAG(x, proxy) \ + EXPAND_KERB_TICKET_FLAG(x, may_postdate) \ + EXPAND_KERB_TICKET_FLAG(x, postdated) \ + EXPAND_KERB_TICKET_FLAG(x, invalid) \ + EXPAND_KERB_TICKET_FLAG(x, renewable) \ + EXPAND_KERB_TICKET_FLAG(x, initial) \ + EXPAND_KERB_TICKET_FLAG(x, pre_authent) \ + EXPAND_KERB_TICKET_FLAG(x, hw_authent) \ + EXPAND_KERB_TICKET_FLAG(x, ok_as_delegate) \ + EXPAND_KERB_TICKET_FLAG(x, name_canonicalize) \ + EXPAND_KERB_TICKET_FLAG(x, cname_in_pa_data) \ + EXPAND_KERB_TICKET_FLAG(x, reserved1) + +WINE_DEFAULT_DEBUG_CHANNEL(klist); + +static WCHAR* get_etype_text(const LONG encryption_type) +{ + EXPAND_KERB_ETYPES(encryption_type) +} + +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) + { + WINE_ERR("OpenProcessToken failed\n"); + return err; + } + + err = GetTokenInformation(token_handle, TokenStatistics, &token_statistics, + sizeof(token_statistics), &token_statistics_len); + if (!err) + { + WINE_ERR("GetTokenInformation failed\n"); + return err; + } + + *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) + { + WINE_ERR("FileTimeToSystemTime failed\n"); + return FALSE; + } + status = SystemTimeToTzSpecificLocalTime(NULL, &system_time, &system_time); + if (!status) + { + WINE_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) + { + WINE_ERR("GetDateFormatEx failed\n"); + return FALSE; + } + + time_len = GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, 0, &system_time, NULL, NULL, 0); + if (time_len == 0) + { + WINE_ERR("GetTimeFormatEx failed\n"); + return FALSE; + } + + date = HeapAlloc(GetProcessHeap(), 0, (date_len + time_len + 1) * sizeof(date[0])); + if (!date) + { + WINE_ERR("HeapAlloc failed\n"); + return FALSE; + } + + time = HeapAlloc(GetProcessHeap(), 0, time_len * sizeof(time[0])); + if (!time) + { + WINE_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) + { + WINE_ERR("GetDateFormatEx failed\n"); + return FALSE; + } + + time_len = GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, 0, &system_time, NULL, time, + time_len); + if (time_len == 0) + { + WINE_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 = 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(L"Unknown error\n"); + WINE_ERR("get_process_logon_id failed\n"); + return 1; + } + + wprintf(L"\n"); + wprintf(L"Current LogonId is %ld:0x%lx\n", logon_id.HighPart, logon_id.LowPart); + wprintf(L"\n"); + + err = LsaConnectUntrusted(&lsa_handle); + if (err != STATUS_SUCCESS) + { + wprintf(L"Unknown error\n"); + WINE_ERR("LsaConnectUntrusted NTSTATUS %X\n", err); + return 1; + } + + err = LsaLookupAuthenticationPackage(lsa_handle, &package_name, &kerberos_package); + if (err != STATUS_SUCCESS) + { + wprintf(L"Unknown error\n"); + WINE_ERR("LsaLookupAuthenticationPackage NTSTATUS %X\n", err); + return 1; + } + + WINE_TRACE("Kerberos LSA package: %d\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(L"Unknown error\n"); + WINE_ERR("LsaCallAuthenticationPackage NTSTATUS %X\n", err); + return 1; + } + + wprintf(L"Cached Tickets: (%d)\n", 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(L"Unknown error\n"); + WINE_ERR("format_dates_and_times failed\n"); + return 1; + } + + wprintf(L"\n"); + wprintf(L"#%ld>", i); + + wprintf(L" Server: %.*ls @ %.*ls\n", + 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" KerbTicket Encryption Type: "); + if (etype_text) + { + wprintf(L"%s\n", etype_text); + } + else + { + wprintf(L"%ld\n", ticket.EncryptionType); + } + + wprintf(L" Ticket Flags: 0x%lx ->", ticket.TicketFlags); + EXPAND_KERB_TICKET_FLAGS(ticket.TicketFlags) + wprintf(L"\n"); + + wprintf(L" Start Time: %ls (local)\n", dates[0]); + wprintf(L" End Time: %ls (local)\n", dates[1]); + wprintf(L" Renew Time: %ls (local)\n", dates[2]); + + for (int i = 0; i < 3; i++) + HeapFree(GetProcessHeap(), 0, (void*)dates[i]); + } + + err = LsaFreeReturnBuffer(kerberos_cache); + if (err != STATUS_SUCCESS) + { + wprintf(L"Unknown error\n"); + WINE_ERR("LsaFreeReturnBuffer NTSTATUS %X\n", err); + return 1; + } + + err = LsaDeregisterLogonProcess(lsa_handle); + if (err != STATUS_SUCCESS) + { + wprintf(L"Unknown error\n"); + WINE_ERR("LsaDeregisterLogonProcess NTSTATUS %X\n", err); + return 1; + } + + return 0; +} + +static int tgt(void) +{ + WINE_ERR("stub\n"); + + return 0; +} + +static int purge(void) +{ + WINE_ERR("stub\n"); + + return 0; +} + +static int get(const WCHAR *const principal) +{ + WINE_ERR("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(USAGE); + return 1; + } + + return get(argv[2]); + } + else + { + wprintf(USAGE); + return 1; + } +}