diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000000..a97a4dc403 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-arm64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..8c7107b437 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,28 @@ +{ + "files.associations": { + "client.h": "c", + "rdpdr.h": "c", + "rdpdr-smartcard.h": "c", + "__locale": "c", + "locale": "c", + "__config": "c", + "__hash_table": "c", + "__split_buffer": "c", + "array": "c", + "functional": "c", + "ios": "c", + "iterator": "c", + "ostream": "c", + "sstream": "c", + "string": "c", + "string_view": "c", + "vector": "c", + "unicode.h": "c", + "complex": "c", + "smartcard-pack.h": "c", + "scard.h": "c", + "smartcard-call.h": "c", + "unordered_map": "c", + "remote-smartcard.h": "c" + } +} diff --git a/Dockerfile b/Dockerfile index 8ec9f4b69b..c021ec9eb7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,7 @@ ARG BUILD_ARCHITECTURE # default, this is detected automatically. ARG BUILD_JOBS -# The directory that will house the guacamole-server source during the build +# The directory that will house the guacamole-server source during the build ARG BUILD_DIR=/tmp/guacamole-server # FreeRDP version (default to version 2) @@ -85,7 +85,7 @@ ARG FREERDP_OPTS="\ -DWITH_OPENH264=OFF \ -DWITH_OPENSSL=ON \ -DWITH_OSS=OFF \ - -DWITH_PCSC=OFF \ + -DWITH_PCSC=ON \ -DWITH_PKCS11=OFF \ -DWITH_PULSE=OFF \ -DWITH_SERVER=OFF \ @@ -183,7 +183,8 @@ RUN apk add --no-cache \ sdl2-dev \ sdl2_ttf-dev \ util-linux-dev \ - webkit2gtk-dev + webkit2gtk-dev \ + pcsc-lite-dev # Copy generic, automatic build script COPY ./src/guacd-docker/bin/autobuild.sh ${BUILD_DIR}/src/guacd-docker/bin/ @@ -309,6 +310,47 @@ ARG PREFIX_DIR # Copy build artifacts into this stage COPY --from=guacamole-server ${PREFIX_DIR} ${PREFIX_DIR} +RUN apk add --no-cache \ + git \ + build-base \ + autoconf \ + automake \ + libtool \ + linux-pam-dev \ + libusb-dev \ + wget \ + xz \ + meson \ + ninja \ + flex \ + bison \ + python3 \ + py3-pip \ + eudev-dev \ + polkit \ + polkit-dev \ + pcsc-lite-dev + +# Download vsmartcard emulator and install +RUN apk add --no-cache \ + help2man + +RUN cd /opt && \ + wget https://github.com/frankmorgner/vsmartcard/releases/download/virtualsmartcard-0.10/virtualsmartcard-0.10.tar.gz && \ + tar -xzf virtualsmartcard-0.10.tar.gz && \ + cd virtualsmartcard-0.10 && \ + ./configure --sysconfdir=/etc && \ + make && make install && \ + chmod 777 /opt/virtualsmartcard-0.10/src/vpicc && \ + mkdir /run/pcscd && chmod 777 /run/pcscd + +# Clone and build pcsc-lite from source +RUN git clone https://github.com/LudovicRousseau/PCSC.git /opt/pcsc-lite && \ + cd /opt/pcsc-lite && \ + meson setup builddir -Dlibsystemd=false && \ + meson compile -C builddir && \ + meson install -C builddir + # Bring runtime environment up to date and install runtime dependencies RUN apk add --no-cache \ ca-certificates \ diff --git a/Notes.md b/Notes.md new file mode 100644 index 0000000000..9d2e1b32fa --- /dev/null +++ b/Notes.md @@ -0,0 +1,20 @@ +## On the Desktop +1. Open services and enable the "Smart Card" service. Ensure it is running and set to run on startup +2. Do the same for "Smart Card Emulation Service" +3. +``` +net start scardsvr +``` +4. Run the Local Group Policy Editor using the gpedit.msc command on the workstation with administrator privileges. +5. Go to Computer configuration → Administrative Templates → Windows Components. +6. Select Remote Desktop Services → Remote Desktop Session Host. +7. Go to Device and Resource Redirection. +8. Select Do not allow supported Plug and Play device redirection. +9. Select Disabled and click ОК. +10. Select Do not allow smart card device redirection. +11. Select Disabled and click ОК. +12. Open the command line as an administrator and run the command: +``` +gpupdate /force +``` +13. Restart the device. diff --git a/configure.ac b/configure.ac index db07e0ee9b..d65a617d72 100644 --- a/configure.ac +++ b/configure.ac @@ -79,7 +79,7 @@ AC_CHECK_LIB([pthread], [pthread_create], [PTHREAD_LIBS=-lpthread # Check for pthread_setattr_default_np AC_CHECK_DECLS([pthread_setattr_default_np], , , [[#include ]]) - + # librt AC_CHECK_FUNC([timer_create], [AC_MSG_RESULT([timer_create was found without librt.])], [AC_CHECK_LIB([rt], [timer_create], @@ -692,7 +692,7 @@ then fi # -# libVNCserver support for the requestedResize member, which enables the +# libVNCserver support for the requestedResize member, which enables the # client to pause frame buffer updates during a resize operation. If support # for this is missing, Guacamole may still attempt to send the resize requests # to the remote display, but there may be odd display behavior just before, @@ -750,6 +750,10 @@ then [FREERDP_PLUGIN_DIR="`$PKG_CONFIG --variable=libdir freerdp2`/freerdp2"])], [freerdp_version= have_freerdp=no]) + + PKG_CHECK_MODULES([PCSC], [libpcsclite], + [CPPFLAGS="${PCSC_CFLAGS} -Werror $CPPFLAGS"], + [AC_MSG_ERROR([libpcsclite is required for smartcard support])]) fi if test "x$with_rdp" != "xno" -a "x${have_freerdp}" = "xno" diff --git a/src/guacd-docker/bin/entrypoint.sh b/src/guacd-docker/bin/entrypoint.sh index e7da4a341f..cca211fe43 100755 --- a/src/guacd-docker/bin/entrypoint.sh +++ b/src/guacd-docker/bin/entrypoint.sh @@ -5,6 +5,12 @@ if [ -n "$GUACD_LOG_LEVEL" ]; then echo "WARNING: The GUACD_LOG_LEVEL environment variable has been deprecated in favor of the LOG_LEVEL environment variable. Please migrate your configuration when possible." >&2 fi +# Start PC/SC daemon +pcscd --disable-polkit & + +# Start virtual card emulator (background) +/opt/virtualsmartcard-0.10/src/vpicc/vicc --reader 0 & + # Listen on 0.0.0.0:4822, logging messages at the info level. Allow log level # to be overridden with LOG_LEVEL, and other behavior to be overridden with # additional command-line options passed to Docker. diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index dc9638e305..2ca5cd04d6 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -54,7 +54,13 @@ libguac_client_rdp_la_SOURCES = \ channels/rdpdr/rdpdr-fs.c \ channels/rdpdr/rdpdr-messages.c \ channels/rdpdr/rdpdr-printer.c \ + channels/rdpdr/rdpdr-smartcard.c \ + channels/rdpdr/smartcard-call.c \ + channels/rdpdr/smartcard-pack.c \ + channels/rdpdr/smartcard-operations.c \ + channels/rdpdr/remote-smartcard.c \ channels/rdpdr/rdpdr.c \ + channels/rdpdr/msz-unicode.c \ channels/rdpei.c \ channels/rdpgfx.c \ channels/rdpsnd/rdpsnd-messages.c \ @@ -100,7 +106,14 @@ noinst_HEADERS = \ channels/rdpdr/rdpdr-fs.h \ channels/rdpdr/rdpdr-messages.h \ channels/rdpdr/rdpdr-printer.h \ + channels/rdpdr/rdpdr-smartcard.h \ + channels/rdpdr/scard.h \ + channels/rdpdr/remote-smartcard.h \ + channels/rdpdr/smartcard-call.h \ + channels/rdpdr/smartcard-pack.h \ + channels/rdpdr/smartcard-operations.h \ channels/rdpdr/rdpdr.h \ + channels/rdpdr/msz-unicode.h \ channels/rdpei.h \ channels/rdpgfx.h \ channels/rdpsnd/rdpsnd-messages.h \ @@ -135,7 +148,8 @@ libguac_client_rdp_la_CFLAGS = \ @COMMON_INCLUDE@ \ @COMMON_SSH_INCLUDE@ \ @LIBGUAC_INCLUDE@ \ - @RDP_CFLAGS@ + @RDP_CFLAGS@ \ + @PCSC_CFLAGS@ libguac_client_rdp_la_LDFLAGS = \ -version-info 0:0:0 \ @@ -145,7 +159,8 @@ libguac_client_rdp_la_LDFLAGS = \ libguac_client_rdp_la_LIBADD = \ @COMMON_LTLIB@ \ - @LIBGUAC_LTLIB@ + @LIBGUAC_LTLIB@ \ + @PCSC_LIBS@ # # Plugins for FreeRDP diff --git a/src/protocols/rdp/channels/rdpdr/msz-unicode.c b/src/protocols/rdp/channels/rdpdr/msz-unicode.c new file mode 100644 index 0000000000..24414db93e --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/msz-unicode.c @@ -0,0 +1,204 @@ +#include "channels/rdpdr/msz-unicode.h" + +#include +#include +#include + +SSIZE_T ConvertMszWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len) +{ + if (wlen == 0) + return 0; + + WINPR_ASSERT(wstr); + + if ((len > INT32_MAX) || (wlen > INT32_MAX)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return -1; + } + + const int iwlen = (int)len; + const int rc = WideCharToMultiByte(CP_UTF8, 0, wstr, (int)wlen, str, iwlen, NULL, NULL); + if ((rc <= 0) || ((len > 0) && (rc > iwlen))) + return -1; + + return rc; +} + +char* ConvertMszWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength) +{ + char* tmp = NULL; + const SSIZE_T rc = ConvertMszWCharNToUtf8(wstr, wlen, NULL, 0); + + if (pUtfCharLength) + *pUtfCharLength = 0; + if (rc < 0) + return NULL; + tmp = calloc((size_t)rc + 1ull, sizeof(char)); + if (!tmp) + return NULL; + const SSIZE_T rc2 = ConvertMszWCharNToUtf8(wstr, wlen, tmp, (size_t)rc + 1ull); + if (rc2 < 0) + { + free(tmp); + return NULL; + } + WINPR_ASSERT(rc == rc2); + if (pUtfCharLength) + *pUtfCharLength = (size_t)rc2; + return tmp; +} + + +SSIZE_T ConvertMszUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen) +{ + if (len == 0) + return 0; + + WINPR_ASSERT(str); + + if ((len > INT32_MAX) || (wlen > INT32_MAX)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return -1; + } + + const int iwlen = (int)wlen; + const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, iwlen); + if ((rc <= 0) || ((wlen > 0) && (rc > iwlen))) + return -1; + + return rc; +} + +WCHAR* ConvertMszUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize) +{ + WCHAR* tmp = NULL; + const SSIZE_T rc = ConvertMszUtf8NToWChar(str, len, NULL, 0); + if (pSize) + *pSize = 0; + if (rc < 0) + return NULL; + tmp = calloc((size_t)rc + 1ull, sizeof(WCHAR)); + if (!tmp) + return NULL; + const SSIZE_T rc2 = ConvertMszUtf8NToWChar(str, len, tmp, (size_t)rc + 1ull); + if (rc2 < 0) + { + free(tmp); + return NULL; + } + WINPR_ASSERT(rc == rc2); + if (pSize) + *pSize = (size_t)rc2; + return tmp; +} + +SSIZE_T ConvertUtf8ToWChar(const char* str, WCHAR* wstr, size_t wlen) +{ + if (!str) + { + if (wstr && wlen) + wstr[0] = 0; + return 0; + } + + const size_t len = strlen(str); + return ConvertUtf8NToWChar(str, len + 1, wstr, wlen); +} + +const WCHAR* InitializeConstWCharFromUtf8(const char* str, WCHAR* buffer, size_t len) +{ + WINPR_ASSERT(str); + WINPR_ASSERT(buffer || (len == 0)); + (void)ConvertUtf8ToWChar(str, buffer, len); + return buffer; +} + +SSIZE_T ConvertUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen) +{ + size_t ilen = strnlen(str, len); + BOOL isNullTerminated = FALSE; + if (len == 0) + return 0; + + WINPR_ASSERT(str); + + if ((len > INT32_MAX) || (wlen > INT32_MAX)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return -1; + } + if (ilen < len) + { + isNullTerminated = TRUE; + ilen++; + } + + const int iwlen = (int)wlen; + const int rc = MultiByteToWideChar(CP_UTF8, 0, str, (int)ilen, wstr, iwlen); + if ((rc <= 0) || ((wlen > 0) && (rc > iwlen))) + return -1; + if (!isNullTerminated) + { + if (wstr && (rc < iwlen)) + wstr[rc] = '\0'; + return rc; + } + else if (rc == iwlen) + { + if (wstr && (wstr[rc - 1] != '\0')) + return rc; + } + return rc - 1; +} + +SSIZE_T ConvertWCharToUtf8(const WCHAR* wstr, char* str, size_t len) +{ + if (!wstr) + { + if (str && len) + str[0] = 0; + return 0; + } + + const size_t wlen = _wcslen(wstr); + return ConvertWCharNToUtf8(wstr, wlen + 1, str, len); +} + +SSIZE_T ConvertWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len) +{ + BOOL isNullTerminated = FALSE; + if (wlen == 0) + return 0; + + WINPR_ASSERT(wstr); + size_t iwlen = _wcsnlen(wstr, wlen); + + if ((len > INT32_MAX) || (wlen > INT32_MAX)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return -1; + } + + if (iwlen < wlen) + { + isNullTerminated = TRUE; + iwlen++; + } + const int rc = WideCharToMultiByte(CP_UTF8, 0, wstr, (int)iwlen, str, (int)len, NULL, NULL); + if ((rc <= 0) || ((len > 0) && ((size_t)rc > len))) + return -1; + else if (!isNullTerminated) + { + if (str && ((size_t)rc < len)) + str[rc] = '\0'; + return rc; + } + else if ((size_t)rc == len) + { + if (str && (str[rc - 1] != '\0')) + return rc; + } + return rc - 1; +} diff --git a/src/protocols/rdp/channels/rdpdr/msz-unicode.h b/src/protocols/rdp/channels/rdpdr/msz-unicode.h new file mode 100644 index 0000000000..31afcbff12 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/msz-unicode.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +SSIZE_T ConvertMszWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len); +char* ConvertMszWCharNToUtf8Alloc(const WCHAR* wstr, size_t wlen, size_t* pUtfCharLength); +SSIZE_T ConvertMszUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen); +WCHAR* ConvertMszUtf8NToWCharAlloc(const char* str, size_t len, size_t* pSize); + +const WCHAR* InitializeConstWCharFromUtf8(const char* str, WCHAR* buffer, size_t len); +SSIZE_T ConvertUtf8ToWChar(const char* str, WCHAR* wstr, size_t wlen); +SSIZE_T ConvertUtf8NToWChar(const char* str, size_t len, WCHAR* wstr, size_t wlen); + +SSIZE_T ConvertWCharToUtf8(const WCHAR* wstr, char* str, size_t len); +SSIZE_T ConvertWCharNToUtf8(const WCHAR* wstr, size_t wlen, char* str, size_t len); diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c index 5fee4658bc..865632c4cd 100644 --- a/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-messages.c @@ -124,7 +124,7 @@ static void guac_rdpdr_send_client_capability(guac_rdp_common_svc* svc) { Stream_Write_UINT16(output_stream, PAKID_CORE_CLIENT_CAPABILITY); /* Capability count + padding */ - Stream_Write_UINT16(output_stream, 3); + Stream_Write_UINT16(output_stream, 4); Stream_Write_UINT16(output_stream, 0); /* Padding */ /* General capability header */ @@ -157,6 +157,11 @@ static void guac_rdpdr_send_client_capability(guac_rdp_common_svc* svc) { Stream_Write_UINT16(output_stream, 8); Stream_Write_UINT32(output_stream, DRIVE_CAPABILITY_VERSION_02); + /* Smartcard support header */ + Stream_Write_UINT16(output_stream, CAP_SMARTCARD_TYPE); // Capability type + Stream_Write_UINT16(output_stream, 8); // Length of capability + Stream_Write_UINT32(output_stream, SMARTCARD_CAPABILITY_VERSION_01); // Version + guac_rdp_common_svc_write(svc, output_stream); guac_client_log(svc->client, GUAC_LOG_DEBUG, "Capabilities sent."); @@ -192,14 +197,14 @@ static void guac_rdpdr_send_client_device_list_announce_request(guac_rdp_common_ /* Get the stream for each of the devices. */ Stream_Write_UINT32(output_stream, rdpdr->devices_registered); for (int i=0; idevices_registered; i++) { - + Stream_Write(output_stream, Stream_Buffer(rdpdr->devices[i].device_announce), rdpdr->devices[i].device_announce_len); guac_client_log(svc->client, GUAC_LOG_DEBUG, "Registered device %i (%s)", rdpdr->devices[i].device_id, rdpdr->devices[i].device_name); - + } guac_rdp_common_svc_write(svc, output_stream); @@ -219,7 +224,7 @@ void guac_rdpdr_process_server_announce(guac_rdp_common_svc* svc, "Device redirection may not work as expected."); return; } - + Stream_Read_UINT16(input_stream, major); Stream_Read_UINT16(input_stream, minor); Stream_Read_UINT32(input_stream, client_id); @@ -258,7 +263,7 @@ void guac_rdpdr_process_device_reply(guac_rdp_common_svc* svc, "Device redirection may not work as expected."); return; } - + Stream_Read_UINT32(input_stream, device_id); Stream_Read_UINT32(input_stream, ntstatus); @@ -301,7 +306,7 @@ void guac_rdpdr_process_device_iorequest(guac_rdp_common_svc* svc, "redirection may not work as expected."); return; } - + /* Read header */ Stream_Read_UINT32(input_stream, iorequest.device_id); Stream_Read_UINT32(input_stream, iorequest.file_id); @@ -309,7 +314,7 @@ void guac_rdpdr_process_device_iorequest(guac_rdp_common_svc* svc, Stream_Read_UINT32(input_stream, iorequest.major_func); Stream_Read_UINT32(input_stream, iorequest.minor_func); - /* If printer, run printer handlers */ + /* If printer or smartcard, run handlers */ if (iorequest.device_id >= 0 && iorequest.device_id < rdpdr->devices_registered) { /* Call handler on device */ @@ -337,7 +342,7 @@ void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc, "Device redirection may not work as expected."); return; } - + /* Read header */ Stream_Read_UINT16(input_stream, count); Stream_Seek(input_stream, 2); @@ -356,7 +361,7 @@ void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc, "expected."); break; } - + Stream_Read_UINT16(input_stream, type); Stream_Read_UINT16(input_stream, length); @@ -368,7 +373,7 @@ void guac_rdpdr_process_server_capability(guac_rdp_common_svc* svc, "expected."); break; } - + /* Ignore all for now */ guac_client_log(svc->client, GUAC_LOG_DEBUG, "Ignoring server capability set type=0x%04x, length=%i", type, length); Stream_Seek(input_stream, length - 4); diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.c b/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.c new file mode 100644 index 0000000000..562fb9731c --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.c @@ -0,0 +1,299 @@ +// rdpr-smartcard.c + +#include "channels/rdpdr/rdpdr-smartcard.h" +#include "channels/rdpdr/rdpdr.h" +#include "channels/rdpdr/remote-smartcard.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/smartcard-call.h" +#include "channels/rdpdr/smartcard-pack.h" +#include "channels/rdpdr/smartcard-operations.h" +#include "rdp.h" +#include "unicode.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +BOOL rdpdr_write_iocompletion_header(wStream* out, UINT32 DeviceId, UINT32 CompletionId, + NTSTATUS ioStatus) +{ + WINPR_ASSERT(out); + Stream_SetPosition(out, 0); + if (!Stream_EnsureRemainingCapacity(out, 16)) + return FALSE; + Stream_Write_UINT16(out, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(out, PAKID_CORE_DEVICE_IOCOMPLETION); /* PacketId (2 bytes) */ + Stream_Write_UINT32(out, DeviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(out, CompletionId); /* CompletionId (4 bytes) */ + Stream_Write_INT32(out, ioStatus); /* IoStatus (4 bytes) */ + + return TRUE; +} + +/* +Code mostly ported from pf_channel_smartcard.c in FreeRDP +*/ +void guac_rdpdr_device_smartcard_iorequest_handler( + guac_rdp_common_svc* svc, + guac_rdpdr_device* device, + guac_rdpdr_iorequest* iorequest, + wStream* input_stream) { + + SMARTCARD_CONTEXT* context = (SMARTCARD_CONTEXT*)device->data; + WINPR_ASSERT(context); + + UINT32 FileId = 0; + UINT32 CompletionId = 0; + NTSTATUS ioStatus = 0; + + FileId = iorequest->file_id; + CompletionId = iorequest->completion_id; + const uint32_t MajorFunction = iorequest->major_func; + + if (MajorFunction != IRP_MJ_DEVICE_CONTROL) + { + guac_client_log(svc->client, GUAC_LOG_WARNING, "Invalid major device recieved, expected %s, got %s.", + rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction)); + return; + } + + wStream* output_stream = Stream_New(NULL, 16); + + // size_t start_pos = Stream_GetPosition(output_stream); + + if (!rdpdr_write_iocompletion_header(output_stream, device->device_id, CompletionId, 0)) { + guac_client_log(svc->client, GUAC_LOG_ERROR, "guac_rdpdr_device_smartcard_iorequest_handler: failed to write iocompletion header."); + } + + if (Stream_GetRemainingLength(input_stream) < 12) { + guac_client_log(svc->client, GUAC_LOG_ERROR, + "IOCTL request too short (need at least 12 bytes)."); + guac_rdpdr_write_io_completion(output_stream, device, CompletionId, STATUS_INVALID_PARAMETER, 0); + guac_rdp_common_svc_write(svc, output_stream); + return; + } + + guac_rdp_scard_operation op; + op.client = svc->client; + op.out = output_stream; + LONG status = guac_rdpdr_smartcard_irp_device_control_decode(input_stream, CompletionId, FileId, &op); + + if (status != 0) { + guac_client_log(svc->client, GUAC_LOG_ERROR, "IOCTL decode status != 0."); + guac_rdpdr_write_io_completion(output_stream, device, CompletionId, STATUS_INVALID_PARAMETER, 0); + guac_rdp_common_svc_write(svc, output_stream); + return; + } + + guac_client_log(svc->client, GUAC_LOG_DEBUG, "iorequest_handler: Smartcard IOCTL request: 0x%08X, %s", op.ioControlCode, scard_get_ioctl_string(op.ioControlCode, true)); + + BOOL asyncIrp = TRUE; + + switch (op.ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + case SCARD_IOCTL_RELEASECONTEXT: + case SCARD_IOCTL_ISVALIDCONTEXT: + case SCARD_IOCTL_CANCEL: + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + case SCARD_IOCTL_RELEASETARTEDEVENT: + asyncIrp = FALSE; + break; + + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + case SCARD_IOCTL_FORGETREADERGROUPA: + case SCARD_IOCTL_FORGETREADERGROUPW: + case SCARD_IOCTL_INTRODUCEREADERA: + case SCARD_IOCTL_INTRODUCEREADERW: + case SCARD_IOCTL_FORGETREADERA: + case SCARD_IOCTL_FORGETREADERW: + case SCARD_IOCTL_ADDREADERTOGROUPA: + case SCARD_IOCTL_ADDREADERTOGROUPW: + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_READCACHEA: + case SCARD_IOCTL_READCACHEW: + case SCARD_IOCTL_WRITECACHEA: + case SCARD_IOCTL_WRITECACHEW: + case SCARD_IOCTL_GETREADERICON: + case SCARD_IOCTL_GETDEVICETYPEID: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + case SCARD_IOCTL_GETTRANSMITCOUNT: + asyncIrp = TRUE; + break; + default: + break; + } + + if (!asyncIrp) + { + status = guac_rdpdr_smartcard_irp_device_control_call(svc, context->call_context, iorequest, &op, &ioStatus); + + smartcard_operation_free(&op, FALSE); + + if (status) + { + guac_client_log(svc->client, GUAC_LOG_ERROR, "smartcard_irp_device_control_call failed with error %" PRId32 "!", + status); + return; + } + } else { + guac_client_log(svc->client, GUAC_LOG_ERROR, "Async IOCTL not supported yet; handling syncronously."); + status = guac_rdpdr_smartcard_irp_device_control_call(svc, context->call_context, iorequest, &op, &ioStatus); + + smartcard_operation_free(&op, FALSE); + + if (status) + { + guac_client_log(svc->client, GUAC_LOG_ERROR, "smartcard_irp_device_control_call failed with error %" PRId32 "!", + status); + return; + } + } + + // guac_client_log(svc->client, GUAC_LOG_DEBUG, "Writing stream to service..."); + guac_rdp_common_svc_write(svc, output_stream); + guac_client_log(svc->client, GUAC_LOG_DEBUG, "\tCompleted IOCTL request."); + + return; +} + +void guac_rdpdr_device_smartcard_free_handler(guac_rdp_common_svc* svc, + guac_rdpdr_device* device) { + guac_client_log(svc->client, GUAC_LOG_WARNING, "Freeing smartcard..."); + + if (!device || !device->data) + return; + + SMARTCARD_CONTEXT* smartcard_ctx = (SMARTCARD_CONTEXT*)device->data; + + if (smartcard_ctx->call_context) { + if (smartcard_ctx->call_context->smartcard) { + RemoteSmartcard_free(smartcard_ctx->call_context->smartcard); + free(smartcard_ctx->call_context->smartcard); + } + + if (smartcard_ctx->call_context->names) { + LinkedList_Free(smartcard_ctx->call_context->names); + } + + free(smartcard_ctx->call_context); + } + + free(smartcard_ctx); + device->data = NULL; + + Stream_Free(device->device_announce, 1); + + guac_client_log(svc->client, GUAC_LOG_WARNING, "Smartcard freed."); +} + +/* See the spec here https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpefs/32e34332-774b-4ead-8c9d-5d64720d6bf9 */ +void guac_rdpdr_register_smartcard(guac_rdp_common_svc* svc, char* smartcard_name) { + + guac_rdpdr* rdpdr = (guac_rdpdr*) svc->data; + int id = rdpdr->devices_registered++; + + /* Get new device */ + guac_rdpdr_device* device = &(rdpdr->devices[id]); + + /* Init device */ + device->device_id = id; + device->device_name = smartcard_name; + int device_name_len = guac_utf8_strlen(device->device_name); + device->device_type = RDPDR_DTYP_SMARTCARD; + device->dos_name = "SCARD\0\0\0"; + + int scard_name_len = (device_name_len + 1) * 2; + /* DeviceData = 12 + driver_name + device_name */ + int device_data_len = 12 + GUAC_SMARTCARD_DRIVER_LENGTH + scard_name_len; + + /* Set up device announce stream */ + device->device_announce_len = 20 + device_data_len; + device->device_announce = Stream_New(NULL, device->device_announce_len); + + Stream_Write_UINT32(device->device_announce, RDPDR_DTYP_SMARTCARD); /* deviceType */ + Stream_Write_UINT32( + device->device_announce, device->device_id); /* deviceID */ + Stream_Write(device->device_announce, "SCARD\0\0\0", 8); + Stream_Write_UINT32(device->device_announce, 6); + Stream_Write(device->device_announce, "SCARD\0", 6); + + /* Set handlers */ + device->iorequest_handler = guac_rdpdr_device_smartcard_iorequest_handler; + device->free_handler = guac_rdpdr_device_smartcard_free_handler; + + if (!assign_smartcard_context_to_device(device, svc->client)) { + guac_client_log(svc->client, GUAC_LOG_ERROR, "Failed to create smartcard context."); + } +} + +// Function to create and initialize a SMARTCARD_CONTEXT and assign it to the device +int assign_smartcard_context_to_device(guac_rdpdr_device* device, guac_client* client) { + if (!device) + return -1; + + // Allocate SMARTCARD_CONTEXT + SMARTCARD_CONTEXT* smartcard_ctx = (SMARTCARD_CONTEXT*)calloc(1, sizeof(SMARTCARD_CONTEXT)); + if (!smartcard_ctx) + return -1; + + // Allocate scard_call_context + scard_call_context* call_context = (scard_call_context*)calloc(1, sizeof(scard_call_context)); + if (!call_context) { + free(smartcard_ctx); + return -1; + } + + call_context->names = LinkedList_New(); + + // Allocate RemoteSmartcard + RemoteSmartcard* remote_scard = (RemoteSmartcard*)calloc(1, sizeof(RemoteSmartcard)); + if (!remote_scard) { + free(call_context); + free(smartcard_ctx); + return -1; + } + + // Store allocations + call_context->smartcard = remote_scard; + smartcard_ctx->call_context = call_context; + device->data = (void*)smartcard_ctx; + + remote_scard->log_client = client; + + return 0; +} diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.h b/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.h new file mode 100644 index 0000000000..0156621436 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/rdpdr-smartcard.h @@ -0,0 +1,120 @@ +// rdrp-smartcard.h + +#ifndef GUAC_RDP_CHANNELS_RDPDR_SMARTCARD_H +#define GUAC_RDP_CHANNELS_RDPDR_SMARTCARD_H + +#include "channels/common-svc.h" +#include "channels/rdpdr/rdpdr.h" +#include "channels/rdpdr/scard.h" + +#include + +#include + +#define RDP_SCARD_CTL_CODE(code) \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, (code), METHOD_BUFFERED, FILE_ANY_ACCESS) + +typedef struct guac_rdp_scard_operation { + guac_client* client; + UINT32 ioControlCode; + UINT32 outputBufferLength; + wStream* out; + + union + { + Handles_Call handles; + Long_Call lng; + Context_Call context; + ContextAndStringA_Call contextAndStringA; + ContextAndStringW_Call contextAndStringW; + ContextAndTwoStringA_Call contextAndTwoStringA; + ContextAndTwoStringW_Call contextAndTwoStringW; + EstablishContext_Call establishContext; + ListReaderGroups_Call listReaderGroups; + ListReaders_Call listReaders; + GetStatusChangeA_Call getStatusChangeA; + LocateCardsA_Call locateCardsA; + LocateCardsW_Call locateCardsW; + LocateCards_ATRMask locateCardsATRMask; + LocateCardsByATRA_Call locateCardsByATRA; + LocateCardsByATRW_Call locateCardsByATRW; + GetStatusChangeW_Call getStatusChangeW; + GetReaderIcon_Call getReaderIcon; + GetDeviceTypeId_Call getDeviceTypeId; + Connect_Common_Call connect; + ConnectA_Call connectA; + ConnectW_Call connectW; + Reconnect_Call reconnect; + HCardAndDisposition_Call hCardAndDisposition; + State_Call state; + Status_Call status; + SCardIO_Request scardIO; + Transmit_Call transmit; + GetTransmitCount_Call getTransmitCount; + Control_Call control; + GetAttrib_Call getAttrib; + SetAttrib_Call setAttrib; + ReadCache_Common readCache; + ReadCacheA_Call readCacheA; + ReadCacheW_Call readCacheW; + WriteCache_Common writeCache; + WriteCacheA_Call writeCacheA; + WriteCacheW_Call writeCacheW; + } call; +} guac_rdp_scard_operation; + +typedef struct _SMARTCARD_CONTEXT { + struct s_scard_call_context* call_context; +} SMARTCARD_CONTEXT; + +// struct guac_rdpdr_thread_arg { +// guac_rdp_common_svc* svc; +// guac_rdpdr_device* device; +// guac_rdpdr_iorequest* iorequest; +// guac_rdp_scard_operation op; +// wStream* input_stream; +// }; + +/** + * Name of the printer driver that should be used on the server. + */ +#define GUAC_SMARTCARD_DRIVER "S\0m\0a\0r\0t\0 \0C\0a\0r\0d\0\0\0" + +/** + * The size of GUAC_SMARTCARD_DRIVER in bytes. + */ +#define GUAC_SMARTCARD_DRIVER_LENGTH 22 + +/** + * Registers a new smartcard device within the RDPDR plugin. This must be done + * before RDPDR connection finishes. + * + * @param svc + * The static virtual channel instance being used for RDPDR. + * + * @param smartcard_name + * The name of the smartcard that will be registered with the RDP + * connection and passed through to the server. + */ +void guac_rdpdr_register_smartcard(guac_rdp_common_svc* svc, char* smartcard_name); + +/** + * Handler for RDPDR Device I/O Requests which processes received messages on + * behalf of a printer device, in this case a simulated printer which produces + * PDF output. + */ +guac_rdpdr_device_iorequest_handler guac_rdpdr_device_smartcard_iorequest_handler; + +/** + * Free handler which frees all data specific to the simulated printer device. + */ +guac_rdpdr_device_free_handler guac_rdpdr_device_smartcard_free_handler; + +// bool guac_rdpdr_start_irp_thread(guac_rdp_common_svc* svc, guac_rdpdr_device* device, const guac_rdpdr_iorequest* request, wStream* input_stream); + +BOOL rdpdr_write_iocompletion_header(wStream* out, UINT32 DeviceId, UINT32 CompletionId, NTSTATUS ioStatus); + +int assign_smartcard_context_to_device(guac_rdpdr_device* device, guac_client* client); + +#endif + diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr.c b/src/protocols/rdp/channels/rdpdr/rdpdr.c index eaeed77465..dfa13bc6b0 100644 --- a/src/protocols/rdp/channels/rdpdr/rdpdr.c +++ b/src/protocols/rdp/channels/rdpdr/rdpdr.c @@ -21,6 +21,8 @@ #include "channels/rdpdr/rdpdr-fs.h" #include "channels/rdpdr/rdpdr-messages.h" #include "channels/rdpdr/rdpdr-printer.h" +#include "channels/rdpdr/rdpdr-smartcard.h" +#include "channels/rdpdr/scard.h" #include "rdp.h" #include "settings.h" @@ -39,7 +41,7 @@ void guac_rdpdr_process_receive(guac_rdp_common_svc* svc, int component; int packet_id; - /* + /* * Check that device redirection stream contains at least 4 bytes * (UINT16 + UINT16). */ @@ -49,7 +51,7 @@ void guac_rdpdr_process_receive(guac_rdp_common_svc* svc, "bytes. Device redirection may not function as expected."); return; } - + /* Read header */ Stream_Read_UINT16(input_stream, component); Stream_Read_UINT16(input_stream, packet_id); @@ -127,6 +129,14 @@ wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device, wStream* output_stream = Stream_New(NULL, 16+size); + guac_rdpdr_write_io_completion(output_stream, device, completion_id, status, size); + + return output_stream; +} + +void guac_rdpdr_write_io_completion(wStream* output_stream, guac_rdpdr_device* device, + unsigned int completion_id, unsigned int status, int size) { + /* Write header */ Stream_Write_UINT16(output_stream, RDPDR_CTYP_CORE); Stream_Write_UINT16(output_stream, PAKID_CORE_DEVICE_IOCOMPLETION); @@ -135,9 +145,6 @@ wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device, Stream_Write_UINT32(output_stream, device->device_id); Stream_Write_UINT32(output_stream, completion_id); Stream_Write_UINT32(output_stream, status); - - return output_stream; - } void guac_rdpdr_process_connect(guac_rdp_common_svc* svc) { @@ -157,6 +164,9 @@ void guac_rdpdr_process_connect(guac_rdp_common_svc* svc) { if (rdp_client->settings->drive_enabled) guac_rdpdr_register_fs(svc, rdp_client->settings->drive_name); + /* Register smartcard if enabled */ + guac_rdpdr_register_smartcard(svc, "Test Smartcard"); + } void guac_rdpdr_process_terminate(guac_rdp_common_svc* svc) { @@ -189,7 +199,193 @@ void guac_rdpdr_load_plugin(rdpContext* context) { guac_client_log(client, GUAC_LOG_WARNING, "Support for the RDPDR " "channel (device redirection) could not be loaded. Drive " "redirection and printing will not work. Sound MAY not work."); + } else { + guac_client_log(client, GUAC_LOG_DEBUG, "Support for the RDPDR " + "channel (device redirection) loaded succesfully ."); } } +const char* rdpdr_irp_string(uint32_t major) +{ + switch (major) + { + case IRP_MJ_CREATE: + return "IRP_MJ_CREATE"; + case IRP_MJ_CLOSE: + return "IRP_MJ_CLOSE"; + case IRP_MJ_READ: + return "IRP_MJ_READ"; + case IRP_MJ_WRITE: + return "IRP_MJ_WRITE"; + case IRP_MJ_DEVICE_CONTROL: + return "IRP_MJ_DEVICE_CONTROL"; + case IRP_MJ_QUERY_VOLUME_INFORMATION: + return "IRP_MJ_QUERY_VOLUME_INFORMATION"; + case IRP_MJ_SET_VOLUME_INFORMATION: + return "IRP_MJ_SET_VOLUME_INFORMATION"; + case IRP_MJ_QUERY_INFORMATION: + return "IRP_MJ_QUERY_INFORMATION"; + case IRP_MJ_SET_INFORMATION: + return "IRP_MJ_SET_INFORMATION"; + case IRP_MJ_DIRECTORY_CONTROL: + return "IRP_MJ_DIRECTORY_CONTROL"; + case IRP_MJ_LOCK_CONTROL: + return "IRP_MJ_LOCK_CONTROL"; + default: + return "IRP_UNKNOWN"; + } +} + +const char* scard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName) +{ + switch (ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + return funcName ? "SCardEstablishContext" : "SCARD_IOCTL_ESTABLISHCONTEXT"; + + case SCARD_IOCTL_RELEASECONTEXT: + return funcName ? "SCardReleaseContext" : "SCARD_IOCTL_RELEASECONTEXT"; + + case SCARD_IOCTL_ISVALIDCONTEXT: + return funcName ? "SCardIsValidContext" : "SCARD_IOCTL_ISVALIDCONTEXT"; + + case SCARD_IOCTL_LISTREADERGROUPSA: + return funcName ? "SCardListReaderGroupsA" : "SCARD_IOCTL_LISTREADERGROUPSA"; + + case SCARD_IOCTL_LISTREADERGROUPSW: + return funcName ? "SCardListReaderGroupsW" : "SCARD_IOCTL_LISTREADERGROUPSW"; + + case SCARD_IOCTL_LISTREADERSA: + return funcName ? "SCardListReadersA" : "SCARD_IOCTL_LISTREADERSA"; + + case SCARD_IOCTL_LISTREADERSW: + return funcName ? "SCardListReadersW" : "SCARD_IOCTL_LISTREADERSW"; + + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + return funcName ? "SCardIntroduceReaderGroupA" : "SCARD_IOCTL_INTRODUCEREADERGROUPA"; + + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + return funcName ? "SCardIntroduceReaderGroupW" : "SCARD_IOCTL_INTRODUCEREADERGROUPW"; + + case SCARD_IOCTL_FORGETREADERGROUPA: + return funcName ? "SCardForgetReaderGroupA" : "SCARD_IOCTL_FORGETREADERGROUPA"; + + case SCARD_IOCTL_FORGETREADERGROUPW: + return funcName ? "SCardForgetReaderGroupW" : "SCARD_IOCTL_FORGETREADERGROUPW"; + + case SCARD_IOCTL_INTRODUCEREADERA: + return funcName ? "SCardIntroduceReaderA" : "SCARD_IOCTL_INTRODUCEREADERA"; + + case SCARD_IOCTL_INTRODUCEREADERW: + return funcName ? "SCardIntroduceReaderW" : "SCARD_IOCTL_INTRODUCEREADERW"; + + case SCARD_IOCTL_FORGETREADERA: + return funcName ? "SCardForgetReaderA" : "SCARD_IOCTL_FORGETREADERA"; + + case SCARD_IOCTL_FORGETREADERW: + return funcName ? "SCardForgetReaderW" : "SCARD_IOCTL_FORGETREADERW"; + + case SCARD_IOCTL_ADDREADERTOGROUPA: + return funcName ? "SCardAddReaderToGroupA" : "SCARD_IOCTL_ADDREADERTOGROUPA"; + + case SCARD_IOCTL_ADDREADERTOGROUPW: + return funcName ? "SCardAddReaderToGroupW" : "SCARD_IOCTL_ADDREADERTOGROUPW"; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + return funcName ? "SCardRemoveReaderFromGroupA" : "SCARD_IOCTL_REMOVEREADERFROMGROUPA"; + + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + return funcName ? "SCardRemoveReaderFromGroupW" : "SCARD_IOCTL_REMOVEREADERFROMGROUPW"; + + case SCARD_IOCTL_LOCATECARDSA: + return funcName ? "SCardLocateCardsA" : "SCARD_IOCTL_LOCATECARDSA"; + + case SCARD_IOCTL_LOCATECARDSW: + return funcName ? "SCardLocateCardsW" : "SCARD_IOCTL_LOCATECARDSW"; + + case SCARD_IOCTL_GETSTATUSCHANGEA: + return funcName ? "SCardGetStatusChangeA" : "SCARD_IOCTL_GETSTATUSCHANGEA"; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + return funcName ? "SCardGetStatusChangeW" : "SCARD_IOCTL_GETSTATUSCHANGEW"; + + case SCARD_IOCTL_CANCEL: + return funcName ? "SCardCancel" : "SCARD_IOCTL_CANCEL"; + + case SCARD_IOCTL_CONNECTA: + return funcName ? "SCardConnectA" : "SCARD_IOCTL_CONNECTA"; + + case SCARD_IOCTL_CONNECTW: + return funcName ? "SCardConnectW" : "SCARD_IOCTL_CONNECTW"; + + case SCARD_IOCTL_RECONNECT: + return funcName ? "SCardReconnect" : "SCARD_IOCTL_RECONNECT"; + + case SCARD_IOCTL_DISCONNECT: + return funcName ? "SCardDisconnect" : "SCARD_IOCTL_DISCONNECT"; + + case SCARD_IOCTL_BEGINTRANSACTION: + return funcName ? "SCardBeginTransaction" : "SCARD_IOCTL_BEGINTRANSACTION"; + + case SCARD_IOCTL_ENDTRANSACTION: + return funcName ? "SCardEndTransaction" : "SCARD_IOCTL_ENDTRANSACTION"; + + case SCARD_IOCTL_STATE: + return funcName ? "SCardState" : "SCARD_IOCTL_STATE"; + + case SCARD_IOCTL_STATUSA: + return funcName ? "SCardStatusA" : "SCARD_IOCTL_STATUSA"; + + case SCARD_IOCTL_STATUSW: + return funcName ? "SCardStatusW" : "SCARD_IOCTL_STATUSW"; + + case SCARD_IOCTL_TRANSMIT: + return funcName ? "SCardTransmit" : "SCARD_IOCTL_TRANSMIT"; + + case SCARD_IOCTL_CONTROL: + return funcName ? "SCardControl" : "SCARD_IOCTL_CONTROL"; + + case SCARD_IOCTL_GETATTRIB: + return funcName ? "SCardGetAttrib" : "SCARD_IOCTL_GETATTRIB"; + + case SCARD_IOCTL_SETATTRIB: + return funcName ? "SCardSetAttrib" : "SCARD_IOCTL_SETATTRIB"; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + return funcName ? "SCardAccessStartedEvent" : "SCARD_IOCTL_ACCESSSTARTEDEVENT"; + + case SCARD_IOCTL_LOCATECARDSBYATRA: + return funcName ? "SCardLocateCardsByATRA" : "SCARD_IOCTL_LOCATECARDSBYATRA"; + + case SCARD_IOCTL_LOCATECARDSBYATRW: + return funcName ? "SCardLocateCardsByATRB" : "SCARD_IOCTL_LOCATECARDSBYATRW"; + + case SCARD_IOCTL_READCACHEA: + return funcName ? "SCardReadCacheA" : "SCARD_IOCTL_READCACHEA"; + + case SCARD_IOCTL_READCACHEW: + return funcName ? "SCardReadCacheW" : "SCARD_IOCTL_READCACHEW"; + + case SCARD_IOCTL_WRITECACHEA: + return funcName ? "SCardWriteCacheA" : "SCARD_IOCTL_WRITECACHEA"; + + case SCARD_IOCTL_WRITECACHEW: + return funcName ? "SCardWriteCacheW" : "SCARD_IOCTL_WRITECACHEW"; + + case SCARD_IOCTL_GETTRANSMITCOUNT: + return funcName ? "SCardGetTransmitCount" : "SCARD_IOCTL_GETTRANSMITCOUNT"; + + case SCARD_IOCTL_RELEASETARTEDEVENT: + return funcName ? "SCardReleaseStartedEvent" : "SCARD_IOCTL_RELEASETARTEDEVENT"; + + case SCARD_IOCTL_GETREADERICON: + return funcName ? "SCardGetReaderIcon" : "SCARD_IOCTL_GETREADERICON"; + + case SCARD_IOCTL_GETDEVICETYPEID: + return funcName ? "SCardGetDeviceTypeId" : "SCARD_IOCTL_GETDEVICETYPEID"; + + default: + return funcName ? "SCardUnknown" : "SCARD_IOCTL_UNKNOWN"; + } +} diff --git a/src/protocols/rdp/channels/rdpdr/rdpdr.h b/src/protocols/rdp/channels/rdpdr/rdpdr.h index 3c5f4e200e..1f70e9c6a0 100644 --- a/src/protocols/rdp/channels/rdpdr/rdpdr.h +++ b/src/protocols/rdp/channels/rdpdr/rdpdr.h @@ -138,12 +138,12 @@ struct guac_rdpdr_device { * The DOS name of the device. Max 8 bytes, including terminator. */ const char *dos_name; - + /** * The stream that stores the RDPDR device announcement for this device. */ wStream* device_announce; - + /** * The length of the device_announce wStream. */ @@ -214,6 +214,9 @@ typedef struct guac_rdpdr { wStream* guac_rdpdr_new_io_completion(guac_rdpdr_device* device, unsigned int completion_id, unsigned int status, int size); +void guac_rdpdr_write_io_completion(wStream* output_stream, guac_rdpdr_device* device, + unsigned int completion_id, unsigned int status, int size); + /** * Initializes device redirection support (file transfer, printing, etc.) for * RDP and handling of the RDPDR channel. If failures occur, messages noting @@ -246,5 +249,8 @@ guac_rdp_common_svc_receive_handler guac_rdpdr_process_receive; */ guac_rdp_common_svc_terminate_handler guac_rdpdr_process_terminate; +const char* rdpdr_irp_string(uint32_t major); +const char* scard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName); + #endif diff --git a/src/protocols/rdp/channels/rdpdr/remote-smartcard.c b/src/protocols/rdp/channels/rdpdr/remote-smartcard.c new file mode 100644 index 0000000000..6cfeadaa6b --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/remote-smartcard.c @@ -0,0 +1,176 @@ +// remote-smartcard.c + +#include "channels/rdpdr/remote-smartcard.h" +#include "channels/rdpdr/msz-unicode.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/rdpdr.h" + +#include + +#include +#include +#include +#include +#include + + +#define RCARD_TAG FREERDP_TAG("remote.smartcard") + +static CHAR g_ReaderNameA[] = { 'F', 'r', 'e', 'e', 'R', 'D', 'P', ' ', 'E', + 'm', 'u', 'l', 'a', 't', 'o', 'r', '\0', '\0' }; +static INIT_ONCE g_ReaderNameWGuard = INIT_ONCE_STATIC_INIT; +static WCHAR g_ReaderNameW[32] = { 0 }; +static size_t g_ReaderNameWLen = 0; + +static wLog* scard_log(void) +{ + static wLog* log = NULL; + if (!log) + log = WLog_Get(RCARD_TAG); + return log; +} + +void RemoteSmartcard_free(RemoteSmartcard* smartcard) { + free(smartcard->context); +} + +// static size_t wcslenW(const WCHAR* str) +// { +// const WCHAR* s = str; +// while (s && *s) +// s++; +// return (size_t)(s - str); +// } + +static BOOL CALLBACK g_ReaderNameWInit(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context) +{ + WINPR_UNUSED(InitOnce); + WINPR_UNUSED(Parameter); + WINPR_UNUSED(Context); + + InitializeConstWCharFromUtf8(g_ReaderNameA, g_ReaderNameW, ARRAYSIZE(g_ReaderNameW)); + g_ReaderNameWLen = _wcsnlen(g_ReaderNameW, ARRAYSIZE(g_ReaderNameW) - 2) + 2; + return TRUE; +} + +void Emulate_SCardAccessStartedEvent(RemoteSmartcard* smartcard) { + guac_client_log(smartcard->log_client, GUAC_LOG_INFO, "RemoteSmartcard: Emulate_SCardAccessStartedEvent."); + return; +} + +LONG Emulate_SCardEstablishContext(RemoteSmartcard* smartcard, DWORD dwScope) { + wLog* log = scard_log(); + WLog_Print(log, WLOG_INFO, "RemoteSmartcard: Emulate_SCardEstablishContext. Scope: %u", dwScope); + + if (smartcard->context != NULL) { + guac_client_log(smartcard->log_client, GUAC_LOG_INFO, "RemoteSmartcard: Emulate_SCardEstablishContext. Context already established."); + return 0; + } + + REDIR_SCARDCONTEXT* context = (REDIR_SCARDCONTEXT*)calloc(1, sizeof(REDIR_SCARDCONTEXT)); + if (!context) { + return SCARD_F_UNKNOWN_ERROR; + } + + context->cbContext = 8; + + // Fill first 4 bytes with a fake handle, remaining 4 bytes as zeros + UINT32 fake_handle = 0x00000004; + memcpy(&context->pbContext[0], &fake_handle, sizeof(UINT32)); + memset(&context->pbContext[4], 0, 4); // Pad the remaining bytes + + smartcard->context = context; + return 0; +} + +LONG Emulate_SCardListReadersW(RemoteSmartcard* smartcard, LPCWSTR mszGroups, LPWSTR* mszReaders, LPDWORD pcchReaders) { + wLog* log = scard_log(); + WLog_Print(log, WLOG_INFO, "RemoteSmartcard: Emulate_SCardListReadersW"); + + // A reader is a device that accepts smartcards - like a USB device. + // The client adverstises all available readers, and the server may eventually call + // SCardConnect("Reader A", ...) to access the smartcard in that reader.; + InitOnceExecuteOnce(&g_ReaderNameWGuard, g_ReaderNameWInit, NULL, NULL); + const DWORD required_len = (DWORD)g_ReaderNameWLen; + + if (!pcchReaders) { + WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - !pcchReaders"); + return SCARD_E_INVALID_PARAMETER; + } + + const WCHAR expected[] = { + 'S','C','a','r','d','$','A','l','l','R','e','a','d','e','r','s','\0' + }; + const size_t len = sizeof(expected); // Includes null terminator + + // Only support SCard$AllReaders + if (!mszGroups || memcmp(mszGroups, expected, len) != 0) { + WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - query is not SCard$AllReaders"); + return SCARD_E_INVALID_PARAMETER; + } + + if (!mszReaders) + { + *pcchReaders = required_len; + return SCARD_S_SUCCESS; + } + + if (*pcchReaders < required_len) + { + *pcchReaders = required_len; + WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - SCARD_E_INSUFFICIENT_BUFFER"); + return SCARD_E_INSUFFICIENT_BUFFER; + } + + // WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - allocating..."); + WCHAR* out = (WCHAR*)calloc(required_len, sizeof(WCHAR)); + + //WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - allocated! copying..."); + memcpy(out, g_ReaderNameW, required_len * sizeof(WCHAR)); + *mszReaders = out; + + //memcpy(mszReaders, g_ReaderNameW, required_len * sizeof(WCHAR)); + *pcchReaders = required_len; + // WLog_Print(log, WLOG_INFO, "RemoteSmartcard: Emulate_SCardListReadersW. required_len: %lu", required_len); + + // char* mszGroupsUtf = NULL; + // char* mszReadersUtf = NULL; + // size_t utf8Len = 0; + + // WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - converting groups..."); + // mszGroupsUtf = ConvertMszWCharNToUtf8Alloc(mszGroups, wcslenW(mszGroups), &utf8Len); + // WLog_Print(log, WLOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW - converting readers..."); + // mszReadersUtf = ConvertMszWCharNToUtf8Alloc(*mszReaders, wcslenW(*mszReaders), &utf8Len); + + // WLog_Print(log, WLOG_INFO, "RemoteSmartcard: Emulate_SCardListReadersW. mszGroups: %s, mszReaders: %s, pcchReaders: %lu", mszGroupsUtf, mszReadersUtf, *pcchReaders); + return 0; +} + +LONG Emulate_SCardGetDeviceTypeIdW(RemoteSmartcard* smartcard, LPCWSTR szReaderName, LPDWORD pdwDeviceTypeId) { + *pdwDeviceTypeId = SCARD_READER_TYPE_USB; + + // TODO: validate the reader name is correct, match the reader name to a device type + + return 0; +} + +LONG Emulate_SCardGetStatusChangeW(RemoteSmartcard* smartcard, DWORD dwTimeout, + LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders) +{ + wLog* log = scard_log(); + WLog_Print(log, WLOG_ERROR, "SCardGetStatusChangeW (emulated) called"); + + for (DWORD i = 0; i < cReaders; i++) + { + LPSCARD_READERSTATEW readerState = &rgReaderStates[i]; + + // Set all readers to STATE_PRESENT regardless of their actual state + readerState->dwEventState = SCARD_STATE_PRESENT; + + // Optionally copy cbAtr and rgbAtr if needed; zero them otherwise + readerState->cbAtr = 0; + memset(readerState->rgbAtr, 0, sizeof(readerState->rgbAtr)); + } + + return SCARD_S_SUCCESS; +} diff --git a/src/protocols/rdp/channels/rdpdr/remote-smartcard.h b/src/protocols/rdp/channels/rdpdr/remote-smartcard.h new file mode 100644 index 0000000000..1bea6b3936 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/remote-smartcard.h @@ -0,0 +1,45 @@ +// smartcard.h + +#include "channels/rdpdr/scard.h" + +#include + +#include +#include +#include + +#ifndef GUAC_SMARTCARD_H +#define GUAC_SMARTCARD_H +/* +Provides a layer of abstraction over the smart card. Every IOCTL call made over the +device channel should eventually make its way here. Some of the calls will be emulated +and cached, while others will send data to the guacamole server. +*/ + +typedef struct smartcard_emulation_context +{ + // wHashTable* handles; + BOOL configured; + const char* pem; + const char* key; + const char* pin; + REDIR_SCARDCONTEXT* context; + + guac_client* log_client; +} RemoteSmartcard; + +void Emulate_SCardAccessStartedEvent(RemoteSmartcard* smartcard); + +LONG Emulate_SCardEstablishContext(RemoteSmartcard* smartcard, DWORD dwScope); + +LONG Emulate_SCardListReadersW(RemoteSmartcard* smartcard, + LPCWSTR mszGroups, + LPWSTR* mszReaders, LPDWORD pcchReaders); + +LONG Emulate_SCardGetDeviceTypeIdW(RemoteSmartcard* smartcard, LPCWSTR szReaderName, LPDWORD pdwDeviceTypeId); + +LONG Emulate_SCardGetStatusChangeW(RemoteSmartcard* smartcard, DWORD dwTimeout, LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders); + +void RemoteSmartcard_free(RemoteSmartcard* smartcard); + +#endif diff --git a/src/protocols/rdp/channels/rdpdr/scard.h b/src/protocols/rdp/channels/rdpdr/scard.h new file mode 100644 index 0000000000..c2766eac73 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/scard.h @@ -0,0 +1,485 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Redirection Virtual Channel + * + * Copyright 2021 Armin Novak + * Copyright 2021 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SCARD_H +#define FREERDP_CHANNEL_SCARD_H + +#include +#include + +#define RDP_SCARD_CTL_CODE(code) \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, (code), METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define SCARD_IOCTL_ESTABLISHCONTEXT RDP_SCARD_CTL_CODE(5) /* SCardEstablishContext */ +#define SCARD_IOCTL_RELEASECONTEXT RDP_SCARD_CTL_CODE(6) /* SCardReleaseContext */ +#define SCARD_IOCTL_ISVALIDCONTEXT RDP_SCARD_CTL_CODE(7) /* SCardIsValidContext */ +#define SCARD_IOCTL_LISTREADERGROUPSA RDP_SCARD_CTL_CODE(8) /* SCardListReaderGroupsA */ +#define SCARD_IOCTL_LISTREADERGROUPSW RDP_SCARD_CTL_CODE(9) /* SCardListReaderGroupsW */ +#define SCARD_IOCTL_LISTREADERSA RDP_SCARD_CTL_CODE(10) /* SCardListReadersA */ +#define SCARD_IOCTL_LISTREADERSW RDP_SCARD_CTL_CODE(11) /* SCardListReadersW */ +#define SCARD_IOCTL_INTRODUCEREADERGROUPA RDP_SCARD_CTL_CODE(20) /* SCardIntroduceReaderGroupA */ +#define SCARD_IOCTL_INTRODUCEREADERGROUPW RDP_SCARD_CTL_CODE(21) /* SCardIntroduceReaderGroupW */ +#define SCARD_IOCTL_FORGETREADERGROUPA RDP_SCARD_CTL_CODE(22) /* SCardForgetReaderGroupA */ +#define SCARD_IOCTL_FORGETREADERGROUPW RDP_SCARD_CTL_CODE(23) /* SCardForgetReaderGroupW */ +#define SCARD_IOCTL_INTRODUCEREADERA RDP_SCARD_CTL_CODE(24) /* SCardIntroduceReaderA */ +#define SCARD_IOCTL_INTRODUCEREADERW RDP_SCARD_CTL_CODE(25) /* SCardIntroduceReaderW */ +#define SCARD_IOCTL_FORGETREADERA RDP_SCARD_CTL_CODE(26) /* SCardForgetReaderA */ +#define SCARD_IOCTL_FORGETREADERW RDP_SCARD_CTL_CODE(27) /* SCardForgetReaderW */ +#define SCARD_IOCTL_ADDREADERTOGROUPA RDP_SCARD_CTL_CODE(28) /* SCardAddReaderToGroupA */ +#define SCARD_IOCTL_ADDREADERTOGROUPW RDP_SCARD_CTL_CODE(29) /* SCardAddReaderToGroupW */ +#define SCARD_IOCTL_REMOVEREADERFROMGROUPA \ + RDP_SCARD_CTL_CODE(30) /* SCardRemoveReaderFromGroupA \ + */ +#define SCARD_IOCTL_REMOVEREADERFROMGROUPW \ + RDP_SCARD_CTL_CODE(31) /* SCardRemoveReaderFromGroupW \ + */ +#define SCARD_IOCTL_LOCATECARDSA RDP_SCARD_CTL_CODE(38) /* SCardLocateCardsA */ +#define SCARD_IOCTL_LOCATECARDSW RDP_SCARD_CTL_CODE(39) /* SCardLocateCardsW */ +#define SCARD_IOCTL_GETSTATUSCHANGEA RDP_SCARD_CTL_CODE(40) /* SCardGetStatusChangeA */ +#define SCARD_IOCTL_GETSTATUSCHANGEW RDP_SCARD_CTL_CODE(41) /* SCardGetStatusChangeW */ +#define SCARD_IOCTL_CANCEL RDP_SCARD_CTL_CODE(42) /* SCardCancel */ +#define SCARD_IOCTL_CONNECTA RDP_SCARD_CTL_CODE(43) /* SCardConnectA */ +#define SCARD_IOCTL_CONNECTW RDP_SCARD_CTL_CODE(44) /* SCardConnectW */ +#define SCARD_IOCTL_RECONNECT RDP_SCARD_CTL_CODE(45) /* SCardReconnect */ +#define SCARD_IOCTL_DISCONNECT RDP_SCARD_CTL_CODE(46) /* SCardDisconnect */ +#define SCARD_IOCTL_BEGINTRANSACTION RDP_SCARD_CTL_CODE(47) /* SCardBeginTransaction */ +#define SCARD_IOCTL_ENDTRANSACTION RDP_SCARD_CTL_CODE(48) /* SCardEndTransaction */ +#define SCARD_IOCTL_STATE RDP_SCARD_CTL_CODE(49) /* SCardState */ +#define SCARD_IOCTL_STATUSA RDP_SCARD_CTL_CODE(50) /* SCardStatusA */ +#define SCARD_IOCTL_STATUSW RDP_SCARD_CTL_CODE(51) /* SCardStatusW */ +#define SCARD_IOCTL_TRANSMIT RDP_SCARD_CTL_CODE(52) /* SCardTransmit */ +#define SCARD_IOCTL_CONTROL RDP_SCARD_CTL_CODE(53) /* SCardControl */ +#define SCARD_IOCTL_GETATTRIB RDP_SCARD_CTL_CODE(54) /* SCardGetAttrib */ +#define SCARD_IOCTL_SETATTRIB RDP_SCARD_CTL_CODE(55) /* SCardSetAttrib */ +#define SCARD_IOCTL_ACCESSSTARTEDEVENT RDP_SCARD_CTL_CODE(56) /* SCardAccessStartedEvent */ +#define SCARD_IOCTL_RELEASETARTEDEVENT RDP_SCARD_CTL_CODE(57) /* SCardReleaseStartedEvent */ +#define SCARD_IOCTL_LOCATECARDSBYATRA RDP_SCARD_CTL_CODE(58) /* SCardLocateCardsByATRA */ +#define SCARD_IOCTL_LOCATECARDSBYATRW RDP_SCARD_CTL_CODE(59) /* SCardLocateCardsByATRW */ +#define SCARD_IOCTL_READCACHEA RDP_SCARD_CTL_CODE(60) /* SCardReadCacheA */ +#define SCARD_IOCTL_READCACHEW RDP_SCARD_CTL_CODE(61) /* SCardReadCacheW */ +#define SCARD_IOCTL_WRITECACHEA RDP_SCARD_CTL_CODE(62) /* SCardWriteCacheA */ +#define SCARD_IOCTL_WRITECACHEW RDP_SCARD_CTL_CODE(63) /* SCardWriteCacheW */ +#define SCARD_IOCTL_GETTRANSMITCOUNT RDP_SCARD_CTL_CODE(64) /* SCardGetTransmitCount */ +#define SCARD_IOCTL_GETREADERICON RDP_SCARD_CTL_CODE(65) /* SCardGetReaderIconA */ +#define SCARD_IOCTL_GETDEVICETYPEID RDP_SCARD_CTL_CODE(66) /* SCardGetDeviceTypeIdA */ + +#pragma pack(push, 1) + +/* interface type_scard_pack */ +/* [unique][version][uuid] */ + +typedef struct +{ + /* [range] */ DWORD cbContext; + /* [size_is][unique] */ BYTE pbContext[8]; +} REDIR_SCARDCONTEXT; + +typedef struct +{ + /* [range] */ DWORD cbHandle; + /* [size_is] */ BYTE pbHandle[8]; +} REDIR_SCARDHANDLE; + +typedef struct +{ + LONG ReturnCode; +} Long_Return; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* msz; +} ListReaderGroups_Return, ListReaders_Return; + +typedef struct +{ + DWORD dwCurrentState; + DWORD dwEventState; + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; +} ReaderState_Return; + +typedef struct +{ + /* [range] */ DWORD cbAtr; + BYTE rgbAtr[36]; + BYTE rgbMask[36]; +} LocateCards_ATRMask; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cReaders; + /* [size_is] */ ReaderState_Return* rgReaderStates; +} LocateCards_Return, GetStatusChange_Return; + +typedef struct +{ + LONG ReturnCode; + ULONG cbDataLen; + BYTE* pbData; +} GetReaderIcon_Return; + +typedef struct +{ + LONG ReturnCode; + ULONG dwDeviceId; +} GetDeviceTypeId_Return; + +typedef struct +{ + LONG ReturnCode; + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; + DWORD dwActiveProtocol; +} Connect_Return; + +typedef struct +{ + LONG ReturnCode; + DWORD dwActiveProtocol; +} Reconnect_Return; + +typedef struct +{ + LONG ReturnCode; + DWORD dwState; + DWORD dwProtocol; + /* [range] */ DWORD cbAtrLen; + /* [size_is][unique] */ BYTE rgAtr[36]; +} State_Return; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* mszReaderNames; + DWORD dwState; + DWORD dwProtocol; + BYTE pbAtr[32]; + /* [range] */ DWORD cbAtrLen; +} Status_Return; + +typedef struct +{ + DWORD dwProtocol; + /* [range] */ DWORD cbExtraBytes; + /* [size_is][unique] */ BYTE* pbExtraBytes; +} SCardIO_Request; + +typedef struct +{ + LONG ReturnCode; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + /* [range] */ DWORD cbRecvLength; + /* [size_is][unique] */ BYTE* pbRecvBuffer; +} Transmit_Return; + +typedef struct +{ + LONG ReturnCode; + DWORD cTransmitCount; +} GetTransmitCount_Return; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cbOutBufferSize; + /* [size_is][unique] */ BYTE* pvOutBuffer; +} Control_Return; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cbAttrLen; + /* [size_is][unique] */ BYTE* pbAttr; +} GetAttrib_Return; + +typedef struct +{ + LONG ReturnCode; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} ReadCache_Return; +#pragma pack(pop) + +typedef struct +{ + REDIR_SCARDCONTEXT hContext; + REDIR_SCARDHANDLE hCard; +} Handles_Call; + +typedef struct +{ + Handles_Call handles; + LONG fmszGroupsIsNULL; + DWORD cchGroups; +} ListReaderGroups_Call; + +typedef struct +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is][unique] */ BYTE* mszGroups; + LONG fmszReadersIsNULL; + DWORD cchReaders; +} ListReaders_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} GetStatusChangeA_Call; + +typedef struct +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is] */ CHAR* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} LocateCardsA_Call; + +typedef struct +{ + Handles_Call handles; + /* [range] */ DWORD cBytes; + /* [size_is] */ WCHAR* mszCards; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} LocateCardsW_Call; + +typedef struct +{ + Handles_Call handles; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEA rgReaderStates; +} LocateCardsByATRA_Call; + +typedef struct +{ + Handles_Call handles; + /* [range] */ DWORD cAtrs; + /* [size_is] */ LocateCards_ATRMask* rgAtrMasks; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} LocateCardsByATRW_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwTimeOut; + /* [range] */ DWORD cReaders; + /* [size_is] */ LPSCARD_READERSTATEW rgReaderStates; +} GetStatusChangeW_Call; + +typedef struct +{ + Handles_Call handles; + WCHAR* szReaderName; +} GetReaderIcon_Call; + +typedef struct +{ + Handles_Call handles; + WCHAR* szReaderName; +} GetDeviceTypeId_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwShareMode; + DWORD dwPreferredProtocols; +} Connect_Common_Call; + +typedef struct +{ + Connect_Common_Call Common; + /* [string] */ CHAR* szReader; +} ConnectA_Call; + +typedef struct +{ + Connect_Common_Call Common; + /* [string] */ WCHAR* szReader; +} ConnectW_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwShareMode; + DWORD dwPreferredProtocols; + DWORD dwInitialization; +} Reconnect_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwDisposition; +} HCardAndDisposition_Call; + +typedef struct +{ + Handles_Call handles; + LONG fpbAtrIsNULL; + DWORD cbAtrLen; +} State_Call; + +typedef struct +{ + Handles_Call handles; + LONG fmszReaderNamesIsNULL; + DWORD cchReaderLen; + DWORD cbAtrLen; +} Status_Call; + +typedef struct +{ + Handles_Call handles; + LPSCARD_IO_REQUEST pioSendPci; + /* [range] */ DWORD cbSendLength; + /* [size_is] */ BYTE* pbSendBuffer; + /* [unique] */ LPSCARD_IO_REQUEST pioRecvPci; + LONG fpbRecvBufferIsNULL; + DWORD cbRecvLength; +} Transmit_Call; + +typedef struct +{ + Handles_Call handles; + LONG LongValue; +} Long_Call; + +typedef struct +{ + Handles_Call handles; +} Context_Call; + +typedef struct +{ + Handles_Call handles; + /* [string] */ char* sz; +} ContextAndStringA_Call; + +typedef struct +{ + Handles_Call handles; + /* [string] */ WCHAR* sz; +} ContextAndStringW_Call; + +typedef struct +{ + Handles_Call handles; + /* [string] */ char* sz1; + /* [string] */ char* sz2; +} ContextAndTwoStringA_Call; + +typedef struct +{ + Handles_Call handles; + /* [string] */ WCHAR* sz1; + /* [string] */ WCHAR* sz2; +} ContextAndTwoStringW_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwScope; +} EstablishContext_Call; + +typedef struct +{ + Handles_Call handles; +} GetTransmitCount_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwControlCode; + /* [range] */ DWORD cbInBufferSize; + /* [size_is][unique] */ BYTE* pvInBuffer; + LONG fpvOutBufferIsNULL; + DWORD cbOutBufferSize; +} Control_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwAttrId; + LONG fpbAttrIsNULL; + DWORD cbAttrLen; +} GetAttrib_Call; + +typedef struct +{ + Handles_Call handles; + DWORD dwAttrId; + /* [range] */ DWORD cbAttrLen; + /* [size_is] */ BYTE* pbAttr; +} SetAttrib_Call; + +typedef struct +{ + Handles_Call handles; + UUID* CardIdentifier; + DWORD FreshnessCounter; + LONG fPbDataIsNULL; + DWORD cbDataLen; +} ReadCache_Common; + +typedef struct +{ + ReadCache_Common Common; + /* [string] */ char* szLookupName; +} ReadCacheA_Call; + +typedef struct +{ + ReadCache_Common Common; + /* [string] */ WCHAR* szLookupName; +} ReadCacheW_Call; + +typedef struct +{ + Handles_Call handles; + UUID* CardIdentifier; + DWORD FreshnessCounter; + /* [range] */ DWORD cbDataLen; + /* [size_is][unique] */ BYTE* pbData; +} WriteCache_Common; + +typedef struct +{ + WriteCache_Common Common; + /* [string] */ char* szLookupName; +} WriteCacheA_Call; + +typedef struct +{ + WriteCache_Common Common; + /* [string] */ WCHAR* szLookupName; +} WriteCacheW_Call; + +#endif /* FREERDP_CHANNEL_SCARD_H */ diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-call.c b/src/protocols/rdp/channels/rdpdr/smartcard-call.c new file mode 100644 index 0000000000..7caded1701 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-call.c @@ -0,0 +1,642 @@ +#include "channels/rdpdr/smartcard-call.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/smartcard-pack.h" +#include "channels/rdpdr/remote-smartcard.h" +#include "channels/rdpdr/msz-unicode.h" + +#include +#include "channels/rdpdr/rdpdr.h" + +#include + +#include +#include +#include + +static LONG scard_log_status_error_wlog(wLog* log, const char* what, LONG status) +{ + if (status != SCARD_S_SUCCESS) + { + DWORD level = WLOG_ERROR; + switch (status) + { + case SCARD_E_TIMEOUT: + level = WLOG_DEBUG; + break; + case SCARD_E_NO_READERS_AVAILABLE: + level = WLOG_INFO; + break; + default: + break; + } + WLog_Print(log, level, "%s failed with error %s [%" PRId32 "]", what, + SCardGetErrorString(status), status); + } + return status; +} + +static LONG smartcard_AccessStartedEvent_Call(scard_call_context* smartcard, guac_rdp_scard_operation* op) +{ + WINPR_UNUSED(smartcard); + WINPR_UNUSED(op); + + return SCARD_S_SUCCESS; +} + +static LONG smartcard_EstablishContext_Call(guac_rdp_common_svc* svc, scard_call_context* smartcard_ctx, guac_rdp_scard_operation* op) +{ + LONG status = 0; + EstablishContext_Call* call = &op->call.establishContext; + status = Emulate_SCardEstablishContext(smartcard_ctx->smartcard, call->dwScope); + + if (status != SCARD_S_SUCCESS) + { + guac_client_log(svc->client, GUAC_LOG_ERROR, "smartcard_EstablishContext_Call failed with error %" PRId32 "!", status); + return status; + } + + status = smartcard_pack_establish_context_return(op->out, smartcard_ctx->smartcard); + if (status != SCARD_S_SUCCESS) + { + guac_client_log(svc->client, GUAC_LOG_ERROR, "smartcard_EstablishContext_Call:smartcard_pack_establish_context_return failed with error %" PRId32 "!", status); + return status; + } + + return status; +} + +static BOOL filter_match(wLinkedList* list, LPCSTR reader, size_t readerLen) +{ + if (readerLen < 1) + return FALSE; + + LinkedList_Enumerator_Reset(list); + + while (LinkedList_Enumerator_MoveNext(list)) + { + const char* filter = LinkedList_Enumerator_Current(list); + + if (filter) + { + if (strstr(reader, filter) != NULL) + return TRUE; + } + } + + return FALSE; +} + +static DWORD filter_device_by_name_a(wLinkedList* list, LPSTR* mszReaders, DWORD cchReaders) +{ + size_t rpos = 0; + size_t wpos = 0; + + if (*mszReaders == NULL || LinkedList_Count(list) < 1) + return cchReaders; + + do + { + LPCSTR rreader = &(*mszReaders)[rpos]; + LPSTR wreader = &(*mszReaders)[wpos]; + size_t readerLen = strnlen(rreader, cchReaders - rpos); + + rpos += readerLen + 1; + + if (filter_match(list, rreader, readerLen)) + { + if (rreader != wreader) + memmove(wreader, rreader, readerLen + 1); + + wpos += readerLen + 1; + } + } while (rpos < cchReaders); + + /* this string must be double 0 terminated */ + if (rpos != wpos) + { + if (wpos >= cchReaders) + return 0; + + (*mszReaders)[wpos++] = '\0'; + } + + return (DWORD)wpos; +} + +#define SCARD_TAG FREERDP_TAG("scard.pack") + +static wLog* scard_log(void) +{ + static wLog* log = NULL; + if (!log) + log = WLog_Get(SCARD_TAG); + return log; +} + +static DWORD filter_device_by_name_w(wLinkedList* list, LPWSTR* mszReaders, DWORD cchReaders) +{ + wLog* log = scard_log(); + WLog_Print(log, WLOG_ERROR, "filter_device_by_name_w: start"); + DWORD rc = 0; + LPSTR readers = NULL; + + if (LinkedList_Count(list) < 1) + return cchReaders; + + WLog_Print(log, WLOG_ERROR, "filter_device_by_name_w: ConvertMszWCharNToUtf8Alloc"); + readers = ConvertMszWCharNToUtf8Alloc(*mszReaders, cchReaders, NULL); + + if (!readers) + { + free(readers); + return 0; + } + + free(*mszReaders); + *mszReaders = NULL; + + WLog_Print(log, WLOG_ERROR, "filter_device_by_name_w: filter_device_by_name_a"); + rc = filter_device_by_name_a(list, &readers, cchReaders); + + WLog_Print(log, WLOG_ERROR, "filter_device_by_name_w: ConvertMszUtf8NToWCharAlloc"); + *mszReaders = ConvertMszUtf8NToWCharAlloc(readers, rc, NULL); + if (!*mszReaders) + rc = 0; + + free(readers); + return rc; +} + +static size_t wcslenW(const WCHAR* str) +{ + const WCHAR* s = str; + while (s && *s) + s++; + return (size_t)(s - str); +} + +static LONG smartcard_ListReadersW_Call(guac_rdp_common_svc* svc, scard_call_context* smartcard, guac_rdp_scard_operation* op) +{ + LONG status = 0; + ListReaders_Return ret = { 0 }; + DWORD cchReaders = 0; + ListReaders_Call* call = NULL; + union + { + const BYTE* bp; + const char* sz; + const WCHAR* wz; + } string; + union + { + WCHAR** ppw; + WCHAR* pw; + CHAR* pc; + BYTE* pb; + } mszReaders; + + WINPR_ASSERT(smartcard); + WINPR_ASSERT(op); + + call = &op->call.listReaders; + + string.bp = call->mszGroups; + cchReaders = SCARD_AUTOALLOCATE; + + char* mszGroupsUtf = NULL; + char* mszReadersUtf = NULL; + size_t utf8Len = 0; + + status = Emulate_SCardListReadersW(smartcard->smartcard, string.wz, &mszReaders.pw, &cchReaders); + + mszGroupsUtf = ConvertMszWCharNToUtf8Alloc(string.wz, wcslenW(string.wz), &utf8Len); + mszReadersUtf = ConvertMszWCharNToUtf8Alloc(mszReaders.pw, wcslenW(mszReaders.pw), &utf8Len); + guac_client_log(svc->client, GUAC_LOG_ERROR, "RemoteSmartcard: Emulate_SCardListReadersW. string.wz: %s, &mszReaders.pw: %s, cchReaders: %lu", mszGroupsUtf, mszReadersUtf, cchReaders); + + if (status == SCARD_S_SUCCESS) + { + if (cchReaders == SCARD_AUTOALLOCATE) { + guac_client_log(svc->client, GUAC_LOG_ERROR, "Emulate_SCardListReadersW: cchReaders SCARD_AUTOALLOCATE, unknown error."); + status = SCARD_F_UNKNOWN_ERROR; + } + } + + if (status != SCARD_S_SUCCESS) + { + guac_client_log(svc->client, GUAC_LOG_ERROR, "SCardListReadersW failed with error %" PRId32 "!", status); + return smartcard_pack_list_readers_return(op->out, &ret, TRUE); + } + + guac_client_log(svc->client, GUAC_LOG_ERROR, "filtering by name."); + cchReaders = filter_device_by_name_w(smartcard->names, &mszReaders.pw, cchReaders); + ret.msz = mszReaders.pb; + ret.cBytes = cchReaders * sizeof(WCHAR); + ret.ReturnCode = status; + + guac_client_log(svc->client, GUAC_LOG_ERROR, "packing the return"); + status = smartcard_pack_list_readers_return(op->out, &ret, TRUE); + guac_client_log(svc->client, GUAC_LOG_ERROR, "return is packed"); + + // if (mszReaders.pb) + // wrap(smartcard, SCardFreeMemory, operation->hContext, mszReaders.pb); + + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_GetDeviceTypeId_Call(scard_call_context* smartcard, guac_rdp_scard_operation* operation) +{ + wLog* log = scard_log(); + LONG status = 0; + GetDeviceTypeId_Return ret = { 0 }; + GetDeviceTypeId_Call* call = NULL; + + WINPR_ASSERT(smartcard); + WINPR_ASSERT(out); + WINPR_ASSERT(operation); + + call = &operation->call.getDeviceTypeId; + + ret.ReturnCode = Emulate_SCardGetDeviceTypeIdW(smartcard->smartcard, call->szReaderName, &ret.dwDeviceId); + scard_log_status_error_wlog(log, "SCardGetDeviceTypeIdW", ret.ReturnCode); + + status = smartcard_pack_device_type_id_return(operation->out, &ret); + if (status != SCARD_S_SUCCESS) + return status; + + return ret.ReturnCode; +} + +static LONG smartcard_GetStatusChangeW_Call(scard_call_context* smartcard, guac_rdp_scard_operation* operation) +{ + wLog* log = scard_log(); + + LONG status = STATUS_NO_MEMORY; + DWORD dwTimeOut = 0; + const DWORD dwTimeStep = 100; + GetStatusChange_Return ret = { 0 }; + LPSCARD_READERSTATEW rgReaderStates = NULL; + + WINPR_ASSERT(smartcard); + WINPR_ASSERT(operation); + + GetStatusChangeW_Call* call = &operation->call.getStatusChangeW; + dwTimeOut = call->dwTimeOut; + + if (call->cReaders > 0) + { + ret.cReaders = call->cReaders; + rgReaderStates = calloc(ret.cReaders, sizeof(SCARD_READERSTATEW)); + ret.rgReaderStates = (ReaderState_Return*)calloc(ret.cReaders, sizeof(ReaderState_Return)); + if (!rgReaderStates || !ret.rgReaderStates) + goto fail; + } + + for (UINT32 x = 0; x < MAX(1, dwTimeOut);) + { + if (call->cReaders > 0) + memcpy(rgReaderStates, call->rgReaderStates, + call->cReaders * sizeof(SCARD_READERSTATEW)); + { + ret.ReturnCode = Emulate_SCardGetStatusChangeW(smartcard->smartcard, MIN(dwTimeOut, dwTimeStep), rgReaderStates, call->cReaders); + } + if (ret.ReturnCode != SCARD_E_TIMEOUT) { + break; + } else { + scard_log_status_error_wlog(log, "Invalid return code", ret.ReturnCode); + return ret.ReturnCode; + } + } + scard_log_status_error_wlog(log, "SCardGetStatusChangeW", ret.ReturnCode); + + for (UINT32 index = 0; index < ret.cReaders; index++) + { + const SCARD_READERSTATEW* cur = &rgReaderStates[index]; + ReaderState_Return* rout = &ret.rgReaderStates[index]; + + rout->dwCurrentState = cur->dwCurrentState; + rout->dwEventState = cur->dwEventState; + rout->cbAtr = cur->cbAtr; + CopyMemory(&(rout->rgbAtr), cur->rgbAtr, sizeof(rout->rgbAtr)); + } + + status = smartcard_pack_get_status_change_return(operation->out, &ret, TRUE); +fail: + free(ret.rgReaderStates); + free(rgReaderStates); + if (status != SCARD_S_SUCCESS) + return status; + return ret.ReturnCode; +} + +static LONG smartcard_ReleaseContext_Call(scard_call_context* smartcard, guac_rdp_scard_operation* operation) +{ + return 0; +} + +LONG guac_rdpdr_smartcard_irp_device_control_call(guac_rdp_common_svc* svc, + scard_call_context* ctx, + guac_rdpdr_iorequest* request, + guac_rdp_scard_operation* op, + NTSTATUS* io_status) { + LONG result = 0; + UINT32 offset = 0; + size_t objectBufferLength = 0; + + const UINT32 ioControlCode = op->ioControlCode; + + /** + * [MS-RDPESC] 3.2.5.1: Sending Outgoing Messages: + * the output buffer length SHOULD be set to 2048 + * + * Since it's a SHOULD and not a MUST, we don't care + * about it, but we still reserve at least 2048 bytes. + */ + const size_t outMaxLen = MAX(2048, op->outputBufferLength); + if (!Stream_EnsureRemainingCapacity(op->out, outMaxLen)) { + guac_client_log(svc->client, GUAC_LOG_ERROR, "guac_rdpdr_smartcard_irp_device_control_call: failed to ensure sufficient memory"); + return SCARD_E_NO_MEMORY; + } + + /* Device Control Response */ + Stream_Write_UINT32(op->out, 0); /* OutputBufferLength (4 bytes) */ + Stream_Zero(op->out, SMARTCARD_COMMON_TYPE_HEADER_LENGTH); /* CommonTypeHeader (8 bytes) */ + Stream_Zero(op->out, SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH); /* PrivateTypeHeader (8 bytes) */ + Stream_Write_UINT32(op->out, 0); /* Result (4 bytes) */ + + /* Call */ + switch (ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + result = smartcard_EstablishContext_Call(svc, ctx, op); + break; + + case SCARD_IOCTL_RELEASECONTEXT: + result = smartcard_ReleaseContext_Call(ctx, op); + break; + + // case SCARD_IOCTL_ISVALIDCONTEXT: + // result = smartcard_IsValidContext_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LISTREADERGROUPSA: + // result = smartcard_ListReaderGroupsA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LISTREADERGROUPSW: + // result = smartcard_ListReaderGroupsW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LISTREADERSA: + // result = smartcard_ListReadersA_Call(smartcard, out, operation); + // break; + + case SCARD_IOCTL_LISTREADERSW: + result = smartcard_ListReadersW_Call(svc, ctx, op); + break; + + // case SCARD_IOCTL_INTRODUCEREADERGROUPA: + // result = smartcard_IntroduceReaderGroupA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_INTRODUCEREADERGROUPW: + // result = smartcard_IntroduceReaderGroupW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_FORGETREADERGROUPA: + // result = smartcard_ForgetReaderA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_FORGETREADERGROUPW: + // result = smartcard_ForgetReaderW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_INTRODUCEREADERA: + // result = smartcard_IntroduceReaderA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_INTRODUCEREADERW: + // result = smartcard_IntroduceReaderW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_FORGETREADERA: + // result = smartcard_ForgetReaderA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_FORGETREADERW: + // result = smartcard_ForgetReaderW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_ADDREADERTOGROUPA: + // result = smartcard_AddReaderToGroupA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_ADDREADERTOGROUPW: + // result = smartcard_AddReaderToGroupW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + // result = smartcard_RemoveReaderFromGroupA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + // result = smartcard_RemoveReaderFromGroupW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LOCATECARDSA: + // result = smartcard_LocateCardsA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LOCATECARDSW: + // result = smartcard_LocateCardsW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_GETSTATUSCHANGEA: + // result = smartcard_GetStatusChangeA_Call(smartcard, out, operation); + // break; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + result = smartcard_GetStatusChangeW_Call(ctx, op); + break; + + // case SCARD_IOCTL_CANCEL: + // result = smartcard_Cancel_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_CONNECTA: + // result = smartcard_ConnectA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_CONNECTW: + // result = smartcard_ConnectW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_RECONNECT: + // result = smartcard_Reconnect_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_DISCONNECT: + // result = smartcard_Disconnect_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_BEGINTRANSACTION: + // result = smartcard_BeginTransaction_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_ENDTRANSACTION: + // result = smartcard_EndTransaction_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_STATE: + // result = smartcard_State_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_STATUSA: + // result = smartcard_StatusA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_STATUSW: + // result = smartcard_StatusW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_TRANSMIT: + // result = smartcard_Transmit_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_CONTROL: + // result = smartcard_Control_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_GETATTRIB: + // result = smartcard_GetAttrib_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_SETATTRIB: + // result = smartcard_SetAttrib_Call(smartcard, out, operation); + // break; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + result = smartcard_AccessStartedEvent_Call(ctx, op); + break; + + // case SCARD_IOCTL_LOCATECARDSBYATRA: + // result = smartcard_LocateCardsByATRA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_LOCATECARDSBYATRW: + // result = smartcard_LocateCardsW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_READCACHEA: + // result = smartcard_ReadCacheA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_READCACHEW: + // result = smartcard_ReadCacheW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_WRITECACHEA: + // result = smartcard_WriteCacheA_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_WRITECACHEW: + // result = smartcard_WriteCacheW_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_GETTRANSMITCOUNT: + // result = smartcard_GetTransmitCount_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_RELEASETARTEDEVENT: + // result = smartcard_ReleaseStartedEvent_Call(smartcard, out, operation); + // break; + + // case SCARD_IOCTL_GETREADERICON: + // result = smartcard_GetReaderIcon_Call(smartcard, out, operation); + // break; + + case SCARD_IOCTL_GETDEVICETYPEID: + result = smartcard_GetDeviceTypeId_Call(ctx, op); + break; + + // default: + // result = STATUS_UNSUCCESSFUL; + // break; + } + + /** + * [MS-RPCE] 2.2.6.3 Primitive Type Serialization + * The type MUST be aligned on an 8-byte boundary. If the size of the + * primitive type is not a multiple of 8 bytes, the data MUST be padded. + */ + + if ((ioControlCode != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioControlCode != SCARD_IOCTL_RELEASETARTEDEVENT)) + { + offset = (RDPDR_DEVICE_IO_RESPONSE_LENGTH + RDPDR_DEVICE_IO_CONTROL_RSP_HDR_LENGTH); + smartcard_pack_write_size_align(op->out, Stream_GetPosition(op->out) - offset, 8); + } + + if ((result != SCARD_S_SUCCESS) && (result != SCARD_E_TIMEOUT) && + (result != SCARD_E_NO_READERS_AVAILABLE) && (result != SCARD_E_NO_SERVICE) && + (result != SCARD_W_CACHE_ITEM_NOT_FOUND) && (result != SCARD_W_CACHE_ITEM_STALE)) + { + guac_client_log(svc->client, GUAC_LOG_WARNING, + "IRP failure: %s (0x%08" PRIX32 "), status: %s (0x%08" PRIX32 ")", + scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, + SCardGetErrorString(result), result); + } + + *io_status = STATUS_SUCCESS; + + if ((result & 0xC0000000L) == 0xC0000000L) + { + /* NTSTATUS error */ + *io_status = result; + guac_client_log(svc->client, GUAC_LOG_WARNING, + "IRP failure: %s (0x%08" PRIX32 "), ntstatus: 0x%08" PRIX32 "", + scard_get_ioctl_string(ioControlCode, TRUE), ioControlCode, result); + } + + Stream_SealLength(op->out); + size_t outputBufferLength = Stream_Length(op->out); + WINPR_ASSERT(outputBufferLength >= RDPDR_DEVICE_IO_RESPONSE_LENGTH + 4U); + outputBufferLength -= (RDPDR_DEVICE_IO_RESPONSE_LENGTH + 4U); + WINPR_ASSERT(outputBufferLength >= RDPDR_DEVICE_IO_RESPONSE_LENGTH); + objectBufferLength = outputBufferLength - RDPDR_DEVICE_IO_RESPONSE_LENGTH; + WINPR_ASSERT(outputBufferLength <= UINT32_MAX); + WINPR_ASSERT(objectBufferLength <= UINT32_MAX); + Stream_SetPosition(op->out, RDPDR_DEVICE_IO_RESPONSE_LENGTH); + + /* [MS-RDPESC] 3.2.5.2 Processing Incoming Replies + * + * if the output buffer is too small, reply with STATUS_BUFFER_TOO_SMALL + * and a outputBufferLength of 0. + * The message should then be retransmitted from the server with a doubled + * buffer size. + */ + if (outputBufferLength > op->outputBufferLength) + { + guac_client_log(svc->client, GUAC_LOG_WARNING, + "IRP warn: expected outputBufferLength %" PRIu32 ", but current limit %" PRIu32 + ", respond with STATUS_BUFFER_TOO_SMALL", + op->outputBufferLength, outputBufferLength); + + *io_status = STATUS_BUFFER_TOO_SMALL; + result = *io_status; + outputBufferLength = 0; + objectBufferLength = 0; + } + + /* Device Control Response */ + Stream_Write_UINT32(op->out, (UINT32)outputBufferLength); /* OutputBufferLength (4 bytes) */ + smartcard_pack_common_type_header(op->out); /* CommonTypeHeader (8 bytes) */ + smartcard_pack_private_type_header( + op->out, (UINT32)objectBufferLength); /* PrivateTypeHeader (8 bytes) */ + Stream_Write_INT32(op->out, result); /* Result (4 bytes) */ + Stream_SetPosition(op->out, Stream_Length(op->out)); + + return SCARD_S_SUCCESS; +} diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-call.h b/src/protocols/rdp/channels/rdpdr/smartcard-call.h new file mode 100644 index 0000000000..e6649149a4 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-call.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smart Card Structure Packing + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "channels/rdpdr/rdpdr-smartcard.h" +#include "channels/rdpdr/remote-smartcard.h" + +#include + +typedef struct s_scard_call_context +{ + wHashTable* rgSCardContextList; + RemoteSmartcard* smartcard; + void* userdata; + wLinkedList* names; +} scard_call_context; + +LONG guac_rdpdr_smartcard_irp_device_control_call(guac_rdp_common_svc* svc, + scard_call_context* ctx, + guac_rdpdr_iorequest* request, + guac_rdp_scard_operation* op, + NTSTATUS* io_status); diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-operations.c b/src/protocols/rdp/channels/rdpdr/smartcard-operations.c new file mode 100644 index 0000000000..60391cffd1 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-operations.c @@ -0,0 +1,387 @@ +#include "channels/rdpdr/smartcard-operations.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/smartcard-pack.h" + +#include +#include "channels/rdpdr/rdpdr.h" + +#include +#include + +static void free_reader_states_a(LPSCARD_READERSTATEA rgReaderStates, UINT32 cReaders) +{ + for (UINT32 x = 0; x < cReaders; x++) + { + SCARD_READERSTATEA* state = &rgReaderStates[x]; + free(state->szReader); + } + + free(rgReaderStates); +} + +static void free_reader_states_w(LPSCARD_READERSTATEW rgReaderStates, UINT32 cReaders) +{ + for (UINT32 x = 0; x < cReaders; x++) + { + SCARD_READERSTATEW* state = &rgReaderStates[x]; + free(state->szReader); + } + + free(rgReaderStates); +} + +void smartcard_operation_free(guac_rdp_scard_operation* op, BOOL allocated) +{ + if (!op) + return; + switch (op->ioControlCode) + { + case SCARD_IOCTL_CANCEL: + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + case SCARD_IOCTL_RELEASETARTEDEVENT: + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_ESTABLISHCONTEXT: + case SCARD_IOCTL_RELEASECONTEXT: + case SCARD_IOCTL_ISVALIDCONTEXT: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_GETTRANSMITCOUNT: + break; + + case SCARD_IOCTL_LOCATECARDSA: + { + LocateCardsA_Call* call = &op->call.locateCardsA; + free(call->mszCards); + + free_reader_states_a(call->rgReaderStates, call->cReaders); + } + break; + case SCARD_IOCTL_LOCATECARDSW: + { + LocateCardsW_Call* call = &op->call.locateCardsW; + free(call->mszCards); + + free_reader_states_w(call->rgReaderStates, call->cReaders); + } + break; + + case SCARD_IOCTL_LOCATECARDSBYATRA: + { + LocateCardsByATRA_Call* call = &op->call.locateCardsByATRA; + + free_reader_states_a(call->rgReaderStates, call->cReaders); + } + break; + case SCARD_IOCTL_LOCATECARDSBYATRW: + { + LocateCardsByATRW_Call* call = &op->call.locateCardsByATRW; + free_reader_states_w(call->rgReaderStates, call->cReaders); + } + break; + case SCARD_IOCTL_FORGETREADERA: + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + case SCARD_IOCTL_FORGETREADERGROUPA: + { + ContextAndStringA_Call* call = &op->call.contextAndStringA; + free(call->sz); + } + break; + + case SCARD_IOCTL_FORGETREADERW: + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + case SCARD_IOCTL_FORGETREADERGROUPW: + { + ContextAndStringW_Call* call = &op->call.contextAndStringW; + free(call->sz); + } + break; + + case SCARD_IOCTL_INTRODUCEREADERA: + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + case SCARD_IOCTL_ADDREADERTOGROUPA: + + { + ContextAndTwoStringA_Call* call = &op->call.contextAndTwoStringA; + free(call->sz1); + free(call->sz2); + } + break; + + case SCARD_IOCTL_INTRODUCEREADERW: + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + case SCARD_IOCTL_ADDREADERTOGROUPW: + + { + ContextAndTwoStringW_Call* call = &op->call.contextAndTwoStringW; + free(call->sz1); + free(call->sz2); + } + break; + + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + { + ListReaders_Call* call = &op->call.listReaders; + free(call->mszGroups); + } + break; + case SCARD_IOCTL_GETSTATUSCHANGEA: + { + GetStatusChangeA_Call* call = &op->call.getStatusChangeA; + free_reader_states_a(call->rgReaderStates, call->cReaders); + } + break; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + { + GetStatusChangeW_Call* call = &op->call.getStatusChangeW; + free_reader_states_w(call->rgReaderStates, call->cReaders); + } + break; + case SCARD_IOCTL_GETREADERICON: + { + GetReaderIcon_Call* call = &op->call.getReaderIcon; + free(call->szReaderName); + } + break; + case SCARD_IOCTL_GETDEVICETYPEID: + { + GetDeviceTypeId_Call* call = &op->call.getDeviceTypeId; + free(call->szReaderName); + } + break; + case SCARD_IOCTL_CONNECTA: + { + ConnectA_Call* call = &op->call.connectA; + free(call->szReader); + } + break; + case SCARD_IOCTL_CONNECTW: + { + ConnectW_Call* call = &op->call.connectW; + free(call->szReader); + } + break; + case SCARD_IOCTL_SETATTRIB: + free(op->call.setAttrib.pbAttr); + break; + case SCARD_IOCTL_TRANSMIT: + { + Transmit_Call* call = &op->call.transmit; + free(call->pbSendBuffer); + free(call->pioSendPci); + free(call->pioRecvPci); + } + break; + case SCARD_IOCTL_CONTROL: + { + Control_Call* call = &op->call.control; + free(call->pvInBuffer); + } + break; + case SCARD_IOCTL_READCACHEA: + { + ReadCacheA_Call* call = &op->call.readCacheA; + free(call->szLookupName); + free(call->Common.CardIdentifier); + } + break; + case SCARD_IOCTL_READCACHEW: + { + ReadCacheW_Call* call = &op->call.readCacheW; + free(call->szLookupName); + free(call->Common.CardIdentifier); + } + break; + case SCARD_IOCTL_WRITECACHEA: + { + WriteCacheA_Call* call = &op->call.writeCacheA; + free(call->szLookupName); + free(call->Common.CardIdentifier); + free(call->Common.pbData); + } + break; + case SCARD_IOCTL_WRITECACHEW: + { + WriteCacheW_Call* call = &op->call.writeCacheW; + free(call->szLookupName); + free(call->Common.CardIdentifier); + free(call->Common.pbData); + } + break; + default: + break; + } + + { + guac_rdp_scard_operation empty = { 0 }; + *op = empty; + } + + if (allocated) + free(op); +} + +static LONG smartcard_EstablishContext_Decode(wStream* stream, guac_rdp_scard_operation* op) { + LONG status = 0; + + status = smartcard_unpack_establish_context_call(stream, &op->call.establishContext); + if (status != SCARD_S_SUCCESS) + { + guac_client_log(op->client, GUAC_LOG_ERROR, "smartcard_EstablishContext_Decode: error."); + return status; + } + + return SCARD_S_SUCCESS; +} + +static LONG smartcard_AccessStartedEvent_Decode(wStream* stream, guac_rdp_scard_operation* op) { + INT32 unused = 0; + + if (Stream_GetRemainingLength(stream) < 4) { + guac_client_log(op->client, GUAC_LOG_ERROR, + "smartcard_AccessStartedEvent_Decode: stream too short."); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Read_INT32(stream, unused); // This value is unused per FreeRDP + (void)unused; + + return SCARD_S_SUCCESS; +} + +static LONG smartcard_ListReadersW_Decode(wStream* stream, guac_rdp_scard_operation* op) +{ + LONG status = 0; + + WINPR_ASSERT(stream); + WINPR_ASSERT(op); + + status = smartcard_unpack_list_readers_call(stream, &op->call.listReaders, TRUE); + + return status; +} + +static LONG smartcard_GetDeviceTypeId_Decode(wStream* s, guac_rdp_scard_operation* op) +{ + LONG status = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(op); + + status = smartcard_unpack_get_device_type_id_call(s, &op->call.getDeviceTypeId); + + return status; +} + +static LONG smartcard_GetStatusChangeW_Decode(wStream* s, guac_rdp_scard_operation* operation) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(operation); + + return smartcard_unpack_get_status_change_w_call(s, &operation->call.getStatusChangeW); +} + +static LONG smartcard_ReleaseContext_Decode(wStream* s, guac_rdp_scard_operation* operation) +{ + LONG status = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(operation); + + status = smartcard_unpack_context_call(s, &operation->call.context, "ReleaseContext"); + if (status != SCARD_S_SUCCESS) { + guac_client_log(operation->client, GUAC_LOG_ERROR, "smartcard_ReleaseContext_Decode-unpack: invalid status."); + } + + return status; +} + +LONG guac_rdpdr_smartcard_irp_device_control_decode(wStream* input_stream, + UINT32 CompletionId, + UINT32 FileId, + guac_rdp_scard_operation* operation) { + UINT32 output_len = 0; + UINT32 input_len = 0; + UINT32 ioctl_code = 0; + LONG status = 0; + + if (Stream_GetRemainingLength(input_stream) < 32) { + guac_client_log(operation->client, GUAC_LOG_ERROR, "Smartcard IOCTL: stream too short."); + return STATUS_INVALID_PARAMETER; + } + + Stream_Read_UINT32(input_stream, output_len); + Stream_Read_UINT32(input_stream, input_len); + Stream_Read_UINT32(input_stream, ioctl_code); + Stream_Seek(input_stream, 20); // padding + + operation->ioControlCode = ioctl_code; + operation->outputBufferLength = output_len; + + if (Stream_Length(input_stream) != (Stream_GetPosition(input_stream) + input_len)) { + guac_client_log(operation->client, GUAC_LOG_WARNING, + "InputBufferLength mismatch: Actual: %" PRIuz " Expected: %" PRIuz "", Stream_Length(input_stream), Stream_GetPosition(input_stream) + input_len); + return STATUS_INVALID_PARAMETER; + } + + // guac_client_log(operation->client, GUAC_LOG_DEBUG, "control_decode: Smartcard IOCTL request: 0x%08X", ioctl_code); + + if ((ioctl_code != SCARD_IOCTL_ACCESSSTARTEDEVENT) && + (ioctl_code != SCARD_IOCTL_RELEASETARTEDEVENT)) { + + status = guac_rdpdr_scard_unpack_common_type_header(input_stream, operation->client); + if (status != SCARD_S_SUCCESS) + return status; + + status = guac_rdpdr_scard_unpack_private_type_header(input_stream, operation->client); + if (status != SCARD_S_SUCCESS) + return status; + } + + // Dispatch decode based on IOCTL + switch (ioctl_code) { + + case SCARD_IOCTL_ESTABLISHCONTEXT: + guac_client_log(operation->client, GUAC_LOG_INFO, "smartcard_EstablishContext_Decode"); + status = smartcard_EstablishContext_Decode(input_stream, operation); + break; + + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + guac_client_log(operation->client, GUAC_LOG_INFO, "smartcard_AccessStartedEvent_Decode"); + status = smartcard_AccessStartedEvent_Decode(input_stream, operation); + break; + + case SCARD_IOCTL_LISTREADERSW: + guac_client_log(operation->client, GUAC_LOG_INFO, "smartcard_ListReadersW_Decode"); + status = smartcard_ListReadersW_Decode(input_stream, operation); + break; + + case SCARD_IOCTL_GETDEVICETYPEID: + status = smartcard_GetDeviceTypeId_Decode(input_stream, operation); + break; + + case SCARD_IOCTL_GETSTATUSCHANGEW: + status = smartcard_GetStatusChangeW_Decode(input_stream, operation); + break; + + case SCARD_IOCTL_RELEASECONTEXT: + status = smartcard_ReleaseContext_Decode(input_stream, operation); + break; + + default: + guac_client_log(operation->client, GUAC_LOG_WARNING, + "Smartcard IOCTL: Unsupported code 0x%08X, %s", ioctl_code, scard_get_ioctl_string(operation->ioControlCode, true)); + status = STATUS_NOT_IMPLEMENTED; + break; + } + + return status; +} diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-operations.h b/src/protocols/rdp/channels/rdpdr/smartcard-operations.h new file mode 100644 index 0000000000..41ae236fd6 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-operations.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smart Card Structure Packing + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "channels/rdpdr/rdpdr-smartcard.h" + +#include + +void smartcard_operation_free(guac_rdp_scard_operation* op, BOOL allocated); + +LONG guac_rdpdr_smartcard_irp_device_control_decode(wStream* input_stream, + UINT32 CompletionId, + UINT32 FileId, + guac_rdp_scard_operation* operation); diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-pack.c b/src/protocols/rdp/channels/rdpdr/smartcard-pack.c new file mode 100644 index 0000000000..6606bb60df --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-pack.c @@ -0,0 +1,1055 @@ +#include "channels/rdpdr/smartcard-pack.h" +#include "channels/rdpdr/rdpdr.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/msz-unicode.h" + +#include +#include + +#include + +#define SCARD_TAG FREERDP_TAG("scard.pack") + +static const DWORD g_LogLevel = WLOG_ERROR; + +typedef enum +{ + NDR_PTR_FULL, + NDR_PTR_SIMPLE, + NDR_PTR_FIXED +} ndr_ptr_t; + +static wLog* scard_log(void) +{ + static wLog* log = NULL; + if (!log) + log = WLog_Get(SCARD_TAG); + return log; +} + +static BOOL Stream_CheckAndLogRequiredLengthWLog(void* log, wStream* s, int size) +{ + if (!s) + { + WLog_Print((wLog*)log, WLOG_ERROR, "Stream is NULL"); + return FALSE; + } + + if (Stream_GetRemainingLength(s) < (size_t)size) + { + WLog_Print((wLog*)log, WLOG_ERROR, + "Stream too short: needed %d bytes, but only %zu available", + size, Stream_GetRemainingLength(s)); + return FALSE; + } + + return TRUE; +} + +static BOOL Stream_CheckAndLogRequiredLengthOfSizeWLog(wLog* log, wStream* s, size_t len, size_t elementSize) { + return TRUE; +} + +#define smartcard_unpack_redir_scard_context(log, s, context, index, ndr) \ + smartcard_unpack_redir_scard_context_((log), (s), (context), (index), (ndr), __FILE__, \ + __func__, __LINE__) + +static LONG smartcard_unpack_redir_scard_context_(wLog* log, wStream* s, + REDIR_SCARDCONTEXT* context, UINT32* index, + UINT32* ppbContextNdrPtr, const char* file, + const char* function, size_t line); + +#define smartcard_context_supported(log, size) \ + smartcard_context_supported_((log), (size), __FILE__, __func__, __LINE__) +static LONG smartcard_context_supported_(wLog* log, uint32_t size, const char* file, + const char* fkt, size_t line) +{ + switch (size) + { + case 0: + case 4: + case 8: + return SCARD_S_SUCCESS; + default: + { + const uint32_t level = WLOG_WARN; + if (WLog_IsLevelActive(log, level)) + { + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, + "REDIR_SCARDCONTEXT length is not 0, 4 or 8: %" PRIu32 "", size); + } + return STATUS_INVALID_PARAMETER; + } + } +} + +/* Reads a NDR pointer and checks if the value read has the expected relative + * addressing */ +#define smartcard_ndr_pointer_read(log, s, index, ptr) \ + smartcard_ndr_pointer_read_((log), (s), (index), (ptr), __FILE__, __func__, __LINE__) +static BOOL smartcard_ndr_pointer_read_(wLog* log, wStream* s, UINT32* index, UINT32* ptr, + const char* file, const char* fkt, size_t line) +{ + const UINT32 expect = 0x20000 + (*index) * 4; + UINT32 ndrPtr = 0; + WINPR_UNUSED(file); + if (!s) + return FALSE; + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */ + + if (ptr) + *ptr = ndrPtr; + if (expect != ndrPtr) + { + /* Allow NULL pointer if we read the result */ + if (ptr && (ndrPtr == 0)) + return TRUE; + WLog_Print(log, WLOG_WARN, + "[%s:%" PRIuz "] Read context pointer 0x%08" PRIx32 ", expected 0x%08" PRIx32, + fkt, line, ndrPtr, expect); + return FALSE; + } + + (*index) = (*index) + 1; + return TRUE; +} + +static BOOL smartcard_ndr_pointer_write(wStream* s, UINT32* index, DWORD length) +{ + const UINT32 ndrPtr = 0x20000 + (*index) * 4; + + if (!s) + return FALSE; + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (length > 0) + { + Stream_Write_UINT32(s, ndrPtr); /* mszGroupsNdrPtr (4 bytes) */ + (*index) = (*index) + 1; + } + else + Stream_Write_UINT32(s, 0); + return TRUE; +} + +static LONG smartcard_ndr_write(wStream* s, const BYTE* data, UINT32 size, UINT32 elementSize, + ndr_ptr_t type) +{ + const UINT32 offset = 0; + const UINT32 len = size; + const UINT32 dataLen = size * elementSize; + size_t required = 0; + + if (size == 0) + return SCARD_S_SUCCESS; + + switch (type) + { + case NDR_PTR_FULL: + required = 12; + break; + case NDR_PTR_SIMPLE: + required = 4; + break; + case NDR_PTR_FIXED: + required = 0; + break; + default: + return SCARD_E_INVALID_PARAMETER; + } + + if (!Stream_EnsureRemainingCapacity(s, required + dataLen + 4)) + return STATUS_BUFFER_TOO_SMALL; + + switch (type) + { + case NDR_PTR_FULL: + Stream_Write_UINT32(s, len); + Stream_Write_UINT32(s, offset); + Stream_Write_UINT32(s, len); + break; + case NDR_PTR_SIMPLE: + Stream_Write_UINT32(s, len); + break; + case NDR_PTR_FIXED: + break; + default: + return SCARD_E_INVALID_PARAMETER; + } + + if (data) + Stream_Write(s, data, dataLen); + else + Stream_Zero(s, dataLen); + return smartcard_pack_write_size_align(s, len, 4); +} + +static LONG smartcard_ndr_read(wLog* log, wStream* s, BYTE** data, size_t min, size_t elementSize, + ndr_ptr_t type) +{ + size_t len = 0; + size_t offset = 0; + size_t len2 = 0; + void* r = NULL; + size_t required = 0; + + *data = NULL; + switch (type) + { + case NDR_PTR_FULL: + required = 12; + break; + case NDR_PTR_SIMPLE: + required = 4; + break; + case NDR_PTR_FIXED: + required = min; + break; + default: + return STATUS_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, required)) + return STATUS_BUFFER_TOO_SMALL; + + switch (type) + { + case NDR_PTR_FULL: + Stream_Read_UINT32(s, len); + Stream_Read_UINT32(s, offset); + Stream_Read_UINT32(s, len2); + if (len != offset + len2) + { + WLog_Print(log, WLOG_ERROR, + "Invalid data when reading full NDR pointer: total=%" PRIu32 + ", offset=%" PRIu32 ", remaining=%" PRIu32, + len, offset, len2); + return STATUS_BUFFER_TOO_SMALL; + } + break; + case NDR_PTR_SIMPLE: + Stream_Read_UINT32(s, len); + + if ((len != min) && (min > 0)) + { + WLog_Print(log, WLOG_ERROR, + "Invalid data when reading simple NDR pointer: total=%" PRIu32 + ", expected=%" PRIu32, + len, min); + return STATUS_BUFFER_TOO_SMALL; + } + break; + case NDR_PTR_FIXED: + len = (UINT32)min; + break; + default: + return STATUS_INVALID_PARAMETER; + } + + if (min > len) + { + WLog_Print(log, WLOG_ERROR, + "Invalid length read from NDR pointer, minimum %" PRIu32 ", got %" PRIu32, min, + len); + return STATUS_DATA_ERROR; + } + + if (len > SIZE_MAX / 2) + return STATUS_BUFFER_TOO_SMALL; + + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(log, s, len, elementSize)) + return STATUS_BUFFER_TOO_SMALL; + + len *= elementSize; + + /* Ensure proper '\0' termination for all kinds of unicode strings + * as we do not know if the data from the wire contains one. + */ + r = calloc(len + sizeof(WCHAR), sizeof(CHAR)); + if (!r) + return SCARD_E_NO_MEMORY; + Stream_Read(s, r, len); + smartcard_unpack_read_size_align(s, len, 4); + *data = r; + return STATUS_SUCCESS; +} + +static LONG smartcard_ndr_read_w(wLog* log, wStream* s, WCHAR** data, ndr_ptr_t type) +{ + union + { + WCHAR** ppc; + BYTE** ppv; + } u; + u.ppc = data; + return smartcard_ndr_read(log, s, u.ppv, 0, sizeof(WCHAR), type); +} + +static char* smartcard_convert_string_list(const void* in, size_t bytes, BOOL unicode) +{ + size_t length = 0; + union + { + const void* pv; + const char* sz; + const WCHAR* wz; + } string; + char* mszA = NULL; + + string.pv = in; + + if (bytes < 1) + return NULL; + + if (in == NULL) + return NULL; + + if (unicode) + { + mszA = ConvertMszWCharNToUtf8Alloc(string.wz, bytes / sizeof(WCHAR), &length); + if (!mszA) + return NULL; + } + else + { + mszA = (char*)calloc(bytes, sizeof(char)); + if (!mszA) + return NULL; + CopyMemory(mszA, string.sz, bytes - 1); + length = bytes; + } + + if (length < 1) + { + free(mszA); + return NULL; + } + for (size_t index = 0; index < length - 1; index++) + { + if (mszA[index] == '\0') + mszA[index] = ','; + } + + return mszA; +} + +static void smartcard_trace_list_readers_call(wLog* log, const ListReaders_Call* call, BOOL unicode) +{ + WINPR_ASSERT(call); + return; + + // if (!WLog_IsLevelActive(log, g_LogLevel)) + // return; + + // char* mszGroupsA = smartcard_convert_string_list(call->mszGroups, call->cBytes, unicode); + + // WLog_Print(log, g_LogLevel, "ListReaders%s_Call {", unicode ? "W" : "A"); + // smartcard_log_context(log, &call->handles.hContext); + + // WLog_Print(log, g_LogLevel, + // "cBytes: %" PRIu32 " mszGroups: %s fmszReadersIsNULL: %" PRId32 + // " cchReaders: 0x%08" PRIX32 "", + // call->cBytes, mszGroupsA, call->fmszReadersIsNULL, call->cchReaders); + // WLog_Print(log, g_LogLevel, "}"); + + // free(mszGroupsA); +} + +static void smartcard_trace_list_readers_return(wLog* log, const ListReaders_Return* ret, + BOOL unicode) +{ + WINPR_ASSERT(ret); + + WLog_Print(log, g_LogLevel, "ListReaders%s_Return {", unicode ? "W" : "A"); + // WLog_Print(log, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", + // SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + + if (ret->ReturnCode != SCARD_S_SUCCESS) + { + WLog_Print(log, g_LogLevel, "}"); + return; + } + + WLog_Print(log, g_LogLevel, "Converting string..."); + char* mszA = smartcard_convert_string_list(ret->msz, ret->cBytes, unicode); + WLog_Print(log, g_LogLevel, "Converted!"); + + WLog_Print(log, g_LogLevel, " cBytes: %" PRIu32 " msz: %s", ret->cBytes, mszA); + WLog_Print(log, g_LogLevel, "}"); + free(mszA); +} + +static void smartcard_trace_context_and_string_call_w(wLog* log, const char* name, + const REDIR_SCARDCONTEXT* phContext, + const WCHAR* sz) +{ + char tmp[1024] = { 0 }; + + // if (!WLog_IsLevelActive(log, g_LogLevel)) + // return; + + if (sz) + (void)ConvertWCharToUtf8(sz, tmp, ARRAYSIZE(tmp)); + + WLog_Print(log, WLOG_WARN, "%s {", name); + // smartcard_log_context(log, phContext); + WLog_Print(log, WLOG_WARN, " sz=%s", tmp); + WLog_Print(log, WLOG_WARN, "}"); +} + +static void smartcard_trace_device_type_id_return(wLog* log, const GetDeviceTypeId_Return* ret) +{ + WINPR_ASSERT(ret); + + // if (!WLog_IsLevelActive(log, g_LogLevel)) + // return; + + WLog_Print(log, WLOG_WARN, "GetDeviceTypeId_Return {"); + WLog_Print(log, WLOG_WARN, " ReturnCode: %s (0x%08" PRIX32 ")", + SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); + WLog_Print(log, WLOG_WARN, " dwDeviceId=%08" PRIx32, ret->dwDeviceId); + + WLog_Print(log, WLOG_WARN, "}"); +} + +static LONG smartcard_unpack_common_context_and_string_w(wLog* log, wStream* s, + REDIR_SCARDCONTEXT* phContext, + WCHAR** pszReaderName) +{ + UINT32 index = 0; + UINT32 pbContextNdrPtr = 0; + + LONG status = smartcard_unpack_redir_scard_context(log, s, phContext, &index, &pbContextNdrPtr); + if (status != SCARD_S_SUCCESS) + return status; + + if (!smartcard_ndr_pointer_read(log, s, &index, NULL)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(log, s, pbContextNdrPtr, phContext); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_ndr_read_w(log, s, pszReaderName, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + return status; + + smartcard_trace_context_and_string_call_w(log, __func__, phContext, *pszReaderName); + return SCARD_S_SUCCESS; +} + +static LONG smartcard_unpack_reader_state_w(wLog* log, wStream* s, LPSCARD_READERSTATEW* ppcReaders, + UINT32 cReaders, UINT32* ptrIndex) +{ + LONG status = SCARD_E_NO_MEMORY; + + WINPR_ASSERT(ppcReaders || (cReaders == 0)); + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4)) + return status; + + UINT32 len; + Stream_Read_UINT32(s, len); + if (len != cReaders) + { + WLog_Print(log, WLOG_ERROR, "Count mismatch when reading LPSCARD_READERSTATEW"); + return status; + } + + LPSCARD_READERSTATEW rgReaderStates = + (LPSCARD_READERSTATEW)calloc(cReaders, sizeof(SCARD_READERSTATEW)); + BOOL* states = calloc(cReaders, sizeof(BOOL)); + + if (!rgReaderStates || !states) + goto fail; + + status = ERROR_INVALID_DATA; + for (UINT32 index = 0; index < cReaders; index++) + { + UINT32 ptr = UINT32_MAX; + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 52)) + goto fail; + + if (!smartcard_ndr_pointer_read(log, s, ptrIndex, &ptr)) + { + if (ptr != 0) + goto fail; + } + /* Ignore NULL length strings */ + states[index] = ptr != 0; + Stream_Read_UINT32(s, readerState->dwCurrentState); /* dwCurrentState (4 bytes) */ + Stream_Read_UINT32(s, readerState->dwEventState); /* dwEventState (4 bytes) */ + Stream_Read_UINT32(s, readerState->cbAtr); /* cbAtr (4 bytes) */ + Stream_Read(s, readerState->rgbAtr, 36); /* rgbAtr [0..36] (36 bytes) */ + } + + for (UINT32 index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + + /* Skip NULL pointers */ + if (!states[index]) + continue; + + status = smartcard_ndr_read_w(log, s, &readerState->szReader, NDR_PTR_FULL); + if (status != SCARD_S_SUCCESS) + goto fail; + } + + *ppcReaders = rgReaderStates; + free(states); + return SCARD_S_SUCCESS; +fail: + if (rgReaderStates) + { + for (UINT32 index = 0; index < cReaders; index++) + { + LPSCARD_READERSTATEW readerState = &rgReaderStates[index]; + free(readerState->szReader); + } + } + free(rgReaderStates); + free(states); + return status; +} + +static LONG smartcard_ndr_write_state(wStream* s, const ReaderState_Return* data, UINT32 size, + ndr_ptr_t type) +{ + union + { + const ReaderState_Return* reader; + const BYTE* data; + } cnv; + + WINPR_ASSERT(data || (size == 0)); + cnv.reader = data; + return smartcard_ndr_write(s, cnv.data, size, sizeof(ReaderState_Return), type); +} + +// static void smartcard_trace_get_status_change_return(wLog* log, const GetStatusChange_Return* ret, +// BOOL unicode) +// { +// WINPR_ASSERT(ret); + +// WLog_Print(log, g_LogLevel, "GetStatusChange%s_Return {", unicode ? "W" : "A"); +// WLog_Print(log, g_LogLevel, " ReturnCode: %s (0x%08" PRIX32 ")", +// SCardGetErrorString(ret->ReturnCode), ret->ReturnCode); +// WLog_Print(log, g_LogLevel, " cReaders: %" PRIu32 "", ret->cReaders); + +// dump_reader_states_return(log, ret->rgReaderStates, ret->cReaders); + +// if (!ret->rgReaderStates && (ret->cReaders > 0)) +// { +// WLog_Print(log, g_LogLevel, " [INVALID STATE] rgReaderStates=NULL, cReaders=%" PRIu32, +// ret->cReaders); +// } +// else if (ret->ReturnCode != SCARD_S_SUCCESS) +// { +// WLog_Print(log, g_LogLevel, " [INVALID RETURN] rgReaderStates, cReaders=%" PRIu32, +// ret->cReaders); +// } +// else +// { +// for (UINT32 index = 0; index < ret->cReaders; index++) +// { +// char buffer[1024] = { 0 }; +// const ReaderState_Return* rgReaderState = &(ret->rgReaderStates[index]); +// char* szCurrentState = SCardGetReaderStateString(rgReaderState->dwCurrentState); +// char* szEventState = SCardGetReaderStateString(rgReaderState->dwEventState); +// WLog_Print(log, g_LogLevel, " [%" PRIu32 "]: dwCurrentState: %s (0x%08" PRIX32 ")", +// index, szCurrentState, rgReaderState->dwCurrentState); +// WLog_Print(log, g_LogLevel, " [%" PRIu32 "]: dwEventState: %s (0x%08" PRIX32 ")", +// index, szEventState, rgReaderState->dwEventState); +// WLog_Print(log, g_LogLevel, " [%" PRIu32 "]: cbAtr: %" PRIu32 " rgbAtr: %s", index, +// rgReaderState->cbAtr, +// smartcard_array_dump(rgReaderState->rgbAtr, rgReaderState->cbAtr, buffer, +// sizeof(buffer))); +// free(szCurrentState); +// free(szEventState); +// } +// } + +// WLog_Print(log, g_LogLevel, "}"); +// } + +LONG smartcard_unpack_read_size_align(wStream* s, size_t size, UINT32 alignment) +{ + size_t pad = 0; + + pad = size; + size = (size + alignment - 1) & ~(alignment - 1); + pad = size - pad; + + if (pad) + Stream_Seek(s, pad); + + return (LONG)pad; +} + +LONG smartcard_unpack_redir_scard_context_(wLog* log, wStream* s, REDIR_SCARDCONTEXT* context, + UINT32* index, UINT32* ppbContextNdrPtr, + const char* file, const char* function, size_t line) +{ + UINT32 pbContextNdrPtr = 0; + + // WLog_Print(log, WLOG_WARN, "smartcard_unpack_redir_scard_context_: start"); + + WINPR_UNUSED(file); + WINPR_ASSERT(context); + + ZeroMemory(context, sizeof(REDIR_SCARDCONTEXT)); + + const LONG status = smartcard_context_supported_(log, context->cbContext, file, function, line); + if (status != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard context not supported."); + return status; + } + + Stream_Read_UINT32(s, context->cbContext); /* cbContext (4 bytes) */ + // size_t position = Stream_GetPosition(s); + // size_t length = Stream_GetRemainingLength(s); + // size_t remaining = Stream_GetRemainingLength(s); + // WLog_Print(log, WLOG_WARN, "smartcard_unpack_redir_scard_context_: context: %lu Position: %zu, Length: %zu, Remaining: %zu", context->cbContext, position, length, remaining); + + if (!smartcard_ndr_pointer_read_(log, s, index, &pbContextNdrPtr, file, function, line)) { + WLog_Print(log, WLOG_ERROR, "smartcard_ndr_pointer_read_: invalid data."); + return ERROR_INVALID_DATA; + } + + if (((context->cbContext == 0) && pbContextNdrPtr) || + ((context->cbContext != 0) && !pbContextNdrPtr)) + { + WLog_Print(log, WLOG_WARN, + "REDIR_SCARDCONTEXT cbContext (%" PRIu32 ") pbContextNdrPtr (%" PRIu32 + ") inconsistency", + context->cbContext, pbContextNdrPtr); + return STATUS_INVALID_PARAMETER; + } + + *ppbContextNdrPtr = pbContextNdrPtr; + + // WLog_Print(log, WLOG_WARN, "smartcard_unpack_redir_scard_context_: end"); + return SCARD_S_SUCCESS; +} + +LONG guac_rdpdr_scard_unpack_common_type_header(wStream* s, guac_client* client) { + UINT8 version = 0; + UINT8 endianness = 0; + UINT16 commonHeaderLength = 0; + UINT32 filler = 0; + + if (Stream_GetRemainingLength(s) < 8) { + guac_client_log(client, GUAC_LOG_ERROR, + "CommonTypeHeader too short: need 8 bytes."); + return STATUS_BUFFER_TOO_SMALL; + } + + Stream_Read_UINT8(s, version); + Stream_Read_UINT8(s, endianness); + Stream_Read_UINT16(s, commonHeaderLength); + Stream_Read_UINT32(s, filler); + + if (version != 1) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unsupported CommonTypeHeader version: %u", version); + return STATUS_INVALID_PARAMETER; + } + + if (endianness != 0x10) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unsupported CommonTypeHeader endianness: 0x%02X", endianness); + return STATUS_INVALID_PARAMETER; + } + + if (commonHeaderLength != 8) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unexpected CommonHeaderLength: %u", commonHeaderLength); + return STATUS_INVALID_PARAMETER; + } + + if (filler != 0xCCCCCCCC) { + guac_client_log(client, GUAC_LOG_WARNING, + "Unexpected filler value: 0x%08" PRIX32, filler); + return STATUS_INVALID_PARAMETER; + } + + return SCARD_S_SUCCESS; +} + +LONG guac_rdpdr_scard_unpack_private_type_header(wStream* s, guac_client* client) { + wLog* log = scard_log(); + UINT32 filler = 0; + UINT32 objectBufferLength = 0; + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8)) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */ + Stream_Read_UINT32(s, filler); /* Filler (4 bytes), should be 0x00000000 */ + + if (filler != 0x00000000) + { + WLog_Print(log, WLOG_WARN, "Unexpected PrivateTypeHeader Filler 0x%08" PRIX32 "", filler); + return STATUS_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, objectBufferLength)) + return STATUS_INVALID_PARAMETER; + + return SCARD_S_SUCCESS; +} + + +LONG smartcard_pack_write_size_align( wStream* s, size_t size, UINT32 alignment) +{ + size_t pad = 0; + + pad = size; + size = (size + alignment - 1) & ~(alignment - 1); + pad = size - pad; + + if (pad) + { + if (!Stream_EnsureRemainingCapacity(s, pad)) + { + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Zero(s, pad); + } + + return SCARD_S_SUCCESS; +} + +void smartcard_pack_common_type_header(wStream* s) +{ + Stream_Write_UINT8(s, 1); /* Version (1 byte) */ + Stream_Write_UINT8(s, 0x10); /* Endianness (1 byte) */ + Stream_Write_UINT16(s, 8); /* CommonHeaderLength (2 bytes) */ + Stream_Write_UINT32(s, 0xCCCCCCCC); /* Filler (4 bytes), should be 0xCCCCCCCC */ +} + +void smartcard_pack_private_type_header(wStream* s, UINT32 objectBufferLength) +{ + Stream_Write_UINT32(s, objectBufferLength); /* ObjectBufferLength (4 bytes) */ + Stream_Write_UINT32(s, 0x00000000); /* Filler (4 bytes), should be 0x00000000 */ +} + +LONG smartcard_unpack_establish_context_call(wStream* s, EstablishContext_Call* call) +{ + wLog* log = scard_log(); + size_t position = Stream_GetPosition(s); + size_t length = Stream_GetRemainingLength(s); + size_t remaining = Stream_GetRemainingLength(s); + WLog_Print(log, WLOG_WARN, "smartcard_unpack_establish_context_call: Position: %zu, Length: %zu, Remaining: %zu", position, length, remaining); + + if (Stream_GetRemainingLength(s) < 4) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, call->dwScope); /* dwScope (4 bytes) */ + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_context(wStream* s, RemoteSmartcard* smartcard, DWORD* index) +{ + wLog* log = scard_log(); + + const UINT32 pbContextNdrPtr = 0x00020000 + *index * 4; + + WINPR_ASSERT(smartcard->context); + + if (smartcard->context->cbContext != 0) + { + Stream_Write_UINT32(s, smartcard->context->cbContext); /* cbContext (4 bytes) */ + WLog_Print(log, WLOG_WARN, "packed cbContext: %lu", smartcard->context->cbContext); + Stream_Write_UINT32(s, pbContextNdrPtr); /* pbContextNdrPtr (4 bytes) */ + WLog_Print(log, WLOG_WARN, "packed pbContextNdrPtr: %zu", pbContextNdrPtr); + *index = *index + 1; + } + else { + Stream_Zero(s, 8); + } + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_redir_scard_context_ref(wStream* s, RemoteSmartcard* smartcard) +{ + wLog* log = scard_log(); + WINPR_ASSERT(smartcard->context); + WLog_Print(log, WLOG_WARN, "smartcard_pack_redir_scard_context_ref: packed cbContext: %lu", smartcard->context->cbContext); + Stream_Write_UINT32(s, smartcard->context->cbContext); /* Length (4 bytes) */ + + if (smartcard->context->cbContext) + { + WLog_Print(log, WLOG_WARN, "smartcard_pack_redir_scard_context_ref: writing pbContext"); + Stream_Write(s, &(smartcard->context->pbContext), smartcard->context->cbContext); + } + + return SCARD_S_SUCCESS; +} + +LONG smartcard_unpack_redir_scard_context_ref(wLog* log, wStream* s, UINT32 pbContextNdrPtr, REDIR_SCARDCONTEXT* context) +{ + UINT32 length = 0; + + WINPR_ASSERT(context); + if (context->cbContext == 0) + return SCARD_S_SUCCESS; + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4)) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length != context->cbContext) + { + WLog_Print(log, WLOG_WARN, + "REDIR_SCARDCONTEXT length (%" PRIu32 ") cbContext (%" PRIu32 ") mismatch", + length, context->cbContext); + return STATUS_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, context->cbContext)) + return STATUS_BUFFER_TOO_SMALL; + + if (context->cbContext) + Stream_Read(s, &(context->pbContext), context->cbContext); + else + ZeroMemory(&(context->pbContext), sizeof(context->pbContext)); + + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_establish_context_return(wStream* s, RemoteSmartcard* smartcard) +{ + LONG status = 0; + DWORD index = 0; + + wLog* log = scard_log(); + + status = smartcard_pack_redir_scard_context(s, smartcard, &index); + if (status != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard_pack_redir_scard_context: failed to pack context!"); + return status; + } + + return smartcard_pack_redir_scard_context_ref(s, smartcard); +} + +LONG smartcard_unpack_list_readers_call(wStream* s, ListReaders_Call* call, BOOL unicode) +{ + UINT32 index = 0; + UINT32 mszGroupsNdrPtr = 0; + UINT32 pbContextNdrPtr = 0; + wLog* log = scard_log(); + + WINPR_ASSERT(call); + call->mszGroups = NULL; + + LONG status = smartcard_unpack_redir_scard_context(log, s, &(call->handles.hContext), &index, + &pbContextNdrPtr); + + if (status != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard_unpack_redir_scard_context failed!"); + return status; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 4)) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, call->cBytes); /* cBytes (4 bytes) */ + if (!smartcard_ndr_pointer_read(log, s, &index, &mszGroupsNdrPtr)) { + WLog_Print(log, WLOG_ERROR, "smartcard_ndr_pointer_read failed!"); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8)) + return STATUS_BUFFER_TOO_SMALL; + Stream_Read_INT32(s, call->fmszReadersIsNULL); /* fmszReadersIsNULL (4 bytes) */ + Stream_Read_UINT32(s, call->cchReaders); /* cchReaders (4 bytes) */ + + status = smartcard_unpack_redir_scard_context_ref(log, s, pbContextNdrPtr, + &(call->handles.hContext)); + if (status != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard_unpack_redir_scard_context_ref failed!"); + return status; + } + + if (mszGroupsNdrPtr) + { + status = smartcard_ndr_read(log, s, &call->mszGroups, call->cBytes, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard_ndr_read failed!"); + return status; + } + } + + smartcard_trace_list_readers_call(log, call, unicode); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_list_readers_return(wStream* s, const ListReaders_Return* ret, BOOL unicode) +{ + WINPR_ASSERT(ret); + wLog* log = scard_log(); + LONG status = 0; + UINT32 index = 0; + UINT32 size = ret->cBytes; + + smartcard_trace_list_readers_return(log, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) { + WLog_Print(log, WLOG_ERROR, "smartcard_trace_list_readers_return returned non 0"); + size = 0; + } + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_Print(log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + WLog_Print(log, WLOG_ERROR, "smartcard_pack_list_readers_return: Size: %d", size); + + Stream_Write_UINT32(s, size); /* cBytes (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, size)) + return SCARD_E_NO_MEMORY; + + if (ret->msz) { + WLog_Print(log, WLOG_ERROR, "smartcard_pack_list_readers_return:MSZ has data :("); + } + else { + WLog_Print(log, WLOG_ERROR, "smartcard_pack_list_readers_return:MSZ has no data :)"); + } + + status = smartcard_ndr_write(s, ret->msz, size, 1, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_get_device_type_id_call(wStream* s, GetDeviceTypeId_Call* call) +{ + WINPR_ASSERT(call); + wLog* log = scard_log(); + return smartcard_unpack_common_context_and_string_w(log, s, &call->handles.hContext, + &call->szReaderName); +} + +LONG smartcard_pack_device_type_id_return(wStream* s, const GetDeviceTypeId_Return* ret) +{ + WINPR_ASSERT(ret); + wLog* log = scard_log(); + smartcard_trace_device_type_id_return(log, ret); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + { + WLog_Print(log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return SCARD_F_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, ret->dwDeviceId); /* cBytes (4 bytes) */ + + return ret->ReturnCode; +} + +LONG smartcard_unpack_get_status_change_w_call(wStream* s, GetStatusChangeW_Call* call) +{ + UINT32 ndrPtr = 0; + UINT32 index = 0; + UINT32 pbContextNdrPtr = 0; + + WINPR_ASSERT(call); + wLog* log = scard_log(); + call->rgReaderStates = NULL; + + LONG status = smartcard_unpack_redir_scard_context(log, s, &(call->handles.hContext), &index, + &pbContextNdrPtr); + if (status != SCARD_S_SUCCESS) + return status; + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8)) + return STATUS_BUFFER_TOO_SMALL; + + Stream_Read_UINT32(s, call->dwTimeOut); /* dwTimeOut (4 bytes) */ + Stream_Read_UINT32(s, call->cReaders); /* cReaders (4 bytes) */ + if (!smartcard_ndr_pointer_read(log, s, &index, &ndrPtr)) + return ERROR_INVALID_DATA; + + status = smartcard_unpack_redir_scard_context_ref(log, s, pbContextNdrPtr, + &(call->handles.hContext)); + if (status != SCARD_S_SUCCESS) + return status; + + if (ndrPtr) + { + status = + smartcard_unpack_reader_state_w(log, s, &call->rgReaderStates, call->cReaders, &index); + if (status != SCARD_S_SUCCESS) + return status; + } + else + { + WLog_Print(log, WLOG_WARN, "ndrPtr=0x%08" PRIx32 ", can not read rgReaderStates", ndrPtr); + return SCARD_E_UNEXPECTED; + } + + // smartcard_trace_get_status_change_w_call(log, call); + return SCARD_S_SUCCESS; +} + +LONG smartcard_pack_get_status_change_return(wStream* s, const GetStatusChange_Return* ret, + BOOL unicode) +{ + WINPR_ASSERT(ret); + + LONG status = 0; + DWORD cReaders = ret->cReaders; + UINT32 index = 0; + + // smartcard_trace_get_status_change_return(log, ret, unicode); + if (ret->ReturnCode != SCARD_S_SUCCESS) + cReaders = 0; + if (cReaders == SCARD_AUTOALLOCATE) + cReaders = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return SCARD_E_NO_MEMORY; + + Stream_Write_UINT32(s, cReaders); /* cReaders (4 bytes) */ + if (!smartcard_ndr_pointer_write(s, &index, cReaders)) + return SCARD_E_NO_MEMORY; + status = smartcard_ndr_write_state(s, ret->rgReaderStates, cReaders, NDR_PTR_SIMPLE); + if (status != SCARD_S_SUCCESS) + return status; + return ret->ReturnCode; +} + +LONG smartcard_unpack_context_call(wStream* s, Context_Call* call, const char* name) +{ + UINT32 index = 0; + UINT32 pbContextNdrPtr = 0; + wLog* log = scard_log(); + + WINPR_ASSERT(call); + LONG status = smartcard_unpack_redir_scard_context(log, s, &(call->handles.hContext), &index, + &pbContextNdrPtr); + if (status != SCARD_S_SUCCESS) + return status; + + status = smartcard_unpack_redir_scard_context_ref(log, s, pbContextNdrPtr, + &(call->handles.hContext)); + if (status != SCARD_S_SUCCESS) + WLog_Print(log, WLOG_ERROR, + "smartcard_unpack_redir_scard_context_ref failed with error %" PRId32 "", + status); + + // smartcard_trace_context_call(log, call, name); + return status; +} diff --git a/src/protocols/rdp/channels/rdpdr/smartcard-pack.h b/src/protocols/rdp/channels/rdpdr/smartcard-pack.h new file mode 100644 index 0000000000..2b3a6799e1 --- /dev/null +++ b/src/protocols/rdp/channels/rdpdr/smartcard-pack.h @@ -0,0 +1,57 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smart Card Structure Packing + * + * Copyright 2025 Armin Novak + * Copyright 2025 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "channels/rdpdr/rdpdr.h" +#include "channels/rdpdr/scard.h" +#include "channels/rdpdr/remote-smartcard.h" + +#include +#include + +#define SMARTCARD_COMMON_TYPE_HEADER_LENGTH 8 +#define SMARTCARD_PRIVATE_TYPE_HEADER_LENGTH 8 + +LONG guac_rdpdr_scard_unpack_common_type_header(wStream* s, guac_client* client); +LONG guac_rdpdr_scard_unpack_private_type_header(wStream* s, guac_client* client); +LONG smartcard_pack_write_size_align(wStream* s, size_t size, UINT32 alignment); +void smartcard_pack_common_type_header(wStream* s); +void smartcard_pack_private_type_header(wStream* s, UINT32 objectBufferLength); + +LONG smartcard_pack_establish_context_return(wStream* s, RemoteSmartcard* smartcard); +LONG smartcard_unpack_establish_context_call(wStream* s, EstablishContext_Call* call); + +LONG smartcard_pack_list_readers_return(wStream* s, const ListReaders_Return* ret, BOOL unicode); +LONG smartcard_unpack_list_readers_call(wStream* s, ListReaders_Call* call, BOOL unicode); + +LONG smartcard_unpack_redir_scard_context_ref(wLog* log, wStream* s, UINT32 pbContextNdrPtr, REDIR_SCARDCONTEXT* context); +LONG smartcard_pack_redir_scard_context(wStream* s, RemoteSmartcard* smartcard, DWORD* index); +LONG smartcard_pack_redir_scard_context_ref(wStream* s, RemoteSmartcard* smartcard); + +LONG smartcard_unpack_read_size_align(wStream* s, size_t size, UINT32 alignment); + +LONG smartcard_unpack_get_device_type_id_call(wStream* s, GetDeviceTypeId_Call* call); +LONG smartcard_pack_device_type_id_return(wStream* s, const GetDeviceTypeId_Return* ret); + +LONG smartcard_unpack_get_status_change_w_call(wStream* s, GetStatusChangeW_Call* call); +LONG smartcard_pack_get_status_change_return(wStream* s, const GetStatusChange_Return* ret, BOOL unicode); + +LONG smartcard_unpack_context_call(wStream* s, Context_Call* call, const char* name);