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.
-- v12: 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.ac | 1 + programs/klist/Makefile.in | 10 + programs/klist/klist.rc | 37 ++++ programs/klist/main.c | 388 +++++++++++++++++++++++++++++++++++++ programs/klist/resources.h | 30 +++ 5 files changed, 466 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..2f3605f2a0a --- /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" + STRING_UNKNOWN_ERROR, "Unknown error\n" + STRING_START_TIME, "Start Time" + STRING_END_TIME, "End Time" + STRING_RENEW_TIME, "Renew Time" + STRING_TICKET_FLAGS, "Ticket Flags" + STRING_CACHED_TICKETS, "Cached Tickets" + STRING_SERVER, "Server" + STRING_ENCRYPTION_TYPE, "KerbTicket Encryption Type" + STRING_LOGON_ID, "Current LogonId is" +} diff --git a/programs/klist/main.c b/programs/klist/main.c new file mode 100644 index 00000000000..a4105cbcfe2 --- /dev/null +++ b/programs/klist/main.c @@ -0,0 +1,388 @@ +/* + * 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 WCHAR msg_buf[1024]; +static const WCHAR *load_resource(UINT id) +{ + if (!LoadStringW(GetModuleHandleW(NULL), id, msg_buf, ARRAY_SIZE(msg_buf))) + ERR("LoadString failed with %lu\n", GetLastError()); + return msg_buf; +} + +static const WCHAR *get_etype_text(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 *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[], 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++) + { + KERB_TICKET_CACHE_INFO ticket = kerberos_cache->Tickets[i]; + const FILETIME *const filetimes[] = { (FILETIME*)&ticket.StartTime, + (FILETIME*)&ticket.EndTime, (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(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) +#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 *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
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=135042
Your paranoid android.
=== debian11b (64 bit WoW report) ===
win32u: win32u.c:1059: Test failed: win32u:win32u:09fc done (0) in 0s 1406B
This merge request was approved by Hans Leidekker.