From: Guillaume Raffin <theelectronwill@gmail.com> --- dlls/winscard/tests/winscard.c | 134 +++++++++++++++++++++++++++++++++ dlls/winscard/winscard.c | 114 ++++++++++++++++++++++++++++ dlls/winscard/winscard.spec | 2 +- 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/dlls/winscard/tests/winscard.c b/dlls/winscard/tests/winscard.c index b67c55404ed..9f8a24a5537 100644 --- a/dlls/winscard/tests/winscard.c +++ b/dlls/winscard/tests/winscard.c @@ -475,6 +475,139 @@ static void test_SCardGetCardTypeProviderName(void) ok(ret == ERROR_SUCCESS, "failed to release context: error %ld\n", ret); } +static void test_SCardListCardsW(void) +{ + LONG ret; + const WCHAR *expected; + BYTE atr_full[36]; + DWORD match_len = 0; + WCHAR *matching_cards; + + /* test basic error conditions */ + ret = SCardListCardsW(0, NULL, NULL, 0, NULL, NULL); + ok(ret == SCARD_E_INVALID_PARAMETER, "should fail when inout_cards_len is null\n"); + + match_len = 2; + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(0, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == SCARD_E_INSUFFICIENT_BUFFER, "should fail when the output buffer is too small\n"); + free(matching_cards); + + /* get length and allocate */ + ret = SCardListCardsW(0, NULL, NULL, 0, NULL, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + ok(match_len > 0, "match size should not be empty"); + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(0, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + free(matching_cards); + + /* test with pre-allocated */ + match_len = 32; + matching_cards = malloc(match_len * sizeof(WCHAR)); + for (int i = 0; i < 32; i++) { matching_cards[i] = 0xcafe; } + ret = SCardListCardsW(0, NULL, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + for (int i = match_len; i < 32; i++) + { + ok(matching_cards[i] == 0xcafe, "memory corruption: matching_cards[%d] = %u\n", i, matching_cards[i]); + } + free(matching_cards); + + /* test with auto alloc */ + match_len = SCARD_AUTOALLOCATE; + matching_cards = NULL; + ret = SCardListCardsW(0, NULL, NULL, 0, (LPWSTR)&matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list all cards: error %#lx\n", ret); + expected = L"PKI-test-1\0PKI-test-2\0\0"; + ok(match_len == 23, "invalid length: expected %ld, got %ld\n", 23L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + ret = SCardFreeMemory(0, matching_cards); + ok(ret == ERROR_SUCCESS, "failed to free auto-allocated memory\n"); + + /* test with ATRs that match exactly card 1 */ + match_len = 32; + matching_cards = calloc(match_len, sizeof(WCHAR)); + ret = SCardListCardsW(0, card_atr_1, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 1: error %#lx\n", ret); + expected = L"PKI-test-1\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + /* test with ATRs that match exactly card 2 */ + match_len = 32; + ret = SCardListCardsW(0, card_atr_2, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + /* test with zero-padded ATR that match */ + for (int i = 0; i < sizeof(card_atr_2); i++) { atr_full[i] = card_atr_2[i]; } + for (int i = sizeof(card_atr_2); i < sizeof(atr_full); i++) { atr_full[i] = 0; } + match_len = 32; + ret = SCardListCardsW(0, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + /* test with an ATR that matches thanks to the mask */ + for (int i = 0; i < sizeof(card_atr_2) - 1; i++) { atr_full[i] = card_atr_2[i]; } + atr_full[sizeof(card_atr_2) - 1] = 0xff; + for (int i = sizeof(card_atr_2); i < sizeof(atr_full); i++) { atr_full[i] = 0; } + match_len = 32; + ret = SCardListCardsW(0, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list card 2: error %#lx\n", ret); + expected = L"PKI-test-2\0"; + ok(match_len == 12, "invalid length: expected %ld, got %ld\n", 12L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + /* test with an ATR that doesn't match */ + atr_full[0] = 0x3B; + atr_full[1] = 0x02; + atr_full[2] = 0x14; + atr_full[3] = 0x50; + for (int i = 4; i < sizeof(atr_full); i++) { atr_full[i] = 0; } + match_len = 32; + ret = SCardListCardsW(0, atr_full, NULL, 0, matching_cards, &match_len); + ok(ret == ERROR_SUCCESS, "failed to list no card: error %#lx\n", ret); + expected = L""; + ok(match_len == 1, "invalid length: expected %ld, got %ld\n", 1L, match_len); + ok(matching_cards != NULL, "the buffer should have been allocated, is NULL\n"); + ok(memcmp(matching_cards, expected, match_len) == 0, + "bad output of SCardListCardsW: %s\n", + debugstr_wn(matching_cards, match_len)); + + free(matching_cards); +} + static void test_smartcard_db(void) { LONG ret; @@ -487,6 +620,7 @@ static void test_smartcard_db(void) /* run the tests */ test_SCardGetCardTypeProviderName(); + test_SCardListCardsW(); /* delete the test keys */ ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, subkey_smartcards_database, 0, KEY_READ | KEY_WRITE, &key); diff --git a/dlls/winscard/winscard.c b/dlls/winscard/winscard.c index 20a34529b08..cd2dc230191 100644 --- a/dlls/winscard/winscard.c +++ b/dlls/winscard/winscard.c @@ -1194,6 +1194,120 @@ static BOOL card_atr_matches(const HKEY db_key, const WCHAR *card_subkey_name, c return matches; } +/** Look up known cards in the smart card database. */ +LONG WINAPI SCardListCardsW(SCARDCONTEXT context, const BYTE *atr, const GUID *interfaces, DWORD interface_count, WCHAR *out_cards, DWORD *inout_cards_len) +{ + struct handle *handle = (struct handle *)context; + HKEY db_key; + LSTATUS ret; + BYTE **new_output; + DWORD res_len_wchars = 0; + + DWORD i_subkey = 0; + WCHAR card_subkey_name[256]; + DWORD card_subkey_name_len_wchars = 256; + DWORD new_len = 0; + int atr_len = 0; + + TRACE("%Ix, %s, %p, %lu, %p, %p\n", context, debug_atr(atr), interfaces, interface_count, out_cards, inout_cards_len); + + if (!inout_cards_len) return SCARD_E_INVALID_PARAMETER; + + if (handle != NULL) + { + if (handle->magic != CONTEXT_MAGIC) + { + return ERROR_INVALID_HANDLE; + } + FIXME("card scopes not implemented\n"); + /* continue anyway */ + } + + if (interfaces != NULL) + { + FIXME("card services identifiers not implemented\n"); + /* continue anyway, it's usually better to try to return at least one card */ + } + + if (atr != NULL) + { + atr_len = parse_atr_length(atr); + if (atr_len < 0) return SCARD_E_INVALID_ATR; + } + + /* + According to the docs, we have 3 cases for the result: + - out_cards == null => return (in inout_cards_len) the length of the buffer that would have been returned if it existed + - out_cards != null && *inout_cards_len == SCARD_AUTOALLOCATE => allocate a buffer ourselves + - out_cards != null && *inout_cards_len != SCARD_AUTOALLOCATE => fill the provided buffer (out_cards) + */ + + /* handle the auto-allocate flag in two passes */ + if (out_cards != NULL && *inout_cards_len == SCARD_AUTOALLOCATE) + { + /* get the buffer size, including the NUL terminator */ + SCardListCardsW(context, atr, interfaces, interface_count, NULL, &res_len_wchars); + + /* allocate and fill */ + new_output = (BYTE**)out_cards; + *new_output = calloc(res_len_wchars, sizeof(WCHAR)); + if (*new_output == NULL) return ERROR_NOT_ENOUGH_MEMORY; + *inout_cards_len = res_len_wchars; + return SCardListCardsW(context, atr, interfaces, interface_count, (WCHAR*)*new_output, inout_cards_len); + } + + ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, SUBKEY_SMARTCARDS_DATABASE, 0, KEY_READ, &db_key); + + if (ret == ERROR_FILE_NOT_FOUND) { + WARN("the smartcard db does not exist: HKLM\\%S not found\n", SUBKEY_SMARTCARDS_DATABASE); + /* return an empty list of cards */ + goto end; + } + else if (ret != ERROR_SUCCESS) + { + return SCARD_F_INTERNAL_ERROR; + } + + /* look at each subkey and try to find a matching card type */ + while ((ret = RegEnumKeyExW(db_key, i_subkey, card_subkey_name, &card_subkey_name_len_wchars, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) + { + TRACE("found key HKLM\\%S\\%S\n", SUBKEY_SMARTCARDS_DATABASE, card_subkey_name); + + if (atr == NULL || card_atr_matches(db_key, card_subkey_name, atr, atr_len)) + { + /* match found => append to the multi-string (or just increase the length if out_cards is null) */ + card_subkey_name_len_wchars++; /* +1 for the trailing \0, which is not included in the count by RegEnumKeyExW */ + new_len = res_len_wchars + card_subkey_name_len_wchars; + if (new_len < res_len_wchars) + { + /* overflow */ + return ERROR_NOT_ENOUGH_MEMORY; + } + if (out_cards != NULL) + { + if (*inout_cards_len < new_len) return SCARD_E_INSUFFICIENT_BUFFER; + lstrcpynW(&out_cards[res_len_wchars], card_subkey_name, card_subkey_name_len_wchars); + } + res_len_wchars = new_len; + } + + /* prepare for the next call of RegEnumKeyExW */ + i_subkey++; + card_subkey_name_len_wchars = 256; + } + + end: + /* terminate the multi-string */ + if (out_cards != NULL) + { + if (*inout_cards_len < res_len_wchars + 1) return SCARD_E_INSUFFICIENT_BUFFER; + out_cards[res_len_wchars] = '\0'; + } + *inout_cards_len = res_len_wchars + 1; + TRACE("returning %s, length %ld\n", debugstr_wn(out_cards, *inout_cards_len), *inout_cards_len); + return SCARD_S_SUCCESS; +} + BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, void *reserved ) { switch (reason) diff --git a/dlls/winscard/winscard.spec b/dlls/winscard/winscard.spec index f5f9deac283..a50f39d9f04 100644 --- a/dlls/winscard/winscard.spec +++ b/dlls/winscard/winscard.spec @@ -35,7 +35,7 @@ @ stub SCardIntroduceReaderW @ stdcall SCardIsValidContext(long) @ stdcall SCardListCardsA(long ptr ptr long str ptr) -@ stub SCardListCardsW +@ stdcall SCardListCardsW(long ptr ptr long wstr ptr) @ stub SCardListInterfacesA @ stub SCardListInterfacesW @ stdcall SCardListReaderGroupsA(long ptr ptr) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10751