From 1a302821ec421d44f0725cf4cd3a15c2800ca0da Mon Sep 17 00:00:00 2001 From: Eduardo Beloni Date: Thu, 6 Oct 2011 10:50:00 -0300 Subject: [PATCH 01/10] list: add list_size() operation --- include/freerdp/utils/list.h | 1 + libfreerdp-utils/list.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/freerdp/utils/list.h b/include/freerdp/utils/list.h index 52c0279dc..ac506ef1c 100644 --- a/include/freerdp/utils/list.h +++ b/include/freerdp/utils/list.h @@ -46,5 +46,6 @@ FREERDP_API void* list_dequeue(LIST* list); FREERDP_API void* list_peek(LIST* list); #define list_add(_l, _d) list_enqueue(_l, _d) FREERDP_API void* list_remove(LIST* list, void* data); +FREERDP_API int list_size(LIST* list); #endif /* __LIST_UTILS_H */ diff --git a/libfreerdp-utils/list.c b/libfreerdp-utils/list.c index 06f8bea16..ef93874ae 100644 --- a/libfreerdp-utils/list.c +++ b/libfreerdp-utils/list.c @@ -49,6 +49,7 @@ LIST* list_new(void) LIST* list; list = xnew(LIST); + list->count = 0; return list; } @@ -75,6 +76,7 @@ void list_enqueue(LIST* list, void* data) list->tail->next = item; list->tail = item; } + list->count++; } void* list_dequeue(LIST* list) @@ -93,6 +95,7 @@ void* list_dequeue(LIST* list) data = item->data; xfree(item); + list->count--; } return data; } @@ -121,8 +124,14 @@ void* list_remove(LIST* list, void* data) if (list->tail == item) list->tail = item->prev; xfree(item); + list->count--; } else data = NULL; return data; } + +int list_size(LIST* list) +{ + return list->count; +} From a512574314792fc69386b82b157ceb33af49d283 Mon Sep 17 00:00:00 2001 From: Eduardo Beloni Date: Fri, 7 Oct 2011 10:38:44 -0300 Subject: [PATCH 02/10] list: add list_next() operation --- include/freerdp/utils/list.h | 1 + libfreerdp-utils/list.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/freerdp/utils/list.h b/include/freerdp/utils/list.h index ac506ef1c..de53a100a 100644 --- a/include/freerdp/utils/list.h +++ b/include/freerdp/utils/list.h @@ -44,6 +44,7 @@ FREERDP_API void list_free(LIST* list); FREERDP_API void list_enqueue(LIST* list, void* data); FREERDP_API void* list_dequeue(LIST* list); FREERDP_API void* list_peek(LIST* list); +FREERDP_API void* list_next(LIST* list, void* data); #define list_add(_l, _d) list_enqueue(_l, _d) FREERDP_API void* list_remove(LIST* list, void* data); FREERDP_API int list_size(LIST* list); diff --git a/libfreerdp-utils/list.c b/libfreerdp-utils/list.c index ef93874ae..9f7990bff 100644 --- a/libfreerdp-utils/list.c +++ b/libfreerdp-utils/list.c @@ -108,6 +108,20 @@ void* list_peek(LIST* list) return item ? item->data : NULL; } +void* list_next(LIST* list, void* data) +{ + LIST_ITEM* item; + + item = list_item_find(list, data); + data = NULL; + if (item != NULL) + { + if (item->next != NULL) + data = item->next->data; + } + return data; +} + void* list_remove(LIST* list, void* data) { LIST_ITEM* item; From 34f3cb90f8d2ec52a3b4b9f710eeaac68ed29699 Mon Sep 17 00:00:00 2001 From: Eduardo Beloni Date: Thu, 6 Oct 2011 10:53:49 -0300 Subject: [PATCH 03/10] rdpdr_types: [fix] add include svc_plugin --- channels/rdpdr/rdpdr_types.h | 1 + 1 file changed, 1 insertion(+) diff --git a/channels/rdpdr/rdpdr_types.h b/channels/rdpdr/rdpdr_types.h index 4419edb49..a87dad289 100644 --- a/channels/rdpdr/rdpdr_types.h +++ b/channels/rdpdr/rdpdr_types.h @@ -24,6 +24,7 @@ #include "config.h" #include #include +#include typedef struct _DEVICE DEVICE; typedef struct _IRP IRP; From 5a57532ee049fdb731eb819664f2f434b0b8c8e8 Mon Sep 17 00:00:00 2001 From: Eduardo Beloni Date: Tue, 4 Oct 2011 15:15:15 -0300 Subject: [PATCH 04/10] rdpdr: migrating serial code --- channels/rdpdr/CMakeLists.txt | 1 + channels/rdpdr/serial/CMakeLists.txt | 34 + channels/rdpdr/serial/serial_constants.h | 154 ++++ channels/rdpdr/serial/serial_main.c | 710 +++++++++++++++++ channels/rdpdr/serial/serial_tty.c | 973 +++++++++++++++++++++++ channels/rdpdr/serial/serial_tty.h | 72 ++ 6 files changed, 1944 insertions(+) create mode 100644 channels/rdpdr/serial/CMakeLists.txt create mode 100644 channels/rdpdr/serial/serial_constants.h create mode 100644 channels/rdpdr/serial/serial_main.c create mode 100644 channels/rdpdr/serial/serial_tty.c create mode 100644 channels/rdpdr/serial/serial_tty.h diff --git a/channels/rdpdr/CMakeLists.txt b/channels/rdpdr/CMakeLists.txt index a0085ff52..e79be8e7d 100644 --- a/channels/rdpdr/CMakeLists.txt +++ b/channels/rdpdr/CMakeLists.txt @@ -40,3 +40,4 @@ install(TARGETS rdpdr DESTINATION ${FREERDP_PLUGIN_PATH}) add_subdirectory(disk) add_subdirectory(printer) add_subdirectory(parallel) +add_subdirectory(serial) diff --git a/channels/rdpdr/serial/CMakeLists.txt b/channels/rdpdr/serial/CMakeLists.txt new file mode 100644 index 000000000..40c962a58 --- /dev/null +++ b/channels/rdpdr/serial/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(serial_SRCS + serial_tty.c + serial_tty.h + serial_constants.h + serial_main.c +) + +include_directories(..) + +add_library(serial ${serial_SRCS}) +set_target_properties(serial PROPERTIES PREFIX "") + +target_link_libraries(serial freerdp-utils) + +install(TARGETS serial DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/rdpdr/serial/serial_constants.h b/channels/rdpdr/serial/serial_constants.h new file mode 100644 index 000000000..ac16f9a7b --- /dev/null +++ b/channels/rdpdr/serial/serial_constants.h @@ -0,0 +1,154 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * + * 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 __SERIAL_CONSTANTS_H +#define __SERIAL_CONSTANTS_H + +/* http://www.codeproject.com/KB/system/chaiyasit_t.aspx */ +#define SERIAL_TIMEOUT_MAX 4294967295u + +/* DR_CONTROL_REQ.IoControlCode */ +enum DR_PORT_CONTROL_REQ +{ + IOCTL_SERIAL_SET_BAUD_RATE = 0x001B0004, + IOCTL_SERIAL_GET_BAUD_RATE = 0x001B0050, + IOCTL_SERIAL_SET_LINE_CONTROL = 0x001B000C, + IOCTL_SERIAL_GET_LINE_CONTROL = 0x001B0054, + IOCTL_SERIAL_SET_TIMEOUTS = 0x001B001C, + IOCTL_SERIAL_GET_TIMEOUTS = 0x001B0020, + +/* GET_CHARS and SET_CHARS are swapped in the RDP docs [MS-RDPESP] */ + IOCTL_SERIAL_GET_CHARS = 0x001B0058, + IOCTL_SERIAL_SET_CHARS = 0x001B005C, + + IOCTL_SERIAL_SET_DTR = 0x001B0024, + IOCTL_SERIAL_CLR_DTR = 0x001B0028, + IOCTL_SERIAL_RESET_DEVICE = 0x001B002C, + IOCTL_SERIAL_SET_RTS = 0x001B0030, + IOCTL_SERIAL_CLR_RTS = 0x001B0034, + IOCTL_SERIAL_SET_XOFF = 0x001B0038, + IOCTL_SERIAL_SET_XON = 0x001B003C, + IOCTL_SERIAL_SET_BREAK_ON = 0x001B0010, + IOCTL_SERIAL_SET_BREAK_OFF = 0x001B0014, + IOCTL_SERIAL_SET_QUEUE_SIZE = 0x001B0008, + IOCTL_SERIAL_GET_WAIT_MASK = 0x001B0040, + IOCTL_SERIAL_SET_WAIT_MASK = 0x001B0044, + IOCTL_SERIAL_WAIT_ON_MASK = 0x001B0048, + IOCTL_SERIAL_IMMEDIATE_CHAR = 0x001B0018, + IOCTL_SERIAL_PURGE = 0x001B004C, + IOCTL_SERIAL_GET_HANDFLOW = 0x001B0060, + IOCTL_SERIAL_SET_HANDFLOW = 0x001B0064, + IOCTL_SERIAL_GET_MODEMSTATUS = 0x001B0068, + IOCTL_SERIAL_GET_DTRRTS = 0x001B0078, + +/* according to [MS-RDPESP] it should be 0x001B0084, but servers send 0x001B006C */ + IOCTL_SERIAL_GET_COMMSTATUS = 0x001B006C, + + IOCTL_SERIAL_GET_PROPERTIES = 0x001B0074, + IOCTL_SERIAL_XOFF_COUNTER = 0x001B0070, + IOCTL_SERIAL_LSRMST_INSERT = 0x001B007C, + IOCTL_SERIAL_CONFIG_SIZE = 0x001B0080, + IOCTL_SERIAL_GET_STATS = 0x001B008C, + IOCTL_SERIAL_CLEAR_STATS = 0x001B0090, + IOCTL_SERIAL_GET_MODEM_CONTROL = 0x001B0094, + IOCTL_SERIAL_SET_MODEM_CONTROL = 0x001B0098, + IOCTL_SERIAL_SET_FIFO_CONTROL = 0x001B009C, +}; + +enum SERIAL_PURGE_MASK +{ + SERIAL_PURGE_TXABORT = 0x00000001, + SERIAL_PURGE_RXABORT = 0x00000002, + SERIAL_PURGE_TXCLEAR = 0x00000004, + SERIAL_PURGE_RXCLEAR = 0x00000008, +}; + +enum SERIAL_WAIT_MASK +{ + SERIAL_EV_RXCHAR = 0x0001, /* Any Character received */ + SERIAL_EV_RXFLAG = 0x0002, /* Received certain character */ + SERIAL_EV_TXEMPTY = 0x0004, /* Transmitt Queue Empty */ + SERIAL_EV_CTS = 0x0008, /* CTS changed state */ + SERIAL_EV_DSR = 0x0010, /* DSR changed state */ + SERIAL_EV_RLSD = 0x0020, /* RLSD changed state */ + SERIAL_EV_BREAK = 0x0040, /* BREAK received */ + SERIAL_EV_ERR = 0x0080, /* Line status error occurred */ + SERIAL_EV_RING = 0x0100, /* Ring signal detected */ + SERIAL_EV_PERR = 0x0200, /* Printer error occured */ + SERIAL_EV_RX80FULL = 0x0400,/* Receive buffer is 80 percent full */ + SERIAL_EV_EVENT1 = 0x0800, /* Provider specific event 1 */ + SERIAL_EV_EVENT2 = 0x1000, /* Provider specific event 2 */ +}; + +enum SERIAL_MODEM_STATUS +{ + SERIAL_MS_DTR = 0x01, + SERIAL_MS_RTS = 0x02, + SERIAL_MS_CTS = 0x10, + SERIAL_MS_DSR = 0x20, + SERIAL_MS_RNG = 0x40, + SERIAL_MS_CAR = 0x80, +}; + +enum SERIAL_HANDFLOW +{ + SERIAL_DTR_CONTROL = 0x01, + SERIAL_CTS_HANDSHAKE = 0x08, + SERIAL_ERROR_ABORT = 0x80000000, +}; + +enum SERIAL_FLOW_CONTROL +{ + SERIAL_XON_HANDSHAKE = 0x01, + SERIAL_XOFF_HANDSHAKE = 0x02, + SERIAL_DSR_SENSITIVITY = 0x40, +}; + +enum SERIAL_CHARS +{ + SERIAL_CHAR_EOF = 0, + SERIAL_CHAR_ERROR = 1, + SERIAL_CHAR_BREAK = 2, + SERIAL_CHAR_EVENT = 3, + SERIAL_CHAR_XON = 4, + SERIAL_CHAR_XOFF = 5, +}; + +enum SERIAL_ABORT_IO +{ + SERIAL_ABORT_IO_NONE = 0, + SERIAL_ABORT_IO_WRITE = 1, + SERIAL_ABORT_IO_READ = 2, +}; + +enum SERIAL_STOP_BITS +{ + SERIAL_STOP_BITS_1 = 0, + SERIAL_STOP_BITS_2 = 2, +}; + +enum SERIAL_PARITY +{ + SERIAL_NO_PARITY = 0, + SERIAL_ODD_PARITY = 1, + SERIAL_EVEN_PARITY = 2, +}; + +#endif diff --git a/channels/rdpdr/serial/serial_main.c b/channels/rdpdr/serial/serial_main.c new file mode 100644 index 000000000..784e4cc1e --- /dev/null +++ b/channels/rdpdr/serial/serial_main.c @@ -0,0 +1,710 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * + * 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. + */ + +#include +#include +#include +#include + +#include "config.h" + +#ifdef HAVE_SYS_MODEM_H +#include +#endif +#ifdef HAVE_SYS_FILIO_H +#include +#endif +#ifdef HAVE_SYS_STRTIO_H +#include +#endif + +#include "rdpdr_types.h" +#include "rdpdr_constants.h" +#include "devman.h" +#include "serial_tty.h" +#include "serial_constants.h" + +#include +#include +#include +#include + +typedef struct _SERIAL_DEVICE SERIAL_DEVICE; +struct _SERIAL_DEVICE +{ + DEVICE device; + + char* path; + SERIAL_TTY* tty; + + LIST* irp_list; + LIST* pending_irps; + freerdp_thread* thread; + struct wait_obj* in_event; + + fd_set read_fds; + fd_set write_fds; + uint32 nfds; + struct timeval tv; + uint32 select_timeout; + uint32 timeout_id; +}; + +static void serial_abort_single_io(SERIAL_DEVICE* serial, uint32 file_id, uint32 abort_io, uint32 io_status); +static void serial_check_for_events(SERIAL_DEVICE* serial); +static void serial_handle_async_irp(SERIAL_DEVICE* serial, IRP* irp); +static boolean serial_check_fds(SERIAL_DEVICE* serial); + +static void serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) +{ + SERIAL_TTY* tty; + uint32 PathLength; + uint32 FileId; + char* path; + UNICONV* uniconv; + + stream_seek(irp->input, 28); /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */ + /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */ + stream_read_uint32(irp->input, PathLength); + + uniconv = freerdp_uniconv_new(); + path = freerdp_uniconv_in(uniconv, stream_get_tail(irp->input), PathLength); + freerdp_uniconv_free(uniconv); + + FileId = irp->devman->id_sequence++; + + tty = serial_tty_new(serial->path, FileId); + if (tty == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + FileId = 0; + + DEBUG_WARN("failed to create %s", path); + } + else + { + serial->tty = tty; + DEBUG_SVC("%s(%d) created.", serial->path, FileId); + } + + stream_write_uint32(irp->output, FileId); + stream_write_uint8(irp->output, 0); + + xfree(path); + + irp->Complete(irp); +} + +static void serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp) +{ + SERIAL_TTY* tty; + + tty = serial->tty; + if (tty == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + DEBUG_WARN("tty not valid."); + } + else + { + DEBUG_SVC("%s(%d) closed.", serial->path, tty->id); + + serial_tty_free(tty); + serial->tty = NULL; + } + + stream_write_zero(irp->output, 5); /* Padding(5) */ + + irp->Complete(irp); +} + +static void serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp) +{ + SERIAL_TTY* tty; + uint32 Length; + uint64 Offset; + uint8* buffer = NULL; + + stream_read_uint32(irp->input, Length); + stream_read_uint64(irp->input, Offset); + + DEBUG_SVC("length %u offset %llu", Length, Offset); + + tty = serial->tty; + if (tty == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + + DEBUG_WARN("tty not valid."); + } + else + { + buffer = (uint8*)xmalloc(Length); + if (!serial_tty_read(tty, buffer, &Length)) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + xfree(buffer); + buffer = NULL; + Length = 0; + + DEBUG_WARN("read %s(%d) failed.", serial->path, tty->id); + } + else + { + DEBUG_SVC("read %llu-%llu from %d", Offset, Offset + Length, tty->id); + } + } + + stream_write_uint32(irp->output, Length); + if (Length > 0) + { + stream_check_size(irp->output, Length); + stream_write(irp->output, buffer, Length); + } + xfree(buffer); + + irp->Complete(irp); +} + +static void serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp) +{ + SERIAL_TTY* tty; + uint32 Length; + uint64 Offset; + + stream_read_uint32(irp->input, Length); + stream_read_uint64(irp->input, Offset); + stream_seek(irp->input, 20); /* Padding */ + + DEBUG_SVC("length %u offset %llu", Length, Offset); + + tty = serial->tty; + if (tty == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + + DEBUG_WARN("tty not valid."); + } + else if (!serial_tty_write(tty, stream_get_tail(irp->input), Length)) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + + DEBUG_WARN("write %s(%d) failed.", serial->path, tty->id); + } + else + { + DEBUG_SVC("write %llu-%llu to %s(%d).", Offset, Offset + Length, serial->path, tty->id); + } + + stream_write_uint32(irp->output, Length); + stream_write_uint8(irp->output, 0); /* Padding */ + + irp->Complete(irp); +} + +static void serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) +{ + SERIAL_TTY* tty; + uint32 IoControlCode; + uint32 InputBufferLength; + uint32 OutputBufferLength; + uint32 abort_io = SERIAL_ABORT_IO_NONE; + + DEBUG_SVC("[in] pending size %d", list_size(serial->pending_irps)); + + stream_read_uint32(irp->input, InputBufferLength); + stream_read_uint32(irp->input, OutputBufferLength); + stream_read_uint32(irp->input, IoControlCode); + stream_seek(irp->input, 20); /* Padding */ + + tty = serial->tty; + if (tty == NULL) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + OutputBufferLength = 0; + + DEBUG_WARN("tty not valid."); + } + else + { + irp->IoStatus = serial_tty_control(tty, IoControlCode, irp->input, irp->output, &abort_io); + } + + if (abort_io & SERIAL_ABORT_IO_WRITE) + serial_abort_single_io(serial, tty->id, SERIAL_ABORT_IO_WRITE, STATUS_CANCELLED); + if (abort_io & SERIAL_ABORT_IO_READ) + serial_abort_single_io(serial, tty->id, SERIAL_ABORT_IO_READ, STATUS_CANCELLED); + + if (irp->IoStatus == STATUS_PENDING) + list_enqueue(serial->pending_irps, irp); + else + irp->Complete(irp); +} + +static void serial_process_irp(SERIAL_DEVICE* serial, IRP* irp) +{ + DEBUG_SVC("MajorFunction %u", irp->MajorFunction); + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + serial_process_irp_create(serial, irp); + break; + + case IRP_MJ_CLOSE: + serial_process_irp_close(serial, irp); + break; + + case IRP_MJ_READ: + serial_handle_async_irp(serial, irp); + //serial_process_irp_read(serial, irp); + break; + + case IRP_MJ_WRITE: + serial_handle_async_irp(serial, irp); + //serial_process_irp_write(serial, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + serial_process_irp_device_control(serial, irp); + break; + + default: + DEBUG_WARN("MajorFunction 0x%X not supported", irp->MajorFunction); + irp->IoStatus = STATUS_NOT_SUPPORTED; + irp->Complete(irp); + break; + } + + serial_check_for_events(serial); +} + +static void serial_process_irp_list(SERIAL_DEVICE* serial) +{ + IRP* irp; + + while (1) + { + if (freerdp_thread_is_stopped(serial->thread)) + break; + + freerdp_thread_lock(serial->thread); + irp = (IRP*)list_dequeue(serial->irp_list); + freerdp_thread_unlock(serial->thread); + + if (irp == NULL) + break; + + serial_process_irp(serial, irp); + } +} + +static void* serial_thread_func(void* arg) +{ + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)arg; + + while (1) + { + freerdp_thread_wait(serial->thread); + + serial->nfds = 1; + FD_ZERO(&serial->read_fds); + FD_ZERO(&serial->write_fds); + + serial->tv.tv_sec = 20; + serial->tv.tv_usec = 0; + serial->select_timeout = 0; + + if (freerdp_thread_is_stopped(serial->thread)) + break; + + freerdp_thread_reset(serial->thread); + serial_process_irp_list(serial); + + if (wait_obj_is_set(serial->in_event)) + { + if (serial_check_fds(serial)) + wait_obj_clear(serial->in_event); + } + } + + freerdp_thread_quit(serial->thread); + + return NULL; +} + +static void serial_irp_request(DEVICE* device, IRP* irp) +{ + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device; + + freerdp_thread_lock(serial->thread); + list_enqueue(serial->irp_list, irp); + freerdp_thread_unlock(serial->thread); + + freerdp_thread_signal(serial->thread); +} + +static void serial_free(DEVICE* device) +{ + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device; + IRP* irp; + + DEBUG_SVC("freeing device"); + + freerdp_thread_stop(serial->thread); + freerdp_thread_free(serial->thread); + + while ((irp = (IRP*)list_dequeue(serial->irp_list)) != NULL) + irp->Discard(irp); + list_free(serial->irp_list); + + while ((irp = (IRP*)list_dequeue(serial->pending_irps)) != NULL) + irp->Discard(irp); + list_free(serial->pending_irps); + + xfree(serial); +} + +int DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + SERIAL_DEVICE* serial; + char* name; + char* path; + int i, len; + + name = (char*)pEntryPoints->plugin_data->data[1]; + path = (char*)pEntryPoints->plugin_data->data[2]; + + if (name[0] && path[0]) + { + serial = xnew(SERIAL_DEVICE); + + serial->device.type = RDPDR_DTYP_SERIAL; + serial->device.name = name; + serial->device.IRPRequest = serial_irp_request; + serial->device.Free = serial_free; + + len = strlen(name); + serial->device.data = stream_new(len + 1); + for (i = 0; i <= len; i++) + stream_write_uint8(serial->device.data, name[i] < 0 ? '_' : name[i]); + + serial->path = path; + serial->irp_list = list_new(); + serial->pending_irps = list_new(); + serial->thread = freerdp_thread_new(); + serial->in_event = wait_obj_new(); + + pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*)serial); + + freerdp_thread_start(serial->thread, serial_thread_func, serial); + } + + return 0; +} + +static void serial_abort_single_io(SERIAL_DEVICE* serial, uint32 file_id, uint32 abort_io, uint32 io_status) +{ + IRP* irp = NULL; + uint32 major; + SERIAL_TTY* tty; + + DEBUG_SVC("[in] pending size %d", list_size(serial->pending_irps)); + + tty = serial->tty; + + switch (abort_io) + { + case SERIAL_ABORT_IO_NONE: + major = 0; + break; + + case SERIAL_ABORT_IO_READ: + major = IRP_MJ_READ; + break; + + case SERIAL_ABORT_IO_WRITE: + major = IRP_MJ_WRITE; + break; + + default: + DEBUG_SVC("unexpected abort_io code %d", abort_io); + return; + } + + irp = (IRP*)list_peek(serial->pending_irps); + while (irp) + { + if (irp->FileId != file_id || irp->MajorFunction != major) + { + irp = (IRP*)list_next(serial->pending_irps, irp); + continue; + } + + /* Process a SINGLE FileId and MajorFunction */ + list_remove(serial->pending_irps, irp); + irp->IoStatus = io_status; + irp->Complete(irp); + + wait_obj_set(serial->in_event); + break; + } + + DEBUG_SVC("[out] pending size %d", list_size(serial->pending_irps)); +} + +static void serial_check_for_events(SERIAL_DEVICE* serial) +{ + IRP* irp = NULL; + IRP* prev; + uint32 result = 0; + SERIAL_TTY* tty; + + tty = serial->tty; + + DEBUG_SVC("[in] pending size %d", list_size(serial->pending_irps)); + + irp = (IRP*)list_peek(serial->pending_irps); + while (irp) + { + prev = NULL; + + if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL) + { + if (serial_tty_get_event(tty, &result)) + { + DEBUG_SVC("got event result %u", result); + + irp->IoStatus = STATUS_SUCCESS; + stream_write_uint32(irp->output, result); + irp->Complete(irp); + + prev = irp; + irp = (IRP*)list_next(serial->pending_irps, irp); + list_remove(serial->pending_irps, prev); + + wait_obj_set(serial->in_event); + } + } + + if (!prev) + irp = (IRP*)list_next(serial->pending_irps, irp); + } + + DEBUG_SVC("[out] pending size %d", list_size(serial->pending_irps)); +} + +void serial_get_timeouts(SERIAL_DEVICE* serial, IRP* irp, uint32* timeout, uint32* interval_timeout) +{ + SERIAL_TTY* tty; + uint32 Length; + uint32 pos; + + pos = stream_get_pos(irp->input); + stream_read_uint32(irp->input, Length); + stream_set_pos(irp->input, pos); + + DEBUG_SVC("length read %u", Length); + + tty = serial->tty; + *timeout = (tty->read_total_timeout_multiplier * Length) + + tty->read_total_timeout_constant; + *interval_timeout = tty->read_interval_timeout; + + DEBUG_SVC("timeouts %u %u", *timeout, *interval_timeout); +} + +static void serial_handle_async_irp(SERIAL_DEVICE* serial, IRP* irp) +{ + uint32 timeout = 0; + uint32 itv_timeout = 0; + SERIAL_TTY* tty; + + tty = serial->tty; + + switch (irp->MajorFunction) + { + case IRP_MJ_WRITE: + DEBUG_SVC("handling IRP_MJ_WRITE"); + break; + + case IRP_MJ_READ: + DEBUG_SVC("handling IRP_MJ_READ"); + + serial_get_timeouts(serial, irp, &timeout, &itv_timeout); + + /* Check if io request timeout is smaller than current (but not 0). */ + if (timeout && (serial->select_timeout == 0 || timeout < serial->select_timeout)) + { + serial->select_timeout = timeout; + serial->tv.tv_sec = serial->select_timeout / 1000; + serial->tv.tv_usec = (serial->select_timeout % 1000) * 1000; + serial->timeout_id = tty->id; + } + if (itv_timeout && (serial->select_timeout == 0 || itv_timeout < serial->select_timeout)) + { + serial->select_timeout = itv_timeout; + serial->tv.tv_sec = serial->select_timeout / 1000; + serial->tv.tv_usec = (serial->select_timeout % 1000) * 1000; + serial->timeout_id = tty->id; + } + DEBUG_SVC("select_timeout %u, tv_sec %lu tv_usec %lu, timeout_id %u", + serial->select_timeout, serial->tv.tv_sec, serial->tv.tv_usec, serial->timeout_id); + break; + + default: + DEBUG_SVC("no need to handle %d", irp->MajorFunction); + return; + } + + irp->IoStatus = STATUS_PENDING; + list_enqueue(serial->pending_irps, irp); + wait_obj_set(serial->in_event); +} + +static void __serial_check_fds(SERIAL_DEVICE* serial) +{ + IRP* irp; + IRP* prev; + SERIAL_TTY* tty; + uint32 result = 0; + + memset(&serial->tv, 0, sizeof(struct timeval)); + tty = serial->tty; + + /* scan every pending */ + irp = list_peek(serial->pending_irps); + while (irp) + { + DEBUG_SVC("MajorFunction %u", irp->MajorFunction); + + switch (irp->MajorFunction) + { + case IRP_MJ_READ: + if (FD_ISSET(tty->fd, &serial->read_fds)) + { + irp->IoStatus = STATUS_SUCCESS; + serial_process_irp_read(serial, irp); + } + break; + + case IRP_MJ_WRITE: + if (FD_ISSET(tty->fd, &serial->write_fds)) + { + irp->IoStatus = STATUS_SUCCESS; + serial_process_irp_write(serial, irp); + } + break; + + case IRP_MJ_DEVICE_CONTROL: + if (serial_tty_get_event(tty, &result)) + { + DEBUG_SVC("got event result %u", result); + + irp->IoStatus = STATUS_SUCCESS; + stream_write_uint32(irp->output, result); + irp->Complete(irp); + } + break; + + default: + DEBUG_SVC("no request found"); + break; + } + + prev = irp; + irp = (IRP*)list_next(serial->pending_irps, irp); + if (prev->IoStatus == STATUS_SUCCESS) + { + list_remove(serial->pending_irps, prev); + wait_obj_set(serial->in_event); + } + } +} + +static void serial_set_fds(SERIAL_DEVICE* serial) +{ + fd_set* fds; + IRP* irp; + SERIAL_TTY* tty; + + DEBUG_SVC("[in] pending size %d", list_size(serial->pending_irps)); + + tty = serial->tty; + irp = (IRP*)list_peek(serial->pending_irps); + while (irp) + { + fds = NULL; + + switch (irp->MajorFunction) + { + case IRP_MJ_WRITE: + fds = &serial->write_fds; + break; + + case IRP_MJ_READ: + fds = &serial->read_fds; + break; + } + + if (fds && (tty->fd >= 0)) + { + FD_SET(tty->fd, fds); + serial->nfds = MAX(serial->nfds, tty->fd); + } + irp = (IRP*)list_next(serial->pending_irps, irp); + } +} + +static boolean serial_check_fds(SERIAL_DEVICE* serial) +{ + if (list_size(serial->pending_irps) == 0) + return 1; + + serial_set_fds(serial); + DEBUG_SVC("waiting %lu %lu", serial->tv.tv_sec, serial->tv.tv_usec); + + switch (select(serial->nfds + 1, &serial->read_fds, &serial->write_fds, NULL, &serial->tv)) + { + case -1: + DEBUG_SVC("select has returned -1 with error: %s", strerror(errno)); + return 0; + + case 0: + if (serial->select_timeout) + { + serial_abort_single_io(serial, serial->timeout_id, SERIAL_ABORT_IO_NONE, STATUS_TIMEOUT); + serial_abort_single_io(serial, serial->timeout_id, SERIAL_ABORT_IO_READ, STATUS_TIMEOUT); + serial_abort_single_io(serial, serial->timeout_id, SERIAL_ABORT_IO_WRITE, STATUS_TIMEOUT); + } + DEBUG_SVC("select has timed out"); + return 0; + + default: + break; + } + + __serial_check_fds(serial); + + return 1; +} diff --git a/channels/rdpdr/serial/serial_tty.c b/channels/rdpdr/serial/serial_tty.c new file mode 100644 index 000000000..d9b3ed474 --- /dev/null +++ b/channels/rdpdr/serial/serial_tty.c @@ -0,0 +1,973 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * + * 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. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdpdr_constants.h" +#include "rdpdr_types.h" +#include "serial_tty.h" +#include "serial_constants.h" + +#ifdef HAVE_SYS_MODEM_H +#include +#endif +#ifdef HAVE_SYS_FILIO_H +#include +#endif +#ifdef HAVE_SYS_STRTIO_H +#include +#endif + +#ifndef CRTSCTS +#define CRTSCTS 0 +#endif + +/* FIONREAD should really do the same thing as TIOCINQ, where it is + * not available */ +#if !defined(TIOCINQ) && defined(FIONREAD) +#define TIOCINQ FIONREAD +#endif +#if !defined(TIOCOUTQ) && defined(FIONWRITE) +#define TIOCOUTQ FIONWRITE +#endif + + +static uint32 tty_write_data(SERIAL_TTY* tty, uint8* data, int len); +static void tty_set_termios(SERIAL_TTY* tty); +static boolean tty_get_termios(SERIAL_TTY* tty); +static int tty_get_error_status(); + +uint32 serial_tty_control(SERIAL_TTY* tty, uint32 IoControlCode, STREAM* input, STREAM* output, uint32* abort_io) +{ + int purge_mask; + uint32 result; + uint32 modemstate; + uint8 immediate; + uint32 ret = STATUS_SUCCESS; + uint32 length = 0; + uint32 pos; + + DEBUG_SVC("in"); + + stream_seek(output, sizeof(uint32)); + + switch (IoControlCode) + { + case IOCTL_SERIAL_SET_BAUD_RATE: + stream_read_uint32(input, tty->baud_rate); + tty_set_termios(tty); + DEBUG_SVC("SERIAL_SET_BAUD_RATE %d", tty->baud_rate); + break; + + case IOCTL_SERIAL_GET_BAUD_RATE: + length = 4; + stream_write_uint32(output, tty->baud_rate); + DEBUG_SVC("SERIAL_GET_BAUD_RATE %d", tty->baud_rate); + break; + + case IOCTL_SERIAL_SET_QUEUE_SIZE: + stream_read_uint32(input, tty->queue_in_size); + stream_read_uint32(input, tty->queue_out_size); + DEBUG_SVC("SERIAL_SET_QUEUE_SIZE in %d out %d", tty->queue_in_size, tty->queue_out_size); + break; + + case IOCTL_SERIAL_SET_LINE_CONTROL: + stream_read_uint8(input, tty->stop_bits); + stream_read_uint8(input, tty->parity); + stream_read_uint8(input, tty->word_length); + tty_set_termios(tty); + DEBUG_SVC("SERIAL_SET_LINE_CONTROL stop %d parity %d word %d", + tty->stop_bits, tty->parity, tty->word_length); + break; + + case IOCTL_SERIAL_GET_LINE_CONTROL: + DEBUG_SVC("SERIAL_GET_LINE_CONTROL"); + length = 3; + stream_write_uint8(output, tty->stop_bits); + stream_write_uint8(output, tty->parity); + stream_write_uint8(output, tty->word_length); + break; + + case IOCTL_SERIAL_IMMEDIATE_CHAR: + DEBUG_SVC("SERIAL_IMMEDIATE_CHAR"); + stream_read_uint8(input, immediate); + tty_write_data(tty, &immediate, 1); + break; + + case IOCTL_SERIAL_CONFIG_SIZE: + DEBUG_SVC("SERIAL_CONFIG_SIZE"); + length = 4; + stream_write_uint32(output, 0); + break; + + case IOCTL_SERIAL_GET_CHARS: + DEBUG_SVC("SERIAL_GET_CHARS"); + length = 6; + stream_write(output, tty->chars, 6); + break; + + case IOCTL_SERIAL_SET_CHARS: + DEBUG_SVC("SERIAL_SET_CHARS"); + stream_read(input, tty->chars, 6); + tty_set_termios(tty); + break; + + case IOCTL_SERIAL_GET_HANDFLOW: + length = 16; + tty_get_termios(tty); + stream_write_uint32(output, tty->control); + stream_write_uint32(output, tty->xonoff); + stream_write_uint32(output, tty->onlimit); + stream_write_uint32(output, tty->offlimit); + DEBUG_SVC("IOCTL_SERIAL_GET_HANDFLOW %X %X %X %X", + tty->control, tty->xonoff, tty->onlimit, tty->offlimit); + break; + + case IOCTL_SERIAL_SET_HANDFLOW: + stream_read_uint32(input, tty->control); + stream_read_uint32(input, tty->xonoff); + stream_read_uint32(input, tty->onlimit); + stream_read_uint32(input, tty->offlimit); + DEBUG_SVC("IOCTL_SERIAL_SET_HANDFLOW %X %X %X %X", + tty->control, tty->xonoff, tty->onlimit, tty->offlimit); + tty_set_termios(tty); + break; + + case IOCTL_SERIAL_SET_TIMEOUTS: + stream_read_uint32(input, tty->read_interval_timeout); + stream_read_uint32(input, tty->read_total_timeout_multiplier); + stream_read_uint32(input, tty->read_total_timeout_constant); + stream_read_uint32(input, tty->write_total_timeout_multiplier); + stream_read_uint32(input, tty->write_total_timeout_constant); + + /* http://www.codeproject.com/KB/system/chaiyasit_t.aspx, see 'ReadIntervalTimeout' section + http://msdn.microsoft.com/en-us/library/ms885171.aspx */ + if (tty->read_interval_timeout == SERIAL_TIMEOUT_MAX) + { + tty->read_interval_timeout = 0; + tty->read_total_timeout_multiplier = 0; + } + + DEBUG_SVC("SERIAL_SET_TIMEOUTS read timeout %d %d %d", + tty->read_interval_timeout, + tty->read_total_timeout_multiplier, + tty->read_total_timeout_constant); + break; + + case IOCTL_SERIAL_GET_TIMEOUTS: + DEBUG_SVC("SERIAL_GET_TIMEOUTS read timeout %d %d %d", + tty->read_interval_timeout, + tty->read_total_timeout_multiplier, + tty->read_total_timeout_constant); + length = 20; + stream_write_uint32(output, tty->read_interval_timeout); + stream_write_uint32(output, tty->read_total_timeout_multiplier); + stream_write_uint32(output, tty->read_total_timeout_constant); + stream_write_uint32(output, tty->write_total_timeout_multiplier); + stream_write_uint32(output, tty->write_total_timeout_constant); + break; + + case IOCTL_SERIAL_GET_WAIT_MASK: + DEBUG_SVC("SERIAL_GET_WAIT_MASK %X", tty->wait_mask); + length = 4; + stream_write_uint32(output, tty->wait_mask); + break; + + case IOCTL_SERIAL_SET_WAIT_MASK: + stream_read_uint32(input, tty->wait_mask); + DEBUG_SVC("SERIAL_SET_WAIT_MASK %X", tty->wait_mask); + break; + + case IOCTL_SERIAL_SET_DTR: + DEBUG_SVC("SERIAL_SET_DTR"); + ioctl(tty->fd, TIOCMGET, &result); + result |= TIOCM_DTR; + ioctl(tty->fd, TIOCMSET, &result); + tty->dtr = 1; + break; + + case IOCTL_SERIAL_CLR_DTR: + DEBUG_SVC("SERIAL_CLR_DTR"); + ioctl(tty->fd, TIOCMGET, &result); + result &= ~TIOCM_DTR; + ioctl(tty->fd, TIOCMSET, &result); + tty->dtr = 0; + break; + + case IOCTL_SERIAL_SET_RTS: + DEBUG_SVC("SERIAL_SET_RTS"); + ioctl(tty->fd, TIOCMGET, &result); + result |= TIOCM_RTS; + ioctl(tty->fd, TIOCMSET, &result); + tty->rts = 1; + break; + + case IOCTL_SERIAL_CLR_RTS: + DEBUG_SVC("SERIAL_CLR_RTS"); + ioctl(tty->fd, TIOCMGET, &result); + result &= ~TIOCM_RTS; + ioctl(tty->fd, TIOCMSET, &result); + tty->rts = 0; + break; + + case IOCTL_SERIAL_GET_MODEMSTATUS: + modemstate = 0; +#ifdef TIOCMGET + ioctl(tty->fd, TIOCMGET, &result); + if (result & TIOCM_CTS) + modemstate |= SERIAL_MS_CTS; + if (result & TIOCM_DSR) + modemstate |= SERIAL_MS_DSR; + if (result & TIOCM_RNG) + modemstate |= SERIAL_MS_RNG; + if (result & TIOCM_CAR) + modemstate |= SERIAL_MS_CAR; + if (result & TIOCM_DTR) + modemstate |= SERIAL_MS_DTR; + if (result & TIOCM_RTS) + modemstate |= SERIAL_MS_RTS; +#endif + DEBUG_SVC("SERIAL_GET_MODEMSTATUS %X", modemstate); + length = 4; + stream_write_uint32(output, modemstate); + break; + + case IOCTL_SERIAL_GET_COMMSTATUS: + length = 18; + stream_write_uint32(output, 0); /* Errors */ + stream_write_uint32(output, 0); /* Hold reasons */ + + result = 0; +#ifdef TIOCINQ + ioctl(tty->fd, TIOCINQ, &result); +#endif + stream_write_uint32(output, result); /* Amount in in queue */ + if (result) + DEBUG_SVC("SERIAL_GET_COMMSTATUS in queue %d", result); + + result = 0; +#ifdef TIOCOUTQ + ioctl(tty->fd, TIOCOUTQ, &result); +#endif + stream_write_uint32(output, result); /* Amount in out queue */ + DEBUG_SVC("SERIAL_GET_COMMSTATUS out queue %d", result); + + stream_write_uint8(output, 0); /* EofReceived */ + stream_write_uint8(output, 0); /* WaitForImmediate */ + break; + + case IOCTL_SERIAL_PURGE: + stream_read_uint32(input, purge_mask); + DEBUG_SVC("SERIAL_PURGE purge_mask %X", purge_mask); + if ((purge_mask & SERIAL_PURGE_TXCLEAR) + && (purge_mask & SERIAL_PURGE_RXCLEAR)) + tcflush(tty->fd, TCIOFLUSH); + else if (purge_mask & SERIAL_PURGE_TXCLEAR) + tcflush(tty->fd, TCOFLUSH); + else if (purge_mask & SERIAL_PURGE_RXCLEAR) + tcflush(tty->fd, TCIFLUSH); + + if (purge_mask & SERIAL_PURGE_TXABORT) + *abort_io |= SERIAL_ABORT_IO_WRITE; + if (purge_mask & SERIAL_PURGE_RXABORT) + *abort_io |= SERIAL_ABORT_IO_READ; + break; + case IOCTL_SERIAL_WAIT_ON_MASK: + DEBUG_SVC("SERIAL_WAIT_ON_MASK %X", tty->wait_mask); + tty->event_pending = 1; + length = 4; + if (serial_tty_get_event(tty, &result)) + { + DEBUG_SVC("WAIT end event = %X", result); + stream_write_uint32(output, result); + break; + } + ret = STATUS_PENDING; + break; + + case IOCTL_SERIAL_SET_BREAK_ON: + DEBUG_SVC("SERIAL_SET_BREAK_ON"); + tcsendbreak(tty->fd, 0); + break; + + case IOCTL_SERIAL_RESET_DEVICE: + DEBUG_SVC("SERIAL_RESET_DEVICE"); + break; + + case IOCTL_SERIAL_SET_BREAK_OFF: + DEBUG_SVC("SERIAL_SET_BREAK_OFF"); + break; + + case IOCTL_SERIAL_SET_XOFF: + DEBUG_SVC("SERIAL_SET_XOFF"); + break; + + case IOCTL_SERIAL_SET_XON: + DEBUG_SVC("SERIAL_SET_XON"); + tcflow(tty->fd, TCION); + break; + + default: + DEBUG_SVC("NOT FOUND IoControlCode SERIAL IOCTL %d", IoControlCode); + return STATUS_INVALID_PARAMETER; + } + + /* Write OutputBufferLength */ + pos = stream_get_pos(output); + stream_set_pos(output, 16); + stream_write_uint32(output, length); + stream_set_pos(output, pos); + + return ret; +} + +boolean serial_tty_read(SERIAL_TTY* tty, uint8* buffer, uint32* Length) +{ + long timeout = 90; + struct termios *ptermios; + ssize_t r; + + DEBUG_SVC("in"); + ptermios = tty->ptermios; + + /* Set timeouts kind of like the windows serial timeout parameters. Multiply timeout + with requested read size */ + if (tty->read_total_timeout_multiplier | tty->read_total_timeout_constant) + { + timeout = + (tty->read_total_timeout_multiplier * (*Length) + + tty->read_total_timeout_constant + 99) / 100; + } + else if (tty->read_interval_timeout) + { + timeout = (tty->read_interval_timeout * (*Length) + 99) / 100; + } + + /* If a timeout is set, do a blocking read, which times out after some time. + It will make FreeRDP less responsive, but it will improve serial performance, + by not reading one character at a time. */ + if (timeout == 0) + { + ptermios->c_cc[VTIME] = 0; + ptermios->c_cc[VMIN] = 0; + } + else + { + ptermios->c_cc[VTIME] = timeout; + ptermios->c_cc[VMIN] = 1; + } + + tcsetattr(tty->fd, TCSANOW, ptermios); + + memset(buffer, 0, *Length); + r = read(tty->fd, buffer, *Length); + if (r < 0) + return False; + + tty->event_txempty = r; + *Length = r; + + return True; +} + +boolean serial_tty_write(SERIAL_TTY* tty, uint8* buffer, uint32 Length) +{ + ssize_t r; + uint32 event_txempty = Length; + + DEBUG_SVC("in"); + + while (Length > 0) + { + r = write(tty->fd, buffer, Length); + if (r < 0) + return False; + + Length -= r; + buffer += r; + } + tty->event_txempty = event_txempty; + + return True; +} + +void serial_tty_free(SERIAL_TTY* tty) +{ + DEBUG_SVC("in"); + + if (tty->fd >= 0) + { + tcsetattr(tty->fd, TCSANOW, tty->pold_termios); + close(tty->fd); + } + + xfree(tty->ptermios); + xfree(tty->pold_termios); + xfree(tty); +} + +SERIAL_TTY* serial_tty_new(const char* path, uint32 id) +{ + SERIAL_TTY* tty; + + tty = xnew(SERIAL_TTY); + tty->id = id; + tty->fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (tty->fd < 0) + { + perror("open"); + DEBUG_WARN("failed to open device %s", path); + return NULL; + } + else + DEBUG_SVC("tty fd %d successfully opened", tty->fd); + + tty->ptermios = (struct termios*) malloc(sizeof(struct termios)); + memset(tty->ptermios, 0, sizeof(struct termios)); + tty->pold_termios = (struct termios*) malloc(sizeof(struct termios)); + memset(tty->pold_termios, 0, sizeof(struct termios)); + tcgetattr(tty->fd, tty->pold_termios); + + if (!tty_get_termios(tty)) + { + DEBUG_WARN("%s access denied", path); + fflush(stdout); + return NULL; + } + + tty->ptermios->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + tty->ptermios->c_iflag = IGNPAR | ICRNL; + tty->ptermios->c_oflag &= ~OPOST; + tty->ptermios->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tty->ptermios->c_cflag &= ~(CSIZE | PARENB); + tty->ptermios->c_cflag |= CLOCAL | CREAD | CS8; + tcsetattr(tty->fd, TCSANOW, tty->ptermios); + + tty->event_txempty = 0; + tty->event_cts = 0; + tty->event_dsr = 0; + tty->event_rlsd = 0; + tty->event_pending = 0; + + /* all read and writes should be non-blocking */ + if (fcntl(tty->fd, F_SETFL, O_NONBLOCK) == -1) + { + DEBUG_WARN("%s fcntl", path); + perror("fcntl"); + return NULL; + } + + tty->read_total_timeout_constant = 5; + + return tty; +} + +boolean serial_tty_get_event(SERIAL_TTY* tty, uint32* result) +{ + int bytes; + boolean ret = False; + + DEBUG_SVC("in"); + + *result = 0; + +#ifdef TIOCINQ + /* When wait_mask is set to zero we ought to cancel it all + For reference: http://msdn.microsoft.com/en-us/library/aa910487.aspx */ + if (tty->wait_mask == 0) + { + tty->event_pending = 0; + return True; + } + + ioctl(tty->fd, TIOCINQ, &bytes); + + if (bytes > 0) + { + DEBUG_SVC("bytes %d", bytes); + + if (bytes > tty->event_rlsd) + { + tty->event_rlsd = bytes; + if (tty->wait_mask & SERIAL_EV_RLSD) + { + DEBUG_SVC("SERIAL_EV_RLSD"); + *result |= SERIAL_EV_RLSD; + ret = True; + } + + } + + if ((bytes > 1) && (tty->wait_mask & SERIAL_EV_RXFLAG)) + { + DEBUG_SVC("SERIAL_EV_RXFLAG bytes %d", bytes); + *result |= SERIAL_EV_RXFLAG; + ret = True; + } + if ((tty->wait_mask & SERIAL_EV_RXCHAR)) + { + DEBUG_SVC("SERIAL_EV_RXCHAR bytes %d", bytes); + *result |= SERIAL_EV_RXCHAR; + ret = True; + } + + } + else + { + tty->event_rlsd = 0; + } +#endif + +#ifdef TIOCOUTQ + ioctl(tty->fd, TIOCOUTQ, &bytes); + if ((bytes == 0) + && (tty->event_txempty > 0) && (tty->wait_mask & SERIAL_EV_TXEMPTY)) + { + DEBUG_SVC("SERIAL_EV_TXEMPTY"); + *result |= SERIAL_EV_TXEMPTY; + ret = True; + } + tty->event_txempty = bytes; +#endif + + ioctl(tty->fd, TIOCMGET, &bytes); + if ((bytes & TIOCM_DSR) != tty->event_dsr) + { + tty->event_dsr = bytes & TIOCM_DSR; + if (tty->wait_mask & SERIAL_EV_DSR) + { + DEBUG_SVC("SERIAL_EV_DSR %s", (bytes & TIOCM_DSR) ? "ON" : "OFF"); + *result |= SERIAL_EV_DSR; + ret = True; + } + } + + if ((bytes & TIOCM_CTS) != tty->event_cts) + { + tty->event_cts = bytes & TIOCM_CTS; + if (tty->wait_mask & SERIAL_EV_CTS) + { + DEBUG_SVC("SERIAL_EV_CTS %s", (bytes & TIOCM_CTS) ? "ON" : "OFF"); + *result |= SERIAL_EV_CTS; + ret = True; + } + } + + if (ret) + tty->event_pending = 0; + + return ret; +} + +static boolean tty_get_termios(SERIAL_TTY* tty) +{ + speed_t speed; + struct termios *ptermios; + ptermios = tty->ptermios; + + DEBUG_SVC("tcgetattr? %d", tcgetattr(tty->fd, ptermios) >= 0); + if (tcgetattr(tty->fd, ptermios) < 0) + return False; + + speed = cfgetispeed(ptermios); + switch (speed) + { +#ifdef B75 + case B75: + tty->baud_rate = 75; + break; +#endif +#ifdef B110 + case B110: + tty->baud_rate = 110; + break; +#endif +#ifdef B134 + case B134: + tty->baud_rate = 134; + break; +#endif +#ifdef B150 + case B150: + tty->baud_rate = 150; + break; +#endif +#ifdef B300 + case B300: + tty->baud_rate = 300; + break; +#endif +#ifdef B600 + case B600: + tty->baud_rate = 600; + break; +#endif +#ifdef B1200 + case B1200: + tty->baud_rate = 1200; + break; +#endif +#ifdef B1800 + case B1800: + tty->baud_rate = 1800; + break; +#endif +#ifdef B2400 + case B2400: + tty->baud_rate = 2400; + break; +#endif +#ifdef B4800 + case B4800: + tty->baud_rate = 4800; + break; +#endif +#ifdef B9600 + case B9600: + tty->baud_rate = 9600; + break; +#endif +#ifdef B19200 + case B19200: + tty->baud_rate = 19200; + break; +#endif +#ifdef B38400 + case B38400: + tty->baud_rate = 38400; + break; +#endif +#ifdef B57600 + case B57600: + tty->baud_rate = 57600; + break; +#endif +#ifdef B115200 + case B115200: + tty->baud_rate = 115200; + break; +#endif +#ifdef B230400 + case B230400: + tty->baud_rate = 230400; + break; +#endif +#ifdef B460800 + case B460800: + tty->baud_rate = 460800; + break; +#endif + default: + tty->baud_rate = 9600; + break; + } + + speed = cfgetospeed(ptermios); + tty->dtr = (speed == B0) ? 0 : 1; + + tty->stop_bits = (ptermios->c_cflag & CSTOPB) ? SERIAL_STOP_BITS_2 : SERIAL_STOP_BITS_1; + tty->parity = + (ptermios->c_cflag & PARENB) ? ((ptermios->c_cflag & PARODD) ? SERIAL_ODD_PARITY : + SERIAL_EVEN_PARITY) : SERIAL_NO_PARITY; + switch (ptermios->c_cflag & CSIZE) + { + case CS5: + tty->word_length = 5; + break; + case CS6: + tty->word_length = 6; + break; + case CS7: + tty->word_length = 7; + break; + default: + tty->word_length = 8; + break; + } + + if (ptermios->c_cflag & CRTSCTS) + { + tty->control = SERIAL_DTR_CONTROL | SERIAL_CTS_HANDSHAKE | SERIAL_ERROR_ABORT; + } + else + { + tty->control = SERIAL_DTR_CONTROL | SERIAL_ERROR_ABORT; + } + + tty->xonoff = SERIAL_DSR_SENSITIVITY; + if (ptermios->c_iflag & IXON) + tty->xonoff |= SERIAL_XON_HANDSHAKE; + + if (ptermios->c_iflag & IXOFF) + tty->xonoff |= SERIAL_XOFF_HANDSHAKE; + + tty->chars[SERIAL_CHAR_XON] = ptermios->c_cc[VSTART]; + tty->chars[SERIAL_CHAR_XOFF] = ptermios->c_cc[VSTOP]; + tty->chars[SERIAL_CHAR_EOF] = ptermios->c_cc[VEOF]; + tty->chars[SERIAL_CHAR_BREAK] = ptermios->c_cc[VINTR]; + tty->chars[SERIAL_CHAR_ERROR] = ptermios->c_cc[VKILL]; + + return True; +} + +static void tty_set_termios(SERIAL_TTY* tty) +{ + speed_t speed; + struct termios *ptermios; + + DEBUG_SVC("in"); + ptermios = tty->ptermios; + switch (tty->baud_rate) + { +#ifdef B75 + case 75: + speed = B75; + break; +#endif +#ifdef B110 + case 110: + speed = B110; + break; +#endif +#ifdef B134 + case 134: + speed = B134; + break; +#endif +#ifdef B150 + case 150: + speed = B150; + break; +#endif +#ifdef B300 + case 300: + speed = B300; + break; +#endif +#ifdef B600 + case 600: + speed = B600; + break; +#endif +#ifdef B1200 + case 1200: + speed = B1200; + break; +#endif +#ifdef B1800 + case 1800: + speed = B1800; + break; +#endif +#ifdef B2400 + case 2400: + speed = B2400; + break; +#endif +#ifdef B4800 + case 4800: + speed = B4800; + break; +#endif +#ifdef B9600 + case 9600: + speed = B9600; + break; +#endif +#ifdef B19200 + case 19200: + speed = B19200; + break; +#endif +#ifdef B38400 + case 38400: + speed = B38400; + break; +#endif +#ifdef B57600 + case 57600: + speed = B57600; + break; +#endif +#ifdef B115200 + case 115200: + speed = B115200; + break; +#endif +#ifdef B230400 + case 230400: + speed = B115200; + break; +#endif +#ifdef B460800 + case 460800: + speed = B115200; + break; +#endif + default: + speed = B9600; + break; + } + +#ifdef CBAUD + ptermios->c_cflag &= ~CBAUD; + ptermios->c_cflag |= speed; +#else + /* on systems with separate ispeed and ospeed, we can remember the speed + in ispeed while changing DTR with ospeed */ + cfsetispeed(tty->ptermios, speed); + cfsetospeed(tty->ptermios, tty->dtr ? speed : 0); +#endif + + ptermios->c_cflag &= ~(CSTOPB | PARENB | PARODD | CSIZE | CRTSCTS); + switch (tty->stop_bits) + { + case SERIAL_STOP_BITS_2: + ptermios->c_cflag |= CSTOPB; + break; + default: + ptermios->c_cflag &= ~CSTOPB; + break; + } + + switch (tty->parity) + { + case SERIAL_EVEN_PARITY: + ptermios->c_cflag |= PARENB; + break; + case SERIAL_ODD_PARITY: + ptermios->c_cflag |= PARENB | PARODD; + break; + case SERIAL_NO_PARITY: + ptermios->c_cflag &= ~(PARENB | PARODD); + break; + } + + switch (tty->word_length) + { + case 5: + ptermios->c_cflag |= CS5; + break; + case 6: + ptermios->c_cflag |= CS6; + break; + case 7: + ptermios->c_cflag |= CS7; + break; + default: + ptermios->c_cflag |= CS8; + break; + } + +#if 0 + if (tty->rts) + ptermios->c_cflag |= CRTSCTS; + else + ptermios->c_cflag &= ~CRTSCTS; +#endif + + if (tty->control & SERIAL_CTS_HANDSHAKE) + { + ptermios->c_cflag |= CRTSCTS; + } + else + { + ptermios->c_cflag &= ~CRTSCTS; + } + + + if (tty->xonoff & SERIAL_XON_HANDSHAKE) + { + ptermios->c_iflag |= IXON | IMAXBEL; + } + if (tty->xonoff & SERIAL_XOFF_HANDSHAKE) + { + ptermios->c_iflag |= IXOFF | IMAXBEL; + } + + if ((tty->xonoff & (SERIAL_XOFF_HANDSHAKE | SERIAL_XON_HANDSHAKE)) == 0) + { + ptermios->c_iflag &= ~IXON; + ptermios->c_iflag &= ~IXOFF; + } + + ptermios->c_cc[VSTART] = tty->chars[SERIAL_CHAR_XON]; + ptermios->c_cc[VSTOP] = tty->chars[SERIAL_CHAR_XOFF]; + ptermios->c_cc[VEOF] = tty->chars[SERIAL_CHAR_EOF]; + ptermios->c_cc[VINTR] = tty->chars[SERIAL_CHAR_BREAK]; + ptermios->c_cc[VKILL] = tty->chars[SERIAL_CHAR_ERROR]; + + tcsetattr(tty->fd, TCSANOW, ptermios); +} + +static uint32 tty_write_data(SERIAL_TTY* tty, uint8* data, int len) +{ + ssize_t r; + + DEBUG_SVC("in"); + + r = write(tty->fd, data, len); + if (r < 0) + return tty_get_error_status(); + + tty->event_txempty = r; + + return STATUS_SUCCESS; +} + +static int tty_get_error_status() +{ + DEBUG_SVC("in errno %d", errno); + + switch (errno) + { + case EACCES: + case ENOTDIR: + case ENFILE: + return STATUS_ACCESS_DENIED; + case EISDIR: + return STATUS_FILE_IS_A_DIRECTORY; + case EEXIST: + return STATUS_OBJECT_NAME_COLLISION; + case EBADF: + return STATUS_INVALID_HANDLE; + default: + return STATUS_NO_SUCH_FILE; + } +} diff --git a/channels/rdpdr/serial/serial_tty.h b/channels/rdpdr/serial/serial_tty.h new file mode 100644 index 000000000..a4b82f98f --- /dev/null +++ b/channels/rdpdr/serial/serial_tty.h @@ -0,0 +1,72 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * + * 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 __SERIAL_TTY_H +#define __SERIAL_TTY_H + +#include +#include +#include + +typedef struct _SERIAL_TTY SERIAL_TTY; +struct _SERIAL_TTY +{ + uint32 id; + int fd; + + int dtr; + int rts; + uint32 control; + uint32 xonoff; + uint32 onlimit; + uint32 offlimit; + uint32 baud_rate; + uint32 queue_in_size; + uint32 queue_out_size; + uint32 wait_mask; + uint32 read_interval_timeout; + uint32 read_total_timeout_multiplier; + uint32 read_total_timeout_constant; + uint32 write_total_timeout_multiplier; + uint32 write_total_timeout_constant; + uint8 stop_bits; + uint8 parity; + uint8 word_length; + uint8 chars[6]; + struct termios* ptermios; + struct termios* pold_termios; + int event_txempty; + int event_cts; + int event_dsr; + int event_rlsd; + int event_pending; +}; + + +SERIAL_TTY* serial_tty_new(const char* path, uint32 id); +void serial_tty_free(SERIAL_TTY* tty); + +boolean serial_tty_read(SERIAL_TTY* tty, uint8* buffer, uint32* Length); +boolean serial_tty_write(SERIAL_TTY* tty, uint8* buffer, uint32 Length); +uint32 serial_tty_control(SERIAL_TTY* tty, uint32 IoControlCode, STREAM* input, STREAM* output, uint32* abort_io); + +boolean serial_tty_get_event(SERIAL_TTY* tty, uint32* result); + +#endif /* __SERIAL_TTY_H */ From 5c27178055267f4c44b3c76adbb3c4bd5c293cc4 Mon Sep 17 00:00:00 2001 From: Nicolas Graziano Date: Sat, 15 Oct 2011 00:59:23 +0200 Subject: [PATCH 05/10] Prevent BadDrawable after creation of offscreen when the current offscreen is recreate. --- include/freerdp/cache/offscreen.h | 1 + libfreerdp-cache/offscreen.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/freerdp/cache/offscreen.h b/include/freerdp/cache/offscreen.h index ffab74062..211a8e143 100644 --- a/include/freerdp/cache/offscreen.h +++ b/include/freerdp/cache/offscreen.h @@ -43,6 +43,7 @@ struct rdp_offscreen_cache cbSetSurface SetSurface; + uint16 currentSurface; uint16 maxSize; uint16 maxEntries; rdpUpdate* update; diff --git a/libfreerdp-cache/offscreen.c b/libfreerdp-cache/offscreen.c index ca87f2e8a..1d111771f 100644 --- a/libfreerdp-cache/offscreen.c +++ b/libfreerdp-cache/offscreen.c @@ -45,6 +45,9 @@ void update_gdi_create_offscreen_bitmap(rdpUpdate* update, CREATE_OFFSCREEN_BITM } offscreen_cache_put(cache->offscreen, create_offscreen_bitmap->id, bitmap); + + if(cache->offscreen->currentSurface == create_offscreen_bitmap->id) + IFCALL(cache->offscreen->SetSurface, update, bitmap, False); } void update_gdi_switch_surface(rdpUpdate* update, SWITCH_SURFACE_ORDER* switch_surface) @@ -61,6 +64,7 @@ void update_gdi_switch_surface(rdpUpdate* update, SWITCH_SURFACE_ORDER* switch_s bitmap = offscreen_cache_get(cache->offscreen, switch_surface->bitmapId); IFCALL(cache->offscreen->SetSurface, update, bitmap, False); } + cache->offscreen->currentSurface = switch_surface->bitmapId; } rdpBitmap* offscreen_cache_get(rdpOffscreenCache* offscreen_cache, uint16 index) @@ -112,6 +116,7 @@ rdpOffscreenCache* offscreen_cache_new(rdpSettings* settings) offscreen_cache->settings = settings; offscreen_cache->update = ((freerdp*) settings->instance)->update; + offscreen_cache->currentSurface = SCREEN_BITMAP_SURFACE; offscreen_cache->maxSize = 7680; offscreen_cache->maxEntries = 100; From 1277bc7f8aa9d0d3e4233c67f0df7b357d22e8d4 Mon Sep 17 00:00:00 2001 From: Anthony Tong Date: Sat, 15 Oct 2011 10:30:10 -0500 Subject: [PATCH 06/10] initial scard support --- CMakeLists.txt | 1 + channels/rdpdr/CMakeLists.txt | 4 + channels/rdpdr/smartcard/CMakeLists.txt | 34 + channels/rdpdr/smartcard/scard_main.c | 204 +++ channels/rdpdr/smartcard/scard_main.h | 52 + channels/rdpdr/smartcard/scard_operations.c | 1547 +++++++++++++++++++ cmake/FindPCSC.cmake | 3 + config.h.in | 1 + 8 files changed, 1846 insertions(+) create mode 100644 channels/rdpdr/smartcard/CMakeLists.txt create mode 100644 channels/rdpdr/smartcard/scard_main.c create mode 100644 channels/rdpdr/smartcard/scard_main.h create mode 100644 channels/rdpdr/smartcard/scard_operations.c create mode 100644 cmake/FindPCSC.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c43c73981..e83f838d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ if(NOT WIN32) find_required_package(ZLIB) find_suggested_package(ALSA) find_optional_package(PulseAudio) + find_optional_package(PCSC) find_suggested_package(Cups) find_suggested_package(FFmpeg) endif() diff --git a/channels/rdpdr/CMakeLists.txt b/channels/rdpdr/CMakeLists.txt index e79be8e7d..a05b61893 100644 --- a/channels/rdpdr/CMakeLists.txt +++ b/channels/rdpdr/CMakeLists.txt @@ -41,3 +41,7 @@ add_subdirectory(disk) add_subdirectory(printer) add_subdirectory(parallel) add_subdirectory(serial) + +if(PCSC_FOUND) + add_subdirectory(smartcard) +endif() diff --git a/channels/rdpdr/smartcard/CMakeLists.txt b/channels/rdpdr/smartcard/CMakeLists.txt new file mode 100644 index 000000000..816fc09f4 --- /dev/null +++ b/channels/rdpdr/smartcard/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# 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. + +set(SCARD_SRCS + scard_main.c + scard_operations.c +) + +include_directories(..) +include_directories(${PCSC_INCLUDE_DIRS}) + +add_library(scard ${SCARD_SRCS}) +set_target_properties(scard PROPERTIES PREFIX "") + +target_link_libraries(scard freerdp-utils) +target_link_libraries(scard ${PCSC_LIBRARIES}) + +install(TARGETS scard DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/rdpdr/smartcard/scard_main.c b/channels/rdpdr/smartcard/scard_main.c new file mode 100644 index 000000000..5066bf2d0 --- /dev/null +++ b/channels/rdpdr/smartcard/scard_main.c @@ -0,0 +1,204 @@ +/* + FreeRDP: A Remote Desktop Protocol client. + Redirected Smart Card Device Service + + Copyright 2011 O.S. Systems Software Ltda. + Copyright 2011 Eduardo Fiss Beloni + Copyright 2011 Anthony Tong + + 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. +*/ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#include "rdpdr_types.h" +#include "rdpdr_constants.h" + +#include "scard_main.h" + + +static void +scard_free(DEVICE* dev) +{ + SCARD_DEVICE* scard = (SCARD_DEVICE*)dev; + IRP* irp; + + freerdp_thread_stop(scard->thread); + freerdp_thread_free(scard->thread); + + while ((irp = (IRP*)list_dequeue(scard->irp_list)) != NULL) + irp->Discard(irp); + list_free(scard->irp_list); + + xfree(dev); + return; +} + + +static void +scard_process_irp(SCARD_DEVICE* scard, IRP* irp) +{ + switch (irp->MajorFunction) + { + case IRP_MJ_DEVICE_CONTROL: + scard_device_control(scard, irp); + break; + + default: + printf("MajorFunction 0x%X unexpected for smartcards.", irp->MajorFunction); + DEBUG_WARN("Smartcard MajorFunction 0x%X not supported.", irp->MajorFunction); + irp->IoStatus = STATUS_NOT_SUPPORTED; + irp->Complete(irp); + break; + } +} + + +static void +scard_process_irp_list(SCARD_DEVICE* scard) +{ + IRP *irp; + + while (!freerdp_thread_is_stopped(scard->thread)) + { + freerdp_thread_lock(scard->thread); + irp = (IRP *) list_dequeue(scard->irp_list); + freerdp_thread_unlock(scard->thread); + + if (irp == NULL) + break; + + scard_process_irp(scard, irp); + } +} + + +struct scard_irp_thread_args { + SCARD_DEVICE* scard; + IRP* irp; + freerdp_thread* thread; +}; + + +static void +scard_process_irp_thread_func(struct scard_irp_thread_args* args) +{ + scard_process_irp(args->scard, args->irp); + + freerdp_thread_free(args->thread); + xfree(args); +} + + +static void * +scard_thread_func(void* arg) +{ + SCARD_DEVICE* scard = (SCARD_DEVICE*) arg; + + while (1) + { + freerdp_thread_wait(scard->thread); + + if (freerdp_thread_is_stopped(scard->thread)) + break; + + freerdp_thread_reset(scard->thread); + scard_process_irp_list(scard); + } + + freerdp_thread_quit(scard->thread); + + return NULL; +} + + +static void +scard_irp_request(DEVICE* device, IRP* irp) +{ + SCARD_DEVICE* scard = (SCARD_DEVICE*)device; + + if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL && + scard_async_op(irp)) + { + /* + * certain potentially long running operations + * get their own thread + * TODO: revise this mechanism.. maybe worker pool + */ + struct scard_irp_thread_args *args = xmalloc(sizeof(struct scard_irp_thread_args)); + + + args->thread = freerdp_thread_new(); + args->scard = scard; + args->irp = irp; + freerdp_thread_start(args->thread, scard_process_irp_thread_func, args); + + return; + } + + freerdp_thread_lock(scard->thread); + list_enqueue(scard->irp_list, irp); + freerdp_thread_unlock(scard->thread); + + freerdp_thread_signal(scard->thread); +} + + +int +DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + SCARD_DEVICE* scard; + char* name; + char* path; + int i, length; + + name = (char *)pEntryPoints->plugin_data->data[1]; + path = (char *)pEntryPoints->plugin_data->data[2]; + + if (name) + { + /* TODO: check if server supports sc redirect (version 5.1) */ + + scard = xnew(SCARD_DEVICE); + + scard->device.type = RDPDR_DTYP_SMARTCARD; + scard->device.name = "SCARD"; + scard->device.IRPRequest = scard_irp_request; + scard->device.Free = scard_free; + + length = strlen(scard->device.name); + scard->device.data = stream_new(length + 1); + + for (i = 0; i <= length; i++) + stream_write_uint8(scard->device.data, name[i] < 0 ? '_' : name[i]); + + scard->path = path; + + scard->irp_list = list_new(); + scard->thread = freerdp_thread_new(); + + pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE *)scard); + + freerdp_thread_start(scard->thread, scard_thread_func, scard); + } + + return 0; +} diff --git a/channels/rdpdr/smartcard/scard_main.h b/channels/rdpdr/smartcard/scard_main.h new file mode 100644 index 000000000..7813f486c --- /dev/null +++ b/channels/rdpdr/smartcard/scard_main.h @@ -0,0 +1,52 @@ +/* + FreeRDP: A Remote Desktop Protocol client. + Redirected Smart Card Device Service + + Copyright 2011 O.S. Systems Software Ltda. + Copyright 2011 Eduardo Fiss Beloni + + 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 __SCARD_MAIN_H +#define __SCARD_MAIN_H + +#include + +#include "devman.h" +#include "rdpdr_types.h" +#include + +struct _SCARD_DEVICE +{ + DEVICE device; + + char * name; + char * path; + + LIST* irp_list; + + freerdp_thread* thread; +}; +typedef struct _SCARD_DEVICE SCARD_DEVICE; + +#ifdef WITH_DEBUG_SCARD +#define DEBUG_SCARD(fmt, ...) DEBUG_CLASS(SCARD, fmt, ## __VA_ARGS__) +#else +#define DEBUG_SCARD(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__) +#endif + +boolean scard_async_op(IRP*); +void scard_device_control(SCARD_DEVICE*, IRP*); + +#endif diff --git a/channels/rdpdr/smartcard/scard_operations.c b/channels/rdpdr/smartcard/scard_operations.c new file mode 100644 index 000000000..7bce705d5 --- /dev/null +++ b/channels/rdpdr/smartcard/scard_operations.c @@ -0,0 +1,1547 @@ +/* + FreeRDP: A Remote Desktop Protocol client. + Redirected Smart Card Device Service + + Copyright (C) Alexi Volkov 2006 + Copyright 2011 O.S. Systems Software Ltda. + Copyright 2011 Anthony Tong + + 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. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "rdpdr_types.h" +#include "rdpdr_constants.h" + +#include "scard_main.h" + + +/* [MS-RDPESC] 3.1.4 */ +#define SCARD_IOCTL_ESTABLISH_CONTEXT 0x00090014 /* EstablishContext */ +#define SCARD_IOCTL_RELEASE_CONTEXT 0x00090018 /* ReleaseContext */ +#define SCARD_IOCTL_IS_VALID_CONTEXT 0x0009001C /* IsValidContext */ +#define SCARD_IOCTL_LIST_READER_GROUPS 0x00090020 /* ListReaderGroups */ +#define SCARD_IOCTL_LIST_READERS 0x00090028 /* ListReadersA */ +#define SCARD_IOCTL_INTRODUCE_READER_GROUP 0x00090050 /* IntroduceReaderGroup */ +#define SCARD_IOCTL_FORGET_READER_GROUP 0x00090058 /* ForgetReader */ +#define SCARD_IOCTL_INTRODUCE_READER 0x00090060 /* IntroduceReader */ +#define SCARD_IOCTL_FORGET_READER 0x00090068 /* IntroduceReader */ +#define SCARD_IOCTL_ADD_READER_TO_GROUP 0x00090070 /* AddReaderToGroup */ +#define SCARD_IOCTL_REMOVE_READER_FROM_GROUP 0x00090078 /* RemoveReaderFromGroup */ +#define SCARD_IOCTL_GET_STATUS_CHANGE 0x000900A0 /* GetStatusChangeA */ +#define SCARD_IOCTL_CANCEL 0x000900A8 /* Cancel */ +#define SCARD_IOCTL_CONNECT 0x000900AC /* ConnectA */ +#define SCARD_IOCTL_RECONNECT 0x000900B4 /* Reconnect */ +#define SCARD_IOCTL_DISCONNECT 0x000900B8 /* Disconnect */ +#define SCARD_IOCTL_BEGIN_TRANSACTION 0x000900BC /* BeginTransaction */ +#define SCARD_IOCTL_END_TRANSACTION 0x000900C0 /* EndTransaction */ +#define SCARD_IOCTL_STATE 0x000900C4 /* State */ +#define SCARD_IOCTL_STATUS 0x000900C8 /* StatusA */ +#define SCARD_IOCTL_TRANSMIT 0x000900D0 /* Transmit */ +#define SCARD_IOCTL_CONTROL 0x000900D4 /* Control */ +#define SCARD_IOCTL_GETATTRIB 0x000900D8 /* GetAttrib */ +#define SCARD_IOCTL_SETATTRIB 0x000900DC /* SetAttrib */ +#define SCARD_IOCTL_ACCESS_STARTED_EVENT 0x000900E0 /* SCardAccessStartedEvent */ +#define SCARD_IOCTL_LOCATE_CARDS_BY_ATR 0x000900E8 /* LocateCardsByATR */ + + +#define SCARD_INPUT_LINKED 0xFFFFFFFF + + +static uint32 +sc_output_string(IRP* irp, char *src, boolean wide) +{ + uint8* p; + uint32 len; + + p = stream_get_tail(irp->output); + len = strlen(src) + 1; + + if (wide) + { + // len = mbstowcs(p, src, len); // need to setlocale for 2byte wchar.. + int i; + for (i = 0; i < len; i++ ) + { + p[2 * i] = src[i] < 0 ? '?' : src[i]; + p[2 * i + 1] = '\0'; + } + + len *= 2; + } + else + { + memcpy(p, src, len); + } + + stream_seek(irp->output, len); + return len; +} + + +static void +sc_output_alignment(IRP *irp, uint32 seed) +{ + uint32 size = stream_get_length(irp->output) - 20; + uint32 add = (seed - (size % seed)) % seed; + + if (add > 0) + stream_write_zero(irp->output, add); +} + + +static void +sc_output_repos(IRP* irp, uint32 written) +{ + uint32 add = (4 - (written % 4)) % 4; + + if (add > 0) + stream_write_zero(irp->output, add); +} + + +static uint32 +sc_output_return(IRP* irp, uint32 rv) +{ + stream_write_zero(irp->output, 256); + return rv; +} + + +static void +sc_output_buffer_limit(IRP* irp, char *buffer, unsigned int length, unsigned int highLimit) +{ + int header = (length < 0) ? (0) : ((length > highLimit) ? (highLimit) : (length)); + + stream_write_uint32(irp->output, header); + + if (length <= 0) + { + stream_write_uint32(irp->output, 0); + } + else + { + if (header < length) + length = header; + + stream_write(irp->output, buffer, length); + sc_output_repos(irp, length); + } +} + + +static void +sc_output_buffer(IRP* irp, char *buffer, unsigned int length) +{ + sc_output_buffer_limit(irp, buffer, length, 0x7FFFFFFF); +} + + +static void +sc_output_buffer_start_limit(IRP *irp, int length, int highLimit) +{ + int header = (length < 0) ? (0) : ((length > highLimit) ? (highLimit) : (length)); + + stream_write_uint32(irp->output, header); + stream_write_uint32(irp->output, 0x00000001); /* Magic DWORD - any non zero */ +} + + +static void +sc_output_buffer_start(IRP *irp, int length) +{ + sc_output_buffer_start_limit(irp, length, 0x7FFFFFFF); +} + + +static uint32 +sc_input_string(IRP* irp, char **dest, uint32 dataLength, boolean wide) +{ + char *buffer; + int bufferSize; + + bufferSize = wide ? (2 * dataLength) : dataLength; + buffer = xmalloc(bufferSize + 2); /* reserve 2 bytes for the '\0' */ + + stream_read(irp->input, buffer, bufferSize); + if (wide) + { + int i; + for (i = 0; i < dataLength; i++) + { + if ((buffer[2 * i] < 0) || (buffer[2 * i + 1] != 0)) + buffer[i] = '?'; + else + buffer[i] = buffer[2 * i]; + } + } + + buffer[dataLength] = '\0'; + *dest = buffer; + + return bufferSize; +} + + +static void +sc_input_repos(IRP* irp, uint32 read) +{ + uint32 add = 4 - (read % 4); + + if (add < 4 && add > 0) + stream_seek(irp->input, add); +} + + +static void +sc_input_reader_name(IRP* irp, char **dest, boolean wide) +{ + uint32 dataLength; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, dataLength); + + DEBUG_SCARD("datalength %d", dataLength); + sc_input_repos(irp, sc_input_string(irp, dest, dataLength, wide)); +} + + +static void +sc_input_skip_linked(IRP* irp) +{ + uint32 len; + stream_read_uint32(irp->input, len); + + if (len > 0) + { + stream_seek(irp->input, len); + sc_input_repos(irp, len); + } +} + + +static uint32 +sc_map_state(uint32 state) +{ + // is this mapping still needed? + if (state & SCARD_SPECIFIC) + state = 0x00000006; + else if (state & SCARD_NEGOTIABLE) + state = 0x00000005; + else if (state & SCARD_POWERED) + state = 0x00000004; + else if (state & SCARD_SWALLOWED) + state = 0x00000003; + else if (state & SCARD_PRESENT) + state = 0x00000002; + else if (state & SCARD_ABSENT) + state = 0x00000001; + else + state = 0x00000000; + + return state; +} + + +static uint32 +handle_EstablishContext(IRP* irp) +{ + uint32 len, rv; + uint32 scope; + SCARDCONTEXT hContext = -1; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, len); + if (len != 8) + return SCARD_F_INTERNAL_ERROR; + + stream_seek_uint32(irp->input); + stream_read_uint32(irp->input, scope); + + rv = SCardEstablishContext(scope, NULL, NULL, &hContext); + + stream_write_uint32(irp->output, 4); // ? + stream_write_uint32(irp->output, -1); // ? + + stream_write_uint32(irp->output, 4); + stream_write_uint32(irp->output, hContext); + + // TODO: store hContext in allowed context list + + return SCARD_S_SUCCESS; +} + + +static uint32 +handle_ReleaseContext(IRP* irp) +{ + uint32 len, rv; + SCARDCONTEXT hContext = -1; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, len); + // if (len != 16) return SCARD_F_INTERNAL_ERROR; + + stream_seek(irp->input, 0x10); + stream_read_uint32(irp->input, hContext); + + rv = SCardReleaseContext(hContext); + if (rv) + DEBUG_SCARD("%s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("success 0x%08lx", hContext); + + return rv; +} + + +static uint32 +handle_IsValidContext(IRP* irp) +{ + uint32 rv; + SCARDCONTEXT hContext; + + stream_seek(irp->input, 0x1c); + stream_read_uint32(irp->input, hContext); + + rv = SCardIsValidContext(hContext); + + if (rv) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success context: 0x%08x", (unsigned) hContext); + + stream_write_uint32(irp->output, rv); + + return rv; +} + + +static uint32 +handle_ListReaders(IRP* irp, boolean wide) +{ + uint32 len, rv; + SCARDCONTEXT hContext; + DWORD dwReaders; + char *readerList = NULL, *walker; + int elemLength, dataLength; + int pos, poslen1, poslen2; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, len); + + stream_seek(irp->input, 0x1c); + stream_read_uint32(irp->input, len); + + if (len != 4) + return SCARD_F_INTERNAL_ERROR; + + stream_read_uint32(irp->input, hContext); + + /* ignore rest of [MS-RDPESC] 2.2.2.4 ListReaders_Call */ + + rv = SCARD_S_SUCCESS; +#ifdef SCARD_AUTOALLOCATE + dwReaders = SCARD_AUTOALLOCATE; + rv = SCardListReaders(hContext, NULL, (LPSTR) &readerList, &dwReaders); +#else + rv = SCardListReaders(hContext, NULL, NULL, &dwReaders); + + readerList = xmalloc(dwReaders); + rv = SCardListReaders(hContext, NULL, readerList, &dwReaders); +#endif + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + return rv; + } + + DEBUG_SCARD("Success 0x%08x %d %d", (unsigned) hContext, (unsigned) cchReaders, (int) strlen(readerList)); + + poslen1 = stream_get_pos(irp->output); + stream_seek_uint32(irp->output); + + stream_write_uint32(irp->output, 0x01760650); + + poslen2 = stream_get_pos(irp->output); + stream_seek_uint32(irp->output); + + walker = readerList; + dataLength = 0; + while (1) + { + elemLength = strlen(walker); + if (elemLength == 0) + break; + + dataLength += sc_output_string(irp, walker, wide); + walker += elemLength + 1; + elemLength = strlen(walker); + } + + dataLength += sc_output_string(irp, "\0", wide); + + pos = stream_get_pos(irp->output); + + stream_set_pos(irp->output, poslen1); + stream_write_uint32(irp->output, dataLength); + stream_set_pos(irp->output, poslen2); + stream_write_uint32(irp->output, dataLength); + + stream_set_pos(irp->output, pos); + + sc_output_repos(irp, dataLength); + sc_output_alignment(irp, 8); + +#ifdef SCARD_AUTOALLOCATE + SCardFreeMemory(hContext, readerList); +#else + xfree(readerList); +#endif + + return rv; +} + + +static uint32 +handle_GetStatusChange(IRP* irp, boolean wide) +{ + LONG rv; + SCARDCONTEXT hContext; + DWORD dwTimeout = 0; + DWORD readerCount = 0; + SCARD_READERSTATE *readerStates, *cur; + int i; + + stream_seek(irp->input, 0x18); + stream_read_uint32(irp->input, dwTimeout); + stream_read_uint32(irp->input, readerCount); + + stream_seek(irp->input, 8); + + stream_read_uint32(irp->input, hContext); + + stream_seek(irp->input, 4); + + DEBUG_SCARD("context: 0x%08x, timeout: 0x%08x, count: %d", + (unsigned) hContext, (unsigned) dwTimeout, (int) readerCount); + if (readerCount > 0) + { + readerStates = xzalloc(readerCount * sizeof(SCARD_READERSTATE)); + if (!readerStates) + return sc_output_return(irp, SCARD_E_NO_MEMORY); + + + for (i = 0; i < readerCount; i++) + { + cur = &readerStates[i]; + + stream_seek(irp->input, 4); + + /* + * TODO: on-wire is little endian; need to either + * convert to host endian or fix the headers to + * request the order we want + */ + stream_read_uint32(irp->input, cur->dwCurrentState); + stream_read_uint32(irp->input, cur->dwEventState); + stream_read_uint32(irp->input, cur->cbAtr); + stream_read(irp->input, cur->rgbAtr, 32); + + stream_seek(irp->input, 4); + + /* reset high bytes? */ + cur->dwCurrentState &= 0x0000FFFF; + cur->dwEventState &= 0x0000FFFF; + cur->dwEventState = 0; + } + + for (i = 0; i < readerCount; i++) + { + cur = &readerStates[i]; + uint32 dataLength; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, dataLength); + sc_input_repos(irp, sc_input_string(irp, (char **) &cur->szReader, dataLength, wide)); + + DEBUG_SCARD(" \"%s\"", cur->szReader ? cur->szReader : "NULL"); + DEBUG_SCARD(" user: 0x%08x, state: 0x%08x, event: 0x%08x", + (unsigned) cur->pvUserData, (unsigned) cur->dwCurrentState, + (unsigned) cur->dwEventState); + + if (strcmp(cur->szReader, "\\\\?PnP?\\Notification") == 0) + cur->dwCurrentState |= SCARD_STATE_IGNORE; + } + } + else + { + readerStates = NULL; + } + + rv = SCardGetStatusChange(hContext, (DWORD) dwTimeout, readerStates, (DWORD) readerCount); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success"); + + stream_write_uint32(irp->output, readerCount); + stream_write_uint32(irp->output, 0x00084dd8); + stream_write_uint32(irp->output, readerCount); + + for (i = 0; i < readerCount; i++) + { + cur = &readerStates[i]; + + DEBUG_SCARD(" \"%s\"", cur->szReader ? cur->szReader : "NULL"); + DEBUG_SCARD(" user: 0x%08x, state: 0x%08x, event: 0x%08x\n", + (unsigned) cur->pvUserData, (unsigned) cur->dwCurrentState, + (unsigned) cur->dwEventState); + + /* TODO: do byte conversions if necessary */ + stream_write_uint32(irp->output, cur->dwCurrentState); + stream_write_uint32(irp->output, cur->dwEventState); + stream_write_uint32(irp->output, cur->cbAtr); + stream_write(irp->output, cur->rgbAtr, 32); + + stream_write_zero(irp->output, 4); + + xfree((void *)cur->szReader); + } + + sc_output_alignment(irp, 8); + + xfree(readerStates); + return rv; +} + + +static uint32 +handle_Cancel(IRP *irp) // TESTME +{ + LONG rv; + SCARDCONTEXT hContext; + + stream_seek(irp->input, 0x1c); + stream_read_uint32(irp->input, hContext); + + rv = SCardCancel(hContext); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)\n", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success context: 0x%08x %s\n", (unsigned) hContext, pcsc_stringify_error(rv)); + + sc_output_alignment(irp, 8); + return rv; +} + + +static uint32 +handle_Connect(IRP* irp, boolean wide) +{ + LONG rv; + SCARDCONTEXT hContext; + char *readerName = NULL; + DWORD dwShareMode = 0; + DWORD dwPreferredProtocol = 0; + DWORD dwActiveProtocol = 0; + SCARDHANDLE hCard; + + stream_seek(irp->input, 0x1c); + stream_read_uint32(irp->input, dwShareMode); + stream_read_uint32(irp->input, dwPreferredProtocol); + + sc_input_reader_name(irp, &readerName, wide); + + stream_seek(irp->input, 4); + stream_read_uint32(irp->input, hContext); + + DEBUG_SCARD("(context: 0x%08x, share: 0x%08x, proto: 0x%08x, reader: \"%s\")", + (unsigned) hContext, (unsigned) dwShareMode, + (unsigned) dwPreferredProtocol, readerName ? readerName : "NULL"); + + rv = SCardConnect(hContext, readerName, (DWORD) dwShareMode, + (DWORD) dwPreferredProtocol, &hCard, (DWORD *) &dwActiveProtocol); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s 0x%08x", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success 0x%08x", (unsigned) hCard); + + stream_write_uint32(irp->output, 0x00000000); + stream_write_uint32(irp->output, 0x00000000); + stream_write_uint32(irp->output, 0x00000004); + stream_write_uint32(irp->output, 0x016Cff34); + stream_write_uint32(irp->output, dwActiveProtocol); + stream_write_uint32(irp->output, 0x00000004); + stream_write_uint32(irp->output, hCard); + stream_seek(irp->output, 28); + + sc_output_alignment(irp, 8); + + xfree(readerName); + return rv; +} + + +static uint32 +handle_Reconnect(IRP* irp) // TESTME +{ + LONG rv; + SCARDCONTEXT hContext; + SCARDHANDLE hCard; + DWORD dwShareMode = 0; + DWORD dwPreferredProtocol = 0; + DWORD dwInitialization = 0; + DWORD dwActiveProtocol = 0; + + stream_seek(irp->input, 0x20); + stream_read_uint32(irp->input, dwShareMode); + stream_read_uint32(irp->input, dwPreferredProtocol); + stream_read_uint32(irp->input, dwInitialization); + + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, hContext); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, hCard); + + DEBUG_SCARD("(context: 0x%08x, hcard: 0x%08x, share: 0x%08x, proto: 0x%08x, init: 0x%08x)", + (unsigned) hContext, (unsigned) hCard, + (unsigned) dwShareMode, (unsigned) dwPreferredProtocol, (unsigned) dwInitialization); + + rv = SCardReconnect(hCard, (DWORD) dwShareMode, (DWORD) dwPreferredProtocol, + (DWORD) dwInitialization, (LPDWORD) &dwActiveProtocol); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success (proto: 0x%08x)", (unsigned) dwActiveProtocol); + + sc_output_alignment(irp, 8); + stream_write_uint32(irp->output, dwActiveProtocol); // reversed? + + return rv; +} + + +static uint32 +handle_Disconnect(IRP* irp) +{ + LONG rv; + SCARDCONTEXT hContext; + SCARDHANDLE hCard; + DWORD dwDisposition = 0; + + stream_seek(irp->input, 0x20); + stream_read_uint32(irp->input, dwDisposition); + stream_seek(irp->input, 4); + stream_read_uint32(irp->input, hContext); + stream_seek(irp->input, 4); + stream_read_uint32(irp->input, hCard); + + DEBUG_SCARD("(context: 0x%08x, hcard: 0x%08x, disposition: 0x%08x)", + (unsigned) hContext, (unsigned) hCard, (unsigned) dwDisposition); + + rv = SCardDisconnect(hCard, (DWORD) dwDisposition); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success"); + + sc_output_alignment(irp, 8); + + return rv; +} + + +static uint32 +handle_BeginTransaction(IRP* irp) +{ + LONG rv; + SCARDCONTEXT hCard; + + stream_seek(irp->input, 0x30); + stream_read_uint32(irp->input, hCard); + + rv = SCardBeginTransaction(hCard); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success hcard: 0x%08x", (unsigned) hCard); + + sc_output_alignment(irp, 8); + + return rv; +} + + +static uint32 +handle_EndTransaction(IRP* irp) +{ + LONG rv; + SCARDCONTEXT hCard; + DWORD dwDisposition = 0; + + stream_seek(irp->input, 0x20); + stream_read_uint32(irp->input, dwDisposition); + + stream_seek(irp->input, 0x0C); + stream_read_uint32(irp->input, hCard); + + rv = SCardEndTransaction(hCard, dwDisposition); + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success hcard: 0x%08x", (unsigned) hCard); + + sc_output_alignment(irp, 8); + return rv; +} + + +static uint32 +handle_State(IRP* irp) // TESTME +{ + LONG rv; + SCARDHANDLE hCard; + DWORD state = 0, protocol = 0; + DWORD readerLen; + DWORD atrLen = MAX_ATR_SIZE; + char * readerName; + BYTE pbAtr[MAX_ATR_SIZE]; + +#ifdef WITH_DEBUG_SCARD + int i; +#endif + + stream_seek(irp->input, 0x24); + stream_seek_uint32(irp->input); // atrLen + + stream_seek(irp->input, 0x0c); + stream_read_uint32(irp->input, hCard); + stream_seek(irp->input, 0x04); + +#ifdef SCARD_AUTOALLOCATE + readerLen = SCARD_AUTOALLOCATE; + + rv = SCardStatus(hCard, (LPSTR) &readerName, &readerLen, &state, &protocol, pbAtr, &atrLen); +#else + readerLen = 256; + readerName = xmalloc(readerLen); + + rv = SCardStatus(hCard, (LPSTR) readerName, &readerLen, &state, &protocol, pbAtr, &atrLen); +#endif + + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + return sc_output_return(irp, rv); + } + + DEBUG_SCARD("Success (hcard: 0x%08x len: %d state: 0x%08x, proto: 0x%08x)", + (unsigned) hCard, (int) atrLen, (unsigned) state, (unsigned) protocol); + +#ifdef WITH_DEBUG_SCARD + printf(" ATR: "); + for (i = 0; i < atrLen; i++) + printf("%02x%c", pbAtr[i], (i == atrLen - 1) ? ' ' : ':'); + printf("\n"); +#endif + + state = sc_map_state(state); + + stream_write_uint32(irp->output, state); + stream_write_uint32(irp->output, protocol); + stream_write_uint32(irp->output, atrLen); + stream_write_uint32(irp->output, 0x00000001); + stream_write_uint32(irp->output, atrLen); + stream_write(irp->output, pbAtr, atrLen); + + sc_output_repos(irp, atrLen); + sc_output_alignment(irp, 8); + +#ifdef SCARD_AUTOALLOCATE + SCard +#else + xfree(readerName); +#endif + + return rv; +} + + +static DWORD +handle_Status(IRP *irp, boolean wide) +{ + LONG rv; + SCARDHANDLE hCard; + DWORD state, protocol; + DWORD readerLen = 0; + DWORD atrLen = 0; + char * readerName; + BYTE pbAtr[MAX_ATR_SIZE]; + uint32 dataLength; + int pos, poslen1, poslen2; + +#ifdef WITH_DEBUG_SCARD + int i; +#endif + + stream_seek(irp->input, 0x24); + stream_read_uint32(irp->input, readerLen); + stream_read_uint32(irp->input, atrLen); + stream_seek(irp->input, 0x0c); + stream_read_uint32(irp->input, hCard); + stream_seek(irp->input, 0x4); + + atrLen = MAX_ATR_SIZE; + +#ifdef SCARD_AUTOALLOCATE + readerLen = SCARD_AUTOALLOCATE; + + rv = SCardStatus(hCard, (LPSTR) &readerName, &readerLen, &state, &protocol, pbAtr, &atrLen); +#else + readerLen = 256; + readerName = xmalloc(readerLen); + + rv = SCardStatus(hCard, (LPSTR) readerName, &readerLen, &state, &protocol, pbAtr, &atrLen); +#endif + + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + return sc_output_return(irp, rv); + } + + DEBUG_SCARD("Success (state: 0x%08x, proto: 0x%08x)", (unsigned) state, (unsigned) protocol); + DEBUG_SCARD(" Reader: \"%s\"", readerName ? readerName : "NULL"); + +#ifdef WITH_DEBUG_SCARD + printf(" ATR: "); + for (i = 0; i < atrLen; i++) + printf("%02x%c", pbAtr[i], (i == atrLen - 1) ? ' ' : ':'); + printf("\n"); +#endif + + state = sc_map_state(state); + + poslen1 = stream_get_pos(irp->output); + stream_write_uint32(irp->output, readerLen); + stream_write_uint32(irp->output, 0x00020000); + stream_write_uint32(irp->output, state); + stream_write_uint32(irp->output, protocol); + stream_write(irp->output, pbAtr, atrLen); + + if (atrLen < 32) + stream_write_zero(irp->output, 32 - atrLen); + stream_write_uint32(irp->output, atrLen); + + poslen2 = stream_get_pos(irp->output); + stream_write_uint32(irp->output, readerLen); + + dataLength = sc_output_string(irp, readerName, wide); + dataLength += sc_output_string(irp, "\0", wide); + sc_output_repos(irp, dataLength); + + pos = stream_get_pos(irp->output); + stream_set_pos(irp->output, poslen1); + stream_write_uint32(irp->output,dataLength); + stream_set_pos(irp->output, poslen2); + stream_write_uint32(irp->output,dataLength); + stream_set_pos(irp->output, pos); + + sc_output_alignment(irp, 8); + +#ifdef SCARD_AUTOALLOCATE + //SCardFreeMemory(NULL, readerName); + free(readerName); +#else + xfree(readerName); +#endif + + return rv; +} + + +static uint32 +handle_Transmit(IRP* irp) +{ + LONG rv; + SCARDCONTEXT hCard; + uint32 map[7], linkedLen; + SCARD_IO_REQUEST pioSendPci, pioRecvPci, *pPioRecvPci; + DWORD cbSendLength = 0, cbRecvLength = 0; + BYTE *sendBuf = NULL, *recvBuf = NULL; + + stream_seek(irp->input, 0x14); + stream_read_uint32(irp->input, map[0]); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, map[1]); + + stream_read_uint32(irp->input, pioSendPci.dwProtocol); + stream_read_uint32(irp->input, pioSendPci.cbPciLength); + + stream_read_uint32(irp->input, map[2]); + stream_read_uint32(irp->input, cbSendLength); + stream_read_uint32(irp->input, map[3]); + stream_read_uint32(irp->input, map[4]); + stream_read_uint32(irp->input, map[5]); + stream_read_uint32(irp->input, cbRecvLength); + + if (map[0] & SCARD_INPUT_LINKED) + sc_input_skip_linked(irp); + + stream_seek(irp->input, 4); + stream_read_uint32(irp->input, hCard); + + if (map[2] & SCARD_INPUT_LINKED) + { + /* sendPci */ + stream_read_uint32(irp->input, linkedLen); + + stream_read_uint32(irp->input, pioSendPci.dwProtocol); + stream_seek(irp->input, linkedLen - 4); + + sc_input_repos(irp, linkedLen); + } + pioSendPci.cbPciLength = sizeof(SCARD_IO_REQUEST); + + if (map[3] & SCARD_INPUT_LINKED) + { + /* send buffer */ + stream_read_uint32(irp->input, linkedLen); + + sendBuf = xmalloc(linkedLen); + stream_read(irp->input, sendBuf, linkedLen); + sc_input_repos(irp, linkedLen); + } + + if (cbRecvLength) + recvBuf = xmalloc(cbRecvLength); + + if (map[4] & SCARD_INPUT_LINKED) + { + /* recvPci */ + stream_read_uint32(irp->input, linkedLen); + + stream_read_uint32(irp->input, pioRecvPci.dwProtocol); + stream_seek(irp->input, linkedLen - 4); + + sc_input_repos(irp, linkedLen); + + stream_read_uint32(irp->input, map[6]); + if (map[6] & SCARD_INPUT_LINKED) + { + // not sure what this is + stream_read_uint32(irp->input, linkedLen); + stream_seek(irp->input, linkedLen); + + sc_input_repos(irp, linkedLen); + } + pioRecvPci.cbPciLength = sizeof(SCARD_IO_REQUEST); + pPioRecvPci = &pioRecvPci; + } + else + { + pPioRecvPci = NULL; + } + pPioRecvPci = NULL; + + DEBUG_SCARD("SCardTransmit(hcard: 0x%08lx, send: %d bytes, recv: %d bytes)", + (long unsigned) hCard, (int) cbSendLength, (int) cbRecvLength); + + rv = SCardTransmit(hCard, &pioSendPci, sendBuf, cbSendLength, + pPioRecvPci, recvBuf, &cbRecvLength); + + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + } + else + { + DEBUG_SCARD("Success (%d bytes)", (int) cbRecvLength); + + stream_write_uint32(irp->output, 0); /* pioRecvPci 0x00; */ + + sc_output_buffer_start(irp, cbRecvLength); /* start of recvBuf output */ + + sc_output_buffer(irp, (char *) recvBuf, cbRecvLength); + } + + sc_output_alignment(irp, 8); + + xfree(sendBuf); + xfree(recvBuf); + + return rv; +} + + +static uint32 +handle_Control(IRP* irp) // TESTME +{ + LONG rv; + SCARDCONTEXT hContext; + SCARDHANDLE hCard; + uint32 map[3]; + uint32 controlCode; + BYTE *recvBuffer = NULL, *sendBuffer = NULL; + uint32 recvLength; + DWORD nBytesReturned; + DWORD outBufferSize; + + stream_seek(irp->input, 0x14); + stream_read_uint32(irp->input, map[0]); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, map[1]); + stream_read_uint32(irp->input, controlCode); + stream_read_uint32(irp->input, recvLength); + stream_read_uint32(irp->input, map[2]); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, outBufferSize); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, hContext); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, hCard); + + if (map[2] & SCARD_INPUT_LINKED) + { + /* read real input size */ + stream_read_uint32(irp->input, recvLength); + + recvBuffer = xmalloc(recvLength); + if (!recvBuffer) + return sc_output_return(irp, SCARD_E_NO_MEMORY); + + stream_read(irp->input, recvBuffer, recvLength); + } + + nBytesReturned = outBufferSize; + sendBuffer = xmalloc(outBufferSize); + if (!sendBuffer) + return sc_output_return(irp, SCARD_E_NO_MEMORY); + + rv = SCardControl(hCard, (DWORD) controlCode, recvBuffer, (DWORD) recvLength, + sendBuffer, (DWORD) outBufferSize, &nBytesReturned); + + if (rv != SCARD_S_SUCCESS) + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned) rv); + else + DEBUG_SCARD("Success (out: %u bytes)", (unsigned) nBytesReturned); + + stream_write_uint32(irp->output, (uint32) nBytesReturned); + stream_write_uint32(irp->output, 0x00000004); + stream_write_uint32(irp->output, nBytesReturned); + + if (nBytesReturned > 0) + { + stream_write(irp->output, sendBuffer, nBytesReturned); + sc_output_repos(irp, nBytesReturned); + } + + sc_output_alignment(irp, 8); + + xfree(recvBuffer); + xfree(sendBuffer); + + return rv; +} + + +static uint32 +handle_GetAttrib(IRP* irp) +{ + LONG rv; + SCARDHANDLE hCard; + DWORD dwAttrId = 0, dwAttrLen = 0; + DWORD attrLen = 0; + unsigned char *pbAttr; + + stream_seek(irp->input, 0x20); + stream_read_uint32(irp->input, dwAttrId); + stream_seek(irp->input, 0x4); + stream_read_uint32(irp->input, dwAttrLen); + stream_seek(irp->input, 0xc); + stream_read_uint32(irp->input, hCard); + + DEBUG_SCARD("hcard: 0x%08x, attrib: 0x%08x (%d bytes)\n", + (unsigned) hCard, (unsigned) dwAttrId, (int) dwAttrLen); + +#ifdef SCARD_AUTOALLOCATE + if(dwAttrLen == 0) + { + attrLen = 0; + } + else + { + attrLen = SCARD_AUTOALLOCATE; + } +#endif + + rv = SCardGetAttrib(hCard, dwAttrId, attrLen == 0 ? NULL : (unsigned char *)&pbAttr, &attrLen); + + if(dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_A && rv == SCARD_E_UNSUPPORTED_FEATURE) + { + rv = SCardGetAttrib(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_W, + attrLen == 0 ? NULL : (unsigned char *)&pbAttr, &attrLen); + } + if(dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_W && rv == SCARD_E_UNSUPPORTED_FEATURE) + { + rv = SCardGetAttrib(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_A, + attrLen == 0 ? NULL : (unsigned char *)&pbAttr, &attrLen); + } + if(attrLen > dwAttrLen && pbAttr != NULL) + { + rv = SCARD_E_INSUFFICIENT_BUFFER; + } + dwAttrLen = attrLen; + + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", pcsc_stringify_error(rv), (unsigned int) rv); + free(pbAttr); + return sc_output_return(irp, rv); + } + else + { + DEBUG_SCARD("Success (%d bytes)", (int) dwAttrLen); + + stream_write_uint32(irp->output, dwAttrLen); + stream_write_uint32(irp->output, 0x00000200); + stream_write_uint32(irp->output, dwAttrLen); + + if (!pbAttr) + { + stream_write_zero(irp->output, dwAttrLen); + } + else + { + stream_write(irp->output, pbAttr, dwAttrLen); + } + sc_output_repos(irp, dwAttrLen); + // Align to multiple of 4 + stream_write_uint32(irp->output, 0); + } + sc_output_alignment(irp, 8); + + xfree(pbAttr); + + return rv; +} + + +static uint32 +handle_AccessStartedEvent(IRP* irp) +{ + stream_write_zero(irp->output, 8); + return SCARD_S_SUCCESS; +} + + +void +scard_error(SCARD_DEVICE* scard, IRP* irp, uint32 ntstatus) +{ + // [MS-RDPESC] 3.1.4.4 + printf("scard processing error %x\n", ntstatus); + + stream_set_pos(irp->output, 0); // CHECKME + irp->IoStatus = ntstatus; + irp->Complete(irp); +} + + +/* http://msdn.microsoft.com/en-gb/library/ms938473.aspx */ +typedef struct _SERVER_SCARD_ATRMASK +{ + uint32 cbAtr; + unsigned char rgbAtr[36]; + unsigned char rgbMask[36]; +} +SERVER_SCARD_ATRMASK; + +static uint32 +handle_LocateCardsByATR(IRP* irp, boolean wide) // TESTME +{ + int i, j, k; + LONG rv; + SCARDCONTEXT hContext; + SERVER_SCARD_ATRMASK *pAtrMasks, *curAtr; + uint32 atrMaskCount = 0; + uint32 readerCount = 0; + SCARD_READERSTATE *readerStates, *rsCur, *cur; + + stream_seek(irp->input, 0x2c); + stream_read_uint32(irp->input, hContext); + stream_read_uint32(irp->input, atrMaskCount); + + pAtrMasks = xmalloc(atrMaskCount * sizeof(SERVER_SCARD_ATRMASK)); + if (!pAtrMasks) + return sc_output_return(irp, SCARD_E_NO_MEMORY); + + for (i = 0; i < atrMaskCount; i++) + { + stream_read_uint32(irp->input, pAtrMasks[i].cbAtr); + stream_read(irp->input, pAtrMasks[i].rgbAtr, 36); + stream_read(irp->input, pAtrMasks[i].rgbMask, 36); + } + + stream_read_uint32(irp->input, readerCount); + + readerStates = xzalloc(readerCount * sizeof(SCARD_READERSTATE)); + if (!readerStates) + return sc_output_return(irp, SCARD_E_NO_MEMORY); + + for (i = 0; i < readerCount; i++) + { + cur = &readerStates[i]; + + stream_seek(irp->input, 4); + + /* + * TODO: on-wire is little endian; need to either + * convert to host endian or fix the headers to + * request the order we want + */ + stream_read_uint32(irp->input, cur->dwCurrentState); + stream_read_uint32(irp->input, cur->dwEventState); + stream_read_uint32(irp->input, cur->cbAtr); + stream_read(irp->input, cur->rgbAtr, 32); + + stream_seek(irp->input, 4); + + /* reset high bytes? */ + cur->dwCurrentState &= 0x0000FFFF; + cur->dwEventState &= 0x0000FFFF; + cur->dwEventState = 0; + } + + for (i = 0; i < readerCount; i++) + { + cur = &readerStates[i]; + uint32 dataLength; + + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, dataLength); + sc_input_repos(irp, sc_input_string(irp, (char **) &cur->szReader, dataLength, wide)); + + DEBUG_SCARD(" \"%s\"", cur->szReader ? cur->szReader : "NULL"); + DEBUG_SCARD(" user: 0x%08x, state: 0x%08x, event: 0x%08x", + (unsigned) cur->pvUserData, (unsigned) cur->dwCurrentState, + (unsigned) cur->dwEventState); + + if (strcmp(cur->szReader, "\\\\?PnP?\\Notification") == 0) + cur->dwCurrentState |= SCARD_STATE_IGNORE; + } + + rv = SCardGetStatusChange(hContext, 0x00000001, readerStates, readerCount); + if (rv != SCARD_S_SUCCESS) + { + DEBUG_SCARD("Failure: %s (0x%08x)", + pcsc_stringify_error(rv), (unsigned) rv); + + return sc_output_return(irp, rv); + } + + DEBUG_SCARD("Success"); + for (i = 0, curAtr = pAtrMasks; i < atrMaskCount; i++, curAtr++) + { + for (j = 0, rsCur = readerStates; j < readerCount; j++, rsCur++) + { + boolean equal = 1; + for (k = 0; k < cur->cbAtr; k++) + { + if ((curAtr->rgbAtr[k] & curAtr->rgbMask[k]) != + (rsCur->rgbAtr[k] & curAtr->rgbMask[k])) + { + equal = 0; + break; + } + } + if (equal) + { + rsCur->dwEventState |= 0x00000040; /* SCARD_STATE_ATRMATCH 0x00000040 */ + } + } + } + + stream_write_uint32(irp->output, readerCount); + stream_write_uint32(irp->output, 0x00084dd8); + stream_write_uint32(irp->output, readerCount); + + for (i = 0, rsCur = readerStates; i < readerCount; i++, rsCur++) + { + stream_write_uint32(irp->output, cur->dwCurrentState); + stream_write_uint32(irp->output, cur->dwEventState); + stream_write_uint32(irp->output, cur->cbAtr); + stream_write(irp->output, cur->rgbAtr, 32); + + stream_write_zero(irp->output, 4); + + xfree((void *)cur->szReader); + } + + sc_output_alignment(irp, 8); + + free(readerStates); + + return rv; +} + + +boolean +scard_async_op(IRP* irp) +{ + uint32 ioctl_code; + + /* peek ahead */ + stream_seek(irp->input, 8); + stream_read_uint32(irp->input, ioctl_code); + stream_rewind(irp->input, 12); + + switch (ioctl_code) + { + /* non-blocking events */ + case SCARD_IOCTL_ACCESS_STARTED_EVENT: + + case SCARD_IOCTL_ESTABLISH_CONTEXT: + case SCARD_IOCTL_RELEASE_CONTEXT: + case SCARD_IOCTL_IS_VALID_CONTEXT: + + return False; + break; + + /* async events */ + case SCARD_IOCTL_GET_STATUS_CHANGE: + case SCARD_IOCTL_GET_STATUS_CHANGE + 4: + + case SCARD_IOCTL_TRANSMIT: + + case SCARD_IOCTL_STATUS: + case SCARD_IOCTL_STATUS + 4: + return True; + break; + + default: + break; + } + + /* default to async */ + return True; +} + + +void +scard_device_control(SCARD_DEVICE* scard, IRP* irp) +{ + uint32 output_len, input_len, ioctl_code; + uint32 stream_len, result; + uint32 pos, pad_len; + uint32 irp_len; + uint32 irp_result_pos, output_len_pos, result_pos; + + stream_read_uint32(irp->input, output_len); + stream_read_uint32(irp->input, input_len); + stream_read_uint32(irp->input, ioctl_code); + + stream_seek(irp->input, 20); /* padding */ + + // stream_seek(irp->input, 4); /* TODO: parse len, le, v1 */ + // stream_seek(irp->input, 4); /* 0xcccccccc */ + // stream_seek(irp->input, 4); /* rpce len */ + + /* [MS-RDPESC] 3.2.5.1 Sending Outgoing Messages */ + stream_extend(irp->output, 2048); + + irp_result_pos = stream_get_pos(irp->output); + + stream_write_uint32(irp->output, 0x00081001); /* len 8, LE, v1 */ + + /* [MS-RPCE] 2.2.6.1 */ + stream_write_uint32(irp->output, 0x00081001); /* len 8, LE, v1 */ + stream_write_uint32(irp->output, 0xcccccccc); /* filler */ + + output_len_pos = stream_get_pos(irp->output); + stream_seek(irp->output, 4); /* size */ + + stream_write_uint32(irp->output, 0x0); /* filler */ + + result_pos = stream_get_pos(irp->output); + stream_seek(irp->output, 4); /* result */ + + /* body */ + switch (ioctl_code) + { + case SCARD_IOCTL_ESTABLISH_CONTEXT: + result = handle_EstablishContext(irp); + break; + + case SCARD_IOCTL_IS_VALID_CONTEXT: + result = handle_IsValidContext(irp); + break; + + case SCARD_IOCTL_RELEASE_CONTEXT: + result = handle_ReleaseContext(irp); + break; + + case SCARD_IOCTL_LIST_READERS: + result = handle_ListReaders(irp, 0); + break; + case SCARD_IOCTL_LIST_READERS + 4: + result = handle_ListReaders(irp, 1); + break; + + case SCARD_IOCTL_LIST_READER_GROUPS: + case SCARD_IOCTL_LIST_READER_GROUPS + 4: + // typically not used unless list_readers fail + result = SCARD_F_INTERNAL_ERROR; + break; + + case SCARD_IOCTL_GET_STATUS_CHANGE: + result = handle_GetStatusChange(irp, 0); + break; + case SCARD_IOCTL_GET_STATUS_CHANGE + 4: + result = handle_GetStatusChange(irp, 1); + break; + + case SCARD_IOCTL_CANCEL: + result = handle_Cancel(irp); + break; + + case SCARD_IOCTL_CONNECT: + result = handle_Connect(irp, 0); + break; + case SCARD_IOCTL_CONNECT + 4: + result = handle_Connect(irp, 1); + break; + + case SCARD_IOCTL_RECONNECT: + result = handle_Reconnect(irp); + break; + + case SCARD_IOCTL_DISCONNECT: + result = handle_Disconnect(irp); + break; + + case SCARD_IOCTL_BEGIN_TRANSACTION: + result = handle_BeginTransaction(irp); + break; + + case SCARD_IOCTL_END_TRANSACTION: + result = handle_EndTransaction(irp); + break; + + case SCARD_IOCTL_STATE: + result = handle_State(irp); + break; + + case SCARD_IOCTL_STATUS: + result = handle_Status(irp, 0); + break; + case SCARD_IOCTL_STATUS + 4: + result = handle_Status(irp, 1); + break; + + case SCARD_IOCTL_TRANSMIT: + result = handle_Transmit(irp); + break; + + case SCARD_IOCTL_CONTROL: + result = handle_Control(irp); + break; + + case SCARD_IOCTL_GETATTRIB: + result = handle_GetAttrib(irp); + break; + + case SCARD_IOCTL_ACCESS_STARTED_EVENT: + result = handle_AccessStartedEvent(irp); + break; + + case SCARD_IOCTL_LOCATE_CARDS_BY_ATR: + result = handle_LocateCardsByATR(irp, 0); + break; + case SCARD_IOCTL_LOCATE_CARDS_BY_ATR + 4: + result = handle_LocateCardsByATR(irp, 1); + break; + + default: + result = 0xc0000001; + printf("scard unknown ioctl 0x%x\n", ioctl_code); + break; + } + + /* look for NTSTATUS errors */ + if ((result & 0xc0000000) == 0xc0000000) + return scard_error(scard, irp, result); + + /* per Ludovic Rousseau, map different usage of this particular + * error code between pcsc-lite & windows */ + if (result == 0x8010001F) + result = 0x80100022; + + /* handle response packet */ + pos = stream_get_pos(irp->output); + stream_len = pos - irp_result_pos - 4; + + stream_set_pos(irp->output, output_len_pos); + stream_write_uint32(irp->output, stream_len - 24); + + stream_set_pos(irp->output, result_pos); + stream_write_uint32(irp->output, result); + + stream_set_pos(irp->output, pos); + + /* pad stream to 16 byte align */ + pad_len = stream_len % 16; + stream_write_zero(irp->output, pad_len); + pos = stream_get_pos(irp->output); + irp_len = stream_len + pad_len; + + stream_set_pos(irp->output, irp_result_pos); + stream_write_uint32(irp->output, irp_len); + stream_set_pos(irp->output, pos); + +#ifdef WITH_DEBUG_SCARD + freerdp_hexdump(stream_get_data(irp->output), stream_get_length(irp->output)); +#endif + irp->IoStatus = 0; + + irp->Complete(irp); + +} + diff --git a/cmake/FindPCSC.cmake b/cmake/FindPCSC.cmake new file mode 100644 index 000000000..a113a0298 --- /dev/null +++ b/cmake/FindPCSC.cmake @@ -0,0 +1,3 @@ +pkg_check_modules(PCSC libpcsclite) + +# TODO: pcsc version diff --git a/config.h.in b/config.h.in index 9b7c01f26..8b4895baa 100644 --- a/config.h.in +++ b/config.h.in @@ -34,5 +34,6 @@ #cmakedefine WITH_DEBUG_X11 #cmakedefine WITH_DEBUG_RAIL #cmakedefine WITH_DEBUG_XV +#cmakedefine WITH_DEBUG_SCARD #endif From 0f69c586da4e3d4b7ba9d3c4deceafcabcb7f5a5 Mon Sep 17 00:00:00 2001 From: Anthony Tong Date: Sat, 15 Oct 2011 11:32:12 -0500 Subject: [PATCH 07/10] fix xf_gdi_bitmap_update() memory leak --- client/X11/xf_gdi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/X11/xf_gdi.c b/client/X11/xf_gdi.c index 0926c0495..1528cfa9d 100644 --- a/client/X11/xf_gdi.c +++ b/client/X11/xf_gdi.c @@ -308,6 +308,9 @@ void xf_gdi_bitmap_update(rdpUpdate* update, BITMAP_UPDATE* bitmap) XPutImage(xfi->display, xfi->primary, xfi->gc, image, 0, 0, x, y, w, h); + XFree(image); + xfree(data); + if (xfi->remote_app != True) XCopyArea(xfi->display, xfi->primary, xfi->drawable, xfi->gc, x, y, w, h, x, y); From 416e506c61a4832f3488cd8910b278a2c372eff5 Mon Sep 17 00:00:00 2001 From: Anthony Tong Date: Sat, 15 Oct 2011 18:25:34 -0500 Subject: [PATCH 08/10] bring back freerdp.pc --- CMakeLists.txt | 4 ++++ freerdp.pc.in | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 freerdp.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index e83f838d8..e04aa5089 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,10 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # Configure files configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +# Generate pkg-config +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/freerdp.pc.in ${CMAKE_CURRENT_BINARY_DIR}/freerdp.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freerdp.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + # Mac OS X if(APPLE) include_directories(/opt/local/include) diff --git a/freerdp.pc.in b/freerdp.pc.in new file mode 100644 index 000000000..d9896d49b --- /dev/null +++ b/freerdp.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@ +includedir=@CMAKE_INSTALL_PREFIX@/include + +Name: FreeRDP +Description: A free remote desktop protocol client +URL: http://www.freerdp.com/ +Version: @FREERDP_VERSION_FULL@ +Requires: +Libs: -L${libdir} -lfreerdp-core -lfreerdp-gdi -lfreerdp-kbd -lfreerdp-rail -lfreerdp-chanman -lfreerdp-utils +Cflags: -I${includedir} + From c70fe10f26d3a20cd6fe4a8a24e92b8751a53443 Mon Sep 17 00:00:00 2001 From: Anthony Tong Date: Sat, 15 Oct 2011 18:47:20 -0500 Subject: [PATCH 09/10] add freerdp-codec --- freerdp.pc.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freerdp.pc.in b/freerdp.pc.in index d9896d49b..7c524daeb 100644 --- a/freerdp.pc.in +++ b/freerdp.pc.in @@ -8,6 +8,6 @@ Description: A free remote desktop protocol client URL: http://www.freerdp.com/ Version: @FREERDP_VERSION_FULL@ Requires: -Libs: -L${libdir} -lfreerdp-core -lfreerdp-gdi -lfreerdp-kbd -lfreerdp-rail -lfreerdp-chanman -lfreerdp-utils +Libs: -L${libdir} -lfreerdp-core -lfreerdp-codec -lfreerdp-gdi -lfreerdp-kbd -lfreerdp-rail -lfreerdp-chanman -lfreerdp-utils Cflags: -I${includedir} From 575ed162c37b886f0ad414a802210444329eeb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Sat, 15 Oct 2011 21:09:19 -0400 Subject: [PATCH 10/10] docs: started visio document for FreeRDP --- docs/FreeRDP.vsd | Bin 0 -> 154624 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/FreeRDP.vsd diff --git a/docs/FreeRDP.vsd b/docs/FreeRDP.vsd new file mode 100644 index 0000000000000000000000000000000000000000..cc017d4be3dba3d8057fffa9b7440e450094bbbf GIT binary patch literal 154624 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