/* BSD-2-Clause license * * Copyright (c) 2018-2023 NST , sss . * */ /* this code mostly ported from XFreeRDP */ #include #include #include "rdp_clipboard.h" #include "rdp_ft.h" static UINT rdp_clip_ServerCapabilities( CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities) { UINT32 i; const CLIPRDR_CAPABILITY_SET *caps; const CLIPRDR_GENERAL_CAPABILITY_SET *generalCaps; const BYTE *capsPtr = (const BYTE *)capabilities->capabilitySets; my_rdp_clipboard *clipboard = (my_rdp_clipboard *)context->custom; clipboard->streams_supported = false; for (i = 0; i < capabilities->cCapabilitiesSets; i++) { caps = (const CLIPRDR_CAPABILITY_SET *)capsPtr; if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) { generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET *)caps; if (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED) { clipboard->streams_supported = true; } } capsPtr += caps->capabilitySetLength; } { const char *msg = "rdp_module: cliprdr: server capabilities received"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientCapabilities(CliprdrClientContext* context, CLIPRDR_CAPABILITIES* capabilities) { return CHANNEL_RC_OK; }*/ static UINT rdp_cliprdr_send_client_capabilities(my_rdp_clipboard *clipboard) { CLIPRDR_CAPABILITIES capabilities; CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; capabilities.cCapabilitiesSets = 1; capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet); generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; generalCapabilitySet.capabilitySetLength = 12; generalCapabilitySet.version = CB_CAPS_VERSION_2; generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; /*|CB_CAN_LOCK_CLIPDATA; */ if (clipboard->streams_supported) { generalCapabilitySet.generalFlags |= CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS | 0x00000020; } { const char *msg = "rdp_module: cliprdr: client capabilities sent"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } return clipboard->clip_context->ClientCapabilities( clipboard->clip_context, &capabilities); } static UINT rdp_cliprdr_send_client_format_list(my_rdp_clipboard *clipboard) { const uint8_t format_count = 2; CLIPRDR_FORMAT formats[format_count]; CLIPRDR_FORMAT_LIST formatList; memset(formats, 0, sizeof(CLIPRDR_FORMAT) * format_count); formats[0].formatId = CF_RAW; formats[1].formatId = CF_TEXT; formatList.common.msgFlags = CB_RESPONSE_OK; formatList.numFormats = format_count; formatList.formats = formats; { const char *msg = "rdp_module: cliprdr: client format list sent"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } return clipboard->clip_context->ClientFormatList( clipboard->clip_context, &formatList); } static UINT rdp_clip_MonitorReady( CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady) { my_rdp_clipboard *clipboard = context->custom; UINT ret; if ((ret = rdp_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK) return ret; if ((ret = rdp_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK) return ret; /* clipboard->sync = true; */ { const char *msg = "rdp_module: cliprdr: monitor ready message handled"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_TempDirectory(CliprdrClientContext* context, CLIPRDR_TEMP_DIRECTORY* tempDirectory) { return CHANNEL_RC_OK; } */ /* unused for now * static UINT rdp_clip_ClientFormatList(CliprdrClientContext* context, CLIPRDR_FORMAT_LIST* formatList) { return CHANNEL_RC_OK; }*/ static UINT rdp_cliprdr_send_client_format_list_response( my_rdp_clipboard *clipboard, BOOL status) { CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE; formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; formatListResponse.common.dataLen = 0; return clipboard->clip_context->ClientFormatListResponse( clipboard->clip_context, &formatListResponse); } static wrdp_enum_clip_format clip_format_from_string(const char *fmt) { if (!strcmp(fmt, "FileGroupDescriptorW")) { return clip_format_file_list; } /* * unused else if (!strcmp(fmt, "FileContents")) { } else if (!strcmp(fmt, "Preffered DropEffect")) { } */ return clip_format_unsupported; } static wrdp_enum_clip_format clip_format_from_id(UINT32 id, my_rdp_clipboard *c) { if (id == CF_TEXT || id == CF_OEMTEXT) { return clip_format_text; } else if (id == CF_RAW) { return clip_format_raw; } else if (id == CF_UNICODETEXT) { return clip_format_unicode; } else if (id == CB_FORMAT_TEXTURILIST) { return clip_format_file_list; } return clip_format_unsupported; } static UINT rdp_clip_ServerFormatList( CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList) { UINT32 i; uint8_t num_supported_fmts = 0, *out_fmts = 0, num_server_formats; my_rdp_clipboard *clipboard = context->custom; my_clip_format *server_formats = 0; UINT ret; if (clipboard->my_rdp_context->ft_to_server->is_runing) { const char *msg = "rdp_module: clipboard: new clipboard formats list " "during runing filetransfer"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); /* return CHANNEL_RC_OK; */ } num_server_formats = formatList->numFormats + 1; /* +1 for CF_RAW */ if (!(server_formats = calloc(num_server_formats, sizeof(my_clip_format)))) { char buf[128]; snprintf(buf, 127, "failed to allocate %d my_clip_format structs", num_server_formats); clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0); return CHANNEL_RC_NO_MEMORY; } if (clipboard->srv_fmts && clipboard->srv_fmts_count) { for (i = 0; i < clipboard->srv_fmts_count; ++i) { if (clipboard->srv_fmts[i].rdp_fmt.formatName) { free(clipboard->srv_fmts[i].rdp_fmt.formatName); } } free(clipboard->srv_fmts); } clipboard->srv_fmts_count = formatList->numFormats; for (i = 0; i < formatList->numFormats; i++) { CLIPRDR_FORMAT *format = &formatList->formats[i]; server_formats[i].rdp_fmt.formatId = format->formatId; wrdp_enum_clip_format fmt = clip_format_from_id(format->formatId, clipboard); if (format->formatName) { server_formats[i].rdp_fmt.formatName = strdup(format->formatName); } if (fmt == clip_format_unsupported && format->formatName) { fmt = clip_format_from_string(format->formatName); } if (fmt != clip_format_unsupported) { num_supported_fmts++; } server_formats[i].my_fmt = fmt; } /* CF_RAW is always implicitly supported by the server */ { my_clip_format *format = &server_formats[formatList->numFormats]; format->rdp_fmt.formatId = CF_RAW; format->rdp_fmt.formatName = NULL; format->my_fmt = clip_format_raw; num_supported_fmts++; } { out_fmts = calloc(num_supported_fmts, sizeof(uint8_t)); if (!out_fmts) { perror("calloc"); if (server_formats) { free(server_formats); } return CHANNEL_RC_NO_MEMORY; } uint8_t pos = 0; for (i = 0; i < num_supported_fmts; ++i) { if (server_formats[i].my_fmt == clip_format_unsupported) { continue; } out_fmts[pos] = server_formats[i].my_fmt; pos++; } clipboard->my_rdp_context->my_internals->core->api_clipboard ->clipboard_changed(out_fmts, num_supported_fmts, clipboard->my_rdp_context->my_internals->task_info); free(out_fmts); clipboard->srv_fmts = server_formats; } { const char *msg = "rdp_module: cliprdr: server format list" " changed message received"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } ret = rdp_cliprdr_send_client_format_list_response(clipboard, TRUE); return ret; } /* unused for now * static UINT rdp_clip_ClientFormatListResponse(CliprdrClientContext* context, CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) { return CHANNEL_RC_OK; }*/ static UINT rdp_clip_ServerFormatListResponse(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) { return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientLockClipboardData(CliprdrClientContext* context, CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) { return CHANNEL_RC_OK; }*/ static UINT rdp_clip_ServerLockClipboardData(CliprdrClientContext *context, const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData) { /* my_rdp_clipboard* clipboard = context->custom; clipboard->my_rdp_context->ft_to_server->clip_data_id = lockClipboardData->clipDataId; */ return CHANNEL_RC_OK; } /* * unused static UINT rdp_clip_ClientUnlockClipboardData(CliprdrClientContext* context, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) { return CHANNEL_RC_OK; } */ static UINT rdp_clip_ServerUnlockClipboardData(CliprdrClientContext *context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData) { /* TODO: */ return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientFormatDataRequest(CliprdrClientContext* context, CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) { return CHANNEL_RC_OK; }*/ static UINT rdp_clip_ServerFormatDataRequest(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) { my_rdp_clipboard *clipboard = context->custom; wrdp_enum_clip_format fmt = clip_format_from_id( formatDataRequest->requestedFormatId, clipboard); clipboard->my_rdp_context->my_internals->core->api_clipboard ->request_data( fmt, clipboard->my_rdp_context->my_internals->task_info); return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientFormatDataResponse(CliprdrClientContext* context, CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) { return CHANNEL_RC_OK; }*/ /*static UINT rdp_cliprdr_send_data_response(my_rdp_clipboard* clipboard, BYTE* data, int size) { CLIPRDR_FORMAT_DATA_RESPONSE response; ZeroMemory(&response, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); response.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; response.dataLen = size; response.requestedFormatData = data; return clipboard->clip_context->ClientFormatDataResponse( clipboard->clip_context, &response); } */ static UINT rdp_clip_ServerFormatDataResponse(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) { UINT32 size = formatDataResponse->common.dataLen; const BYTE *data = formatDataResponse->requestedFormatData; my_rdp_clipboard *clipboard = context->custom; if (!data) { const char *msg = "rdp_module: cliprdr: failed to get clipboard data"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return CHANNEL_RC_OK; } switch (clipboard->cli_req_fmt_id.my_fmt) { case clip_format_file_list: { /* TODO: check this */ UINT error = NO_ERROR; FILEDESCRIPTORW *file_array = 0; UINT32 file_count = 0, pos = 0; uint8_t *msg_struct = 0, *msg = 0; wrdp_backend_ft_list_entry *list = 0; size_t msg_size_result = 0; error = cliprdr_parse_file_list( data, size, &file_array, &file_count); if (error) { char buf[128]; snprintf(buf, 127, "failed to deserialize" " CLIPRDR_FILELIST: 0x%08X", error); clipboard->my_rdp_context->my_internals->core ->api_utils->log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0); return CHANNEL_RC_OK; } msg_struct = calloc( file_count, sizeof(wrdp_backend_ft_list_entry)); if (!msg_struct) { perror("calloc"); if (file_count && file_array) { free(file_array); } return CHANNEL_RC_OK; } msg_size_result = (file_count * sizeof(wrdp_backend_ft_list_entry)) + 3; char *filename_array[file_count]; { int i = 0; for (; i < file_count; ++i) { filename_array[i] = 0; } } list = (wrdp_backend_ft_list_entry *)msg_struct; for (; file_array && pos < file_count; ++pos) { list[pos].file_id = pos; if (file_array[pos].cFileName[0]) { iconv_t utf16_to_utf8 = iconv_open("UTF-8", "UTF-16LE"); char *ptr_w, *ptr_r, *tmp_buf = calloc(520, sizeof(char)); size_t name_len_utf16 = 260 * sizeof(WCHAR), name_len_utf8 = 520, name_len_result = 0; if (!tmp_buf) { perror("calloc"); iconv_close(utf16_to_utf8); free(msg_struct); if (file_count) { int i = 0; for (; (i < file_count) && filename_array [i]; ++i) { free( filename_array [i]); } free(file_array); } return CHANNEL_RC_OK; } ptr_w = tmp_buf; ptr_r = (char *)file_array[pos].cFileName; while (name_len_utf16 && name_len_utf8) { iconv(utf16_to_utf8, &ptr_r, &name_len_utf16, &ptr_w, &name_len_utf8); } #ifdef DEBUG if (!name_len_utf8 && name_len_utf16) { log_msg_info i = {0}; i.level = wrdp_log_level_trace; i.buf = (const uint8_t *)"rdp_module: " "cliprdr: iconv: not " "sufficient space in " "output buffer"; i.task_info = clipboard->my_internals ->task_info; clipboard->my_internals->core ->api_utils->log_msg_ex(&i); } #endif iconv(utf16_to_utf8, 0, 0, &ptr_w, &name_len_utf8); name_len_result = strlen(tmp_buf); msg_size_result += name_len_result; list[pos].filename_len = name_len_result; filename_array[pos] = strdup(tmp_buf); iconv_close(utf16_to_utf8); free(tmp_buf); } /* TODO: check/change endianness */ memcpy(&(list[pos].file_size), &file_array[pos].nFileSizeLow, 4); memcpy(((uint8_t *)&(list[pos].file_size)) + 4, &file_array[pos].nFileSizeHigh, 4); } msg = malloc(msg_size_result); if (!msg) { perror("malloc"); for (pos = 0; pos < file_count; ++pos) { free(filename_array[pos]); } free(msg_struct); return CHANNEL_RC_OK; } memset(msg, 0, msg_size_result); *((uint8_t *)msg) = clip_format_file_list; memcpy(msg + 1, &file_count, 2); { size_t msg_pos = 3; for (pos = 0; pos < file_count; ++pos) { memcpy(msg + msg_pos, &list[pos].filename_len, 2); msg_pos += 2; memcpy(msg + msg_pos, &list[pos].file_id, 4); msg_pos += 4; memcpy(msg + msg_pos, &list[pos].file_size, 8); msg_pos += 8; /* TODO: looks like i have missed * something ... "Null pointer passed as * an argument to a 'nonnull' parameter" * just added null ptr check for now */ if (filename_array[pos]) { memcpy(msg + msg_pos, filename_array[pos], list[pos].filename_len); free(filename_array[pos]); } msg_pos += list[pos].filename_len; } } free(msg_struct); clipboard->my_rdp_context->my_internals->core ->api_clipboard->send_data((uint8_t *)msg, msg_size_result, clipboard->my_rdp_context->my_internals ->task_info); free(msg); if (file_count && file_array) { free(file_array); } return CHANNEL_RC_OK; } break; case clip_format_raw: case clip_format_text: { size_t msg_len = size + 1; uint8_t *msg = malloc(msg_len); if (!msg) { perror("malloc"); return CHANNEL_RC_OK; } *(uint8_t *)msg = clip_format_text; memcpy(msg + 1, data, size); clipboard->my_rdp_context->my_internals->core ->api_clipboard->send_data(msg, msg_len, clipboard->my_rdp_context->my_internals ->task_info); free(msg); } break; case clip_format_unicode: { iconv_t utf16_to_utf8 = iconv_open("UTF-8", "UTF-16LE"); size_t in_left = size, out_left = size; uint8_t *tmp_buf = malloc(out_left), *tmp_buf_ptr; uint8_t *msg = 0; if (!tmp_buf) { perror("malloc"); return CHANNEL_RC_OK; } tmp_buf_ptr = tmp_buf; /* wcstombs((char*)msg + 1, tmp_buf, text_len); */ while (in_left && out_left) { if (iconv(utf16_to_utf8, (char **)&data, &in_left, (char **)&tmp_buf_ptr, &out_left) == (size_t)-1) { log_msg_info mi = {0}; mi.level = wrdp_log_level_warning; mi.buf = (const uint8_t *)"rdp_module: cliprdr: iconv: " "failed to encode outgoing " "buffer from utf8 to utf16le"; mi.task_info = clipboard->my_internals ->task_info; clipboard->my_internals->core->api_utils ->log_msg_ex(&mi); free(tmp_buf); iconv_close(utf16_to_utf8); return CHANNEL_RC_NO_BUFFER; } } #ifdef DEBUG if (!out_left && in_left) { log_msg_info i = {0}; i.level = wrdp_log_level_trace; i.buf = (const uint8_t *)"rdp_module: cliprdr: iconv: not " "sufficient space in output buffer"; i.task_info = clipboard->my_internals->task_info; clipboard->my_internals->core->api_utils ->log_msg_ex(&i); } #endif iconv(utf16_to_utf8, 0, 0, (char **)&tmp_buf_ptr, &out_left); iconv_close(utf16_to_utf8); msg = malloc(size - out_left + 1); if (!msg) { perror("malloc"); return CHANNEL_RC_OK; } *(uint8_t *)msg = clip_format_unicode; memcpy(msg + 1, tmp_buf, size - out_left); #ifdef DEBUG { log_msg_info i = {0}; size_t len = 1024; char msg_buf[len]; snprintf(msg_buf, len - 1, "rdp_module: cliprdr: sending encoded from " "utf16le to utf8 text: %s", tmp_buf); i.level = wrdp_log_level_trace; i.buf = (const uint8_t *)msg_buf; i.task_info = clipboard->my_internals->task_info; clipboard->my_internals->core->api_utils ->log_msg_ex(&i); } #endif free(tmp_buf); clipboard->my_rdp_context->my_internals->core ->api_clipboard->send_data(msg, size - out_left + 1, clipboard->my_rdp_context->my_internals ->task_info); free(msg); } break; default: { const char *msg = "rdp_module: cliprdr: unsupported" " clipboardformat requested"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); } break; } return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientFileContentsRequest(CliprdrClientContext* context, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) { return CHANNEL_RC_OK; }*/ static UINT rdp_clip_ServerFileContentsRequest(CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest) { my_rdp_clipboard *clipboard = context->custom; if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) { CLIPRDR_FILE_CONTENTS_RESPONSE resp; if (fileContentsRequest->nPositionLow || fileContentsRequest->nPositionHigh || (fileContentsRequest->cbRequested != 0x00000008)) { const char *msg = "rdp_module: ft: ServerFileContentsRequest: " "malformed request"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return CHANNEL_RC_OK; } memset(&resp, 0, sizeof(CLIPRDR_FILE_CONTENTS_RESPONSE)); resp.common.msgType = CB_FILECONTENTS_RESPONSE; resp.common.msgFlags = CB_RESPONSE_OK; resp.streamId = fileContentsRequest->streamId; resp.cbRequested = sizeof(uint64_t); resp.requestedData = (BYTE *)&( clipboard ->client_filelist_cache[fileContentsRequest->listIndex] .file_size); clipboard->clip_context->ClientFileContentsResponse( clipboard->clip_context, &resp); } else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) { wrdp_backend_ft_file_request req; memset(&req, 0, sizeof(wrdp_backend_ft_list_entry)); /* TODO: better way to detect runing transfer */ clipboard->my_rdp_context->ft_to_server->is_runing = true; req.file_id = fileContentsRequest->listIndex; req.req_size = fileContentsRequest->cbRequested; clipboard->my_rdp_context->ft_to_server->transfer_id = fileContentsRequest->streamId; memcpy( &req.file_offset, &(fileContentsRequest->nPositionLow), 4); memcpy(((uint8_t *)&req.file_offset) + 4, &(fileContentsRequest->nPositionHigh), 4); clipboard->my_rdp_context->my_internals->core->api_filetransfers ->ft_request(&req, clipboard->my_rdp_context->my_internals->task_info); } return CHANNEL_RC_OK; } /* unused for now * static UINT rdp_clip_ClientFileContentsResponse(CliprdrClientContext* context, CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) { return CHANNEL_RC_OK; } */ static UINT rdp_clip_ServerFileContentsResponse( CliprdrClientContext *context, const CLIPRDR_FILE_CONTENTS_RESPONSE *resp) { /* TODO: somehow avoid memory copying here */ my_rdp_clipboard *clipboard = context->custom; wrdp_backend_ft_chunk chunk; if (resp->common.msgFlags & CB_RESPONSE_FAIL) { const char *msg = "rdp_module: ft: ServerFileContentsResponse: " "CB_RESPONSE_FAIL"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); goto transfer_failed; } if (!resp->cbRequested && (clipboard->my_rdp_context->ft_to_client->transferred != clipboard->my_rdp_context->ft_to_client->file_size)) { char msg[128]; snprintf(msg, 127, "rdp_module: ft: chunk with zero size received on" " position %lu of %lu", clipboard->my_rdp_context->ft_to_client->transferred, clipboard->my_rdp_context->ft_to_client->file_size); clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); goto transfer_failed; } else if (clipboard->my_rdp_context->ft_to_client->transfer_id != resp->streamId) { const char *msg = "rdp_module: clipboard: ft: transfer_id mismatch in " "ServerFileContentsResponse"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); goto transfer_failed; } else if (resp->cbRequested) { chunk.transfer_id = resp->streamId; chunk.size = resp->cbRequested; clipboard->my_rdp_context->my_internals->core->api_filetransfers ->ft_send_chunk(&chunk, (uint8_t *)(resp->requestedData), clipboard->my_rdp_context->my_internals->task_info); clipboard->my_rdp_context->ft_to_client->transferred += resp->cbRequested; } /* request next file chunk */ if (clipboard->my_rdp_context->ft_to_client->transferred < clipboard->my_rdp_context->ft_to_client->file_size) { my_rdp_context *c = clipboard->my_rdp_context; CLIPRDR_FILE_CONTENTS_REQUEST file_req; uint32_t chunk_len = 512000; uint64_t left = 0; memset(&file_req, 0, sizeof(CLIPRDR_FILE_CONTENTS_REQUEST)); left = c->ft_to_client->file_size - c->ft_to_client->transferred; if (left < chunk_len) { chunk_len = left; } file_req.cbRequested = chunk_len; c->ft_to_client->transfer_id = rand(); file_req.streamId = c->ft_to_client->transfer_id; file_req.listIndex = c->ft_to_client->file_id; file_req.dwFlags = FILECONTENTS_RANGE; /* TODO: check/change endianness */ memcpy(&(file_req.nPositionLow), &(c->ft_to_client->transferred), 4); memcpy(&(file_req.nPositionHigh), (((uint8_t *)&(c->ft_to_client->transferred)) + 4), 4); c->clipboard->clip_context->ClientFileContentsRequest( c->clipboard->clip_context, &file_req); } else if (clipboard->my_rdp_context->ft_to_client->is_runing == true) { my_rdp_context *c = clipboard->my_rdp_context; wrdp_backend_ft_status status; { char msg[128]; snprintf(msg, 127, "rdp_module: ft: status: transfer finished," " transfered size: %lu", clipboard->my_rdp_context->ft_to_client ->transferred); clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } memset(&status, 0, sizeof(wrdp_backend_ft_status)); status.status = ft_status_success; status.file_id = c->ft_to_client->file_id; status.transfer_id = c->ft_to_client->transfer_id; c->my_internals->core->api_filetransfers->ft_finish( &status, c->my_internals->task_info); clipboard->my_rdp_context->ft_to_client->file_size = 0; clipboard->my_rdp_context->ft_to_client->transfer_id = 0; clipboard->my_rdp_context->ft_to_client->file_id = 0; clipboard->my_rdp_context->ft_to_client->is_runing = false; clipboard->my_rdp_context->ft_to_client->transferred = 0; } else { const char *msg = "rdp_module: clipboard: ft:" " unexpected data chunk on finished transfer"; clipboard->my_rdp_context->my_internals->core->api_utils ->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); } return CHANNEL_RC_OK; transfer_failed: { my_rdp_context *c = clipboard->my_rdp_context; wrdp_backend_ft_status status; memset(&status, 0, sizeof(wrdp_backend_ft_status)); status.status = ft_status_failure; status.file_id = c->ft_to_client->file_id; status.transfer_id = c->ft_to_client->transfer_id; c->my_internals->core->api_filetransfers->ft_finish( &status, c->my_internals->task_info); clipboard->my_rdp_context->ft_to_client->file_size = 0; clipboard->my_rdp_context->ft_to_client->transfer_id = 0; clipboard->my_rdp_context->ft_to_client->file_id = 0; clipboard->my_rdp_context->ft_to_client->is_runing = false; clipboard->my_rdp_context->ft_to_client->transferred = 0; } return CHANNEL_RC_OK; } void rdp_cliprdr_init(my_rdp_context *ctx, CliprdrClientContext *cliprdr) { ctx->clipboard->clip_context = cliprdr; cliprdr->custom = (void *)ctx->clipboard; ctx->clipboard->my_internals = ctx->my_internals; cliprdr->MonitorReady = rdp_clip_MonitorReady; cliprdr->ServerCapabilities = rdp_clip_ServerCapabilities; cliprdr->ServerFormatList = rdp_clip_ServerFormatList; cliprdr->ServerFormatListResponse = rdp_clip_ServerFormatListResponse; cliprdr->ServerFormatDataRequest = rdp_clip_ServerFormatDataRequest; cliprdr->ServerFormatDataResponse = rdp_clip_ServerFormatDataResponse; cliprdr->ServerFileContentsRequest = rdp_clip_ServerFileContentsRequest; cliprdr->ServerFileContentsResponse = rdp_clip_ServerFileContentsResponse; cliprdr->ServerLockClipboardData = rdp_clip_ServerLockClipboardData; cliprdr->ServerUnlockClipboardData = rdp_clip_ServerUnlockClipboardData; } void rdp_cliprdr_uninit(my_rdp_context *ctx, CliprdrClientContext *cliprdr) { /* do not need to do it here, done during backend destruction */ /* cliprdr->custom = NULL; if (ctx->clipboard) ctx->clipboard->clip_context = NULL; ctx->clipboard = NULL; */ } /* unused for now * static UINT ClientRequestFileSize(wClipboardDelegate* delegate, const wClipboardFileSizeRequest* request) { return 0; }*/ static UINT ClipboardFileSizeSuccess(wClipboardDelegate *delegate, const wClipboardFileSizeRequest *request, UINT64 fileSize) { CLIPRDR_FILE_CONTENTS_RESPONSE response; my_rdp_clipboard *clipboard = delegate->custom; ZeroMemory(&response, sizeof(response)); response.common.msgFlags = CB_RESPONSE_OK; response.streamId = request->streamId; response.cbRequested = sizeof(UINT64); response.requestedData = (BYTE *)&fileSize; return clipboard->clip_context->ClientFileContentsResponse( clipboard->clip_context, &response); // return 0; } static UINT ClipboardFileSizeFailure(wClipboardDelegate *delegate, const wClipboardFileSizeRequest *request, UINT errorCode) { CLIPRDR_FILE_CONTENTS_RESPONSE response; my_rdp_clipboard *clipboard = delegate->custom; ZeroMemory(&response, sizeof(response)); response.common.msgFlags = CB_RESPONSE_FAIL; response.streamId = request->streamId; return clipboard->clip_context->ClientFileContentsResponse( clipboard->clip_context, &response); } /* unused for now * static UINT ClientRequestFileRange(wClipboardDelegate* delegate, const wClipboardFileRangeRequest* request) { return 0; } */ static UINT ClipboardFileRangeSuccess(wClipboardDelegate *delegate, const wClipboardFileRangeRequest *request, const BYTE *data, UINT32 size) { CLIPRDR_FILE_CONTENTS_RESPONSE response; my_rdp_clipboard *clipboard = delegate->custom; ZeroMemory(&response, sizeof(response)); response.common.msgFlags = CB_RESPONSE_OK; response.streamId = request->streamId; response.cbRequested = size; response.requestedData = (BYTE *)data; return clipboard->clip_context->ClientFileContentsResponse( clipboard->clip_context, &response); } static UINT ClipboardFileRangeFailure(wClipboardDelegate *delegate, const wClipboardFileRangeRequest *request, UINT errorCode) { CLIPRDR_FILE_CONTENTS_RESPONSE response; my_rdp_clipboard *clipboard = delegate->custom; ZeroMemory(&response, sizeof(response)); response.common.msgFlags = CB_RESPONSE_FAIL; response.streamId = request->streamId; return clipboard->clip_context->ClientFileContentsResponse( clipboard->clip_context, &response); } my_rdp_clipboard * rdp_clipboard_new(my_rdp_context *context) { my_rdp_clipboard *clip = calloc(1, sizeof(my_rdp_clipboard)); if (!clip) { perror("calloc"); return 0; } clip->my_rdp_context = context; clip->clipboard = ClipboardCreate(); clip->delegate = ClipboardGetDelegate(clip->clipboard); clip->delegate->custom = clip; clip->delegate->ClipboardFileSizeSuccess = ClipboardFileSizeSuccess; clip->delegate->ClipboardFileSizeFailure = ClipboardFileSizeFailure; clip->delegate->ClipboardFileRangeSuccess = ClipboardFileRangeSuccess; clip->delegate->ClipboardFileRangeFailure = ClipboardFileRangeFailure; context->clipboard = clip; return clip; } static UINT cliprdr_send_data_request(my_rdp_clipboard *clipboard) { CLIPRDR_FORMAT_DATA_REQUEST request; ZeroMemory(&request, sizeof(CLIPRDR_FORMAT_DATA_REQUEST)); request.requestedFormatId = clipboard->cli_req_fmt_id.rdp_fmt.formatId; return clipboard->clip_context->ClientFormatDataRequest( clipboard->clip_context, &request); } static bool clip_api_handle_request_data( const wrdp_backend_clipbrd_data_request *request, void *backend_internals) { rdp_internals *i = backend_internals; i->context->clipboard->cli_req_fmt_id.my_fmt = request->format; switch (request->format) { case clip_format_raw: { /* i->context->clipboard->cli_req_fmt_id = CF_RAW; * raw format does not work for some reason */ i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId = CF_TEXT; } break; case clip_format_text: { i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId = CF_TEXT; } break; case clip_format_file_list: { /* 4.5.3 Format Data Request PDU The following is an annotated dump of a Format Data Request PDU (section 2.2.5.1). The format being requested is the File List that was advertised in section 4.5.1 (the advertised ID in the Format List PDU was 49273). */ uint8_t p; bool format_found = false; for (p = 0; p < i->context->clipboard->srv_fmts_count; ++p) { if (!strcmp("FileGroupDescriptorW", i->context->clipboard->srv_fmts[p] .rdp_fmt.formatName)) { i->context->clipboard->cli_req_fmt_id .rdp_fmt.formatId = i->context->clipboard->srv_fmts[p] .rdp_fmt.formatId; format_found = true; break; } } if (!format_found) { const char *msg = "rdp_module: cliprdr: " "requested data format" " id not found"; i->core->api_utils->log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); i->context->clipboard->cli_req_fmt_id.rdp_fmt .formatId = CF_RAW; } } break; case clip_format_unicode: { i->context->clipboard->cli_req_fmt_id.rdp_fmt.formatId = CF_UNICODETEXT; } break; default: { const char *msg = "rdp_module: cliprdr: unsuported data format" " requested"; i->core->api_utils->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); } break; } cliprdr_send_data_request(i->context->clipboard); return true; } static bool clip_api_handle_data_changed( const wrdp_backend_clipbrd_fmts *fmts, void *backend_internals) { rdp_internals *i = backend_internals; my_rdp_clipboard *clip = i->context->clipboard; uint8_t pos = 0; CLIPRDR_FORMAT *formats = 0; CLIPRDR_FORMAT_LIST formatList; if (!fmts->count) { const char *msg = "received empty clipboard formats list"; i->core->api_utils->log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return false; } formats = calloc(fmts->count, sizeof(CLIPRDR_FORMAT)); if (!formats) { perror("calloc"); return false; } for (; pos < fmts->count; ++pos) { switch ((wrdp_enum_clip_format)fmts->formats[pos]) { case clip_format_raw: { formats[pos].formatId = CF_RAW; } break; case clip_format_text: { formats[pos].formatId = CF_TEXT; } break; case clip_format_unicode: { formats[pos].formatId = CF_UNICODETEXT; } break; case clip_format_file_list: { formats[pos].formatId = CB_FORMAT_TEXTURILIST; formats[pos].formatName = "FileGroupDescriptorW"; } break; default: { formats[pos].formatId = CF_RAW; } break; } } formatList.common.msgFlags = CB_RESPONSE_OK; formatList.numFormats = fmts->count; formatList.formats = formats; /* TODO: check return value of ClientFormatList */ clip->clip_context->ClientFormatList(clip->clip_context, &formatList); free(formats); return true; } static bool clip_api_handle_send_data( const wrdp_backend_clipbrd_data *data, void *backend_internals) { rdp_internals *i = backend_internals; my_rdp_clipboard *clip = i->context->clipboard; CLIPRDR_FORMAT_DATA_RESPONSE response; uint8_t type = data->data[0]; memset(&response, 0, sizeof(CLIPRDR_FORMAT_DATA_RESPONSE)); response.common.msgType = CB_FORMAT_DATA_RESPONSE; response.common.msgFlags = CB_RESPONSE_FAIL; response.common.dataLen = 0; response.requestedFormatData = 0; #ifdef DEBUG { const char *msg = "rdp_module: clipboard: from client: raw clipboard" " data:"; i->core->api_utils->log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); i->core->api_utils->log_msg(data->data, data->size, wrdp_log_level_trace, wrdp_log_flag_binary); } #endif switch (type) { case clip_format_raw: case clip_format_text: { if (data->size > 1) { response.common.msgFlags = CB_RESPONSE_OK; response.common.dataLen = data->size - 1; response.requestedFormatData = data->data + 1; } clip->clip_context->ClientFormatDataResponse( clip->clip_context, &response); return true; } break; case clip_format_unicode: { uint8_t *data_buf = 0; BYTE *msg_buf = 0; iconv_t utf8_to_utf16 = iconv_open("UTF-16LE", "UTF-8"); char *ptr_w, *ptr_r; size_t text_len_utf16 = (data->size - 1) * 2, text_len_utf8 = data->size - 1, text_len_result = text_len_utf16; data_buf = calloc(text_len_utf16, sizeof(uint8_t)); if (!data_buf) { perror("calloc"); return true; } ptr_w = (char *)data_buf; ptr_r = (char *)(data->data + 1); while (text_len_utf8 && text_len_utf16) { if (iconv(utf8_to_utf16, &ptr_r, &text_len_utf8, &ptr_w, &text_len_utf16) == (size_t)-1) { log_msg_info mi = {0}; free(data_buf); clip->clip_context ->ClientFormatDataResponse( clip->clip_context, &response); mi.level = wrdp_log_level_warning; mi.buf = (const uint8_t *)"rdp_module: cliprdr: iconv: " "failed to encode outgoing " "buffer from utf8 to utf16le"; mi.task_info = i->task_info; i->core->api_utils->log_msg_ex(&mi); iconv_close(utf8_to_utf16); return true; } } #ifdef DEBUG if (text_len_utf8 && !text_len_utf16) { log_msg_info mi = {0}; mi.level = wrdp_log_level_trace; mi.buf = (const uint8_t *)"rdp_module: cliprdr: iconv: not " "sufficient space in output buffer"; mi.task_info = i->task_info; i->core->api_utils->log_msg_ex(&mi); } #endif iconv(utf8_to_utf16, 0, 0, &ptr_w, &text_len_utf16); text_len_result = text_len_result - text_len_utf16; iconv_close(utf8_to_utf16); if (text_len_result) { msg_buf = calloc(text_len_result, sizeof(BYTE)); memcpy(msg_buf, data_buf, text_len_result); free(data_buf); response.common.msgFlags = CB_RESPONSE_OK; response.common.dataLen = text_len_result; response.requestedFormatData = msg_buf; } #ifdef DEBUG if (msg_buf) { log_msg_info mi = {0}; mi.level = wrdp_log_level_trace; mi.buf = (const uint8_t *)"rdp_module: clipboard: to server:" " utf16 encoded buffer"; mi.task_info = i->task_info; i->core->api_utils->log_msg_ex(&mi); mi.flags = wrdp_log_flag_binary; mi.buf = msg_buf; mi.buf_size = text_len_result; i->core->api_utils->log_msg_ex(&mi); } #endif clip->clip_context->ClientFormatDataResponse( clip->clip_context, &response); if (msg_buf) { free(msg_buf); } return true; } break; case clip_format_file_list: { uint16_t file_count; bool broken_msg = false; int _i; uint64_t data_offset = 3; FILEDESCRIPTORW *file_list = 0; BYTE *format_data = 0; UINT32 format_data_length = 0; memcpy(&file_count, data->data + 1, 2); if (!file_count) { const char *msg = "rdp_module: clipboard: ft: " "empty file_list received"; i->core->api_utils->log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return true; } if (clip->client_filelist_cache) { free(clip->client_filelist_cache); } clip->client_filelist_cache = calloc(file_count, sizeof(file_list_cache_entry)); if (!clip->client_filelist_cache) { perror("calloc"); return false; } file_list = calloc(file_count, sizeof(FILEDESCRIPTOR)); if (!file_list) { perror("calloc"); return false; } for (_i = 0; _i < file_count; ++_i) { uint16_t filename_len; uint32_t file_id; uint64_t file_size; char *filename, *ptr_w, *ptr_r; iconv_t utf8_to_utf16 = iconv_open("UTF-16LE", "UTF-8"); size_t text_len_utf16 = 260 * sizeof(WCHAR), text_len_utf8; memcpy( &filename_len, data->data + data_offset, 2); data_offset += 2; if (data_offset >= data->size) { broken_msg = true; break; } text_len_utf8 = filename_len; memcpy(&file_id, data->data + data_offset, 4); clip->client_filelist_cache[_i].file_id = file_id; data_offset += 4; if (data_offset >= data->size) { broken_msg = true; break; } memcpy(&file_size, data->data + data_offset, 8); clip->client_filelist_cache[_i].file_size = file_size; data_offset += 8; if ((data_offset >= data->size) && (_i < file_count)) { broken_msg = true; break; } filename = (char *)data->data + data_offset; ptr_r = filename; ptr_w = (char *)(file_list[_i].cFileName); iconv(utf8_to_utf16, &ptr_r, &text_len_utf8, &ptr_w, &text_len_utf16); iconv(utf8_to_utf16, 0, 0, &ptr_w, &text_len_utf16); iconv_close(utf8_to_utf16); data_offset += filename_len; file_list[_i].dwFileAttributes = FILE_ATTRIBUTE_NORMAL; file_list[_i].dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_SHOWPROGRESSUI; memcpy(&(file_list[_i].nFileSizeLow), &file_size, 4); memcpy(&(file_list[_i].nFileSizeHigh), ((uint8_t *)&file_size) + 4, 4); } if (broken_msg) { char msg[128]; snprintf(msg, 127, "rdp_module: cliprdr: error: wrong file" " list message size: %d, file count: %d\n", data->size, file_count); i->core->api_utils->log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); } else { UINT error = NO_ERROR; error = cliprdr_serialize_file_list(file_list, file_count, &format_data, &format_data_length); if (error) { char buf[128]; snprintf(buf, 127, "failed to serialize " "CLIPRDR_FILELIST:" " 0x%08X", error); i->core->api_utils->log_msg( (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0); } } free(file_list); if (format_data_length) { response.common.msgFlags = CB_RESPONSE_OK; response.common.dataLen = format_data_length; response.requestedFormatData = format_data; } clip->clip_context->ClientFormatDataResponse( clip->clip_context, &response); free(format_data); return true; } break; default: { response.common.msgFlags = CB_RESPONSE_FAIL; response.common.dataLen = 0; response.requestedFormatData = 0; clip->clip_context->ClientFormatDataResponse( clip->clip_context, &response); return true; } break; } /* TODO: check return code from ClientFormatDataResponse */ return true; } void register_clipboard(wrdp_backend_module *backend) { backend->callbacks_clipbrd->request_data = clip_api_handle_request_data; backend->callbacks_clipbrd->send_data = clip_api_handle_send_data; backend->callbacks_clipbrd->data_changed = clip_api_handle_data_changed; }