/** * xrdp: A Remote Desktop Protocol server. * * xrdp device redirection - only drive redirection is currently supported * * Copyright (C) Laxmikant Rashinkar 2013 LK.Rashinkar@gmail.com * * 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. */ /* * TODO * o there is one difference in the protocol initialization * sequence upon reconnection: if a user is already logged on, * send a SERVER_USER_LOGGED_ON msg as per section 3.3.5.1.5 * * o how to announce / delete a drive after a connection has been * established? * * o need to support multiple drives; * o for multiple drives, we cannot have global device_id's and file_id's * and all functions must be reenterant * o replace printf's with log_xxx * o mark local funcs with static */ #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include "arch.h" #include "parse.h" #include "os_calls.h" #include "string_calls.h" #include "log.h" #include "chansrv.h" #include "chansrv_fuse.h" #include "devredir.h" #include "smartcard.h" #include "ms-rdpefs.h" #include "ms-smb2.h" #include "ms-fscc.h" #include "ms-erref.h" /* client minor versions */ #define RDP_CLIENT_50 0x0002 #define RDP_CLIENT_51 0x0005 #define RDP_CLIENT_52 0x000a #define RDP_CLIENT_60_61 0x000c /* Windows time starts on Jan 1, 1601 */ /* Linux time starts on Jan 1, 1970 */ #define EPOCH_DIFF 11644473600LL #define WINDOWS_TO_LINUX_TIME(_t) (((_t) / 10000000) - EPOCH_DIFF); #define LINUX_TO_WINDOWS_TIME(_t) (((_t) + EPOCH_DIFF) * 10000000) /* * CompletionID types, used in IRPs to indicate I/O operation */ enum COMPLETION_TYPE { CID_CREATE_DIR_REQ = 1, CID_DIRECTORY_CONTROL, CID_CREATE_REQ, CID_OPEN_REQ, CID_READ, CID_WRITE, CID_CLOSE, CID_FILE_CLOSE, CID_RMDIR_OR_FILE, CID_RMDIR_OR_FILE_RESP, CID_RENAME_FILE, CID_RENAME_FILE_RESP, CID_LOOKUP, CID_SETATTR }; /* globals */ extern int g_rdpdr_chan_id; /* in chansrv.c */ /* Capabilities from GENERAL_CAPS_SET in Client Core Capability Response */ struct client_caps { tui32 extended_pdu; int printer_redir_supported; int port_redir_supported; int drive_redir_supported; int smartcard_redir_supported; unsigned int drive_redir_version; }; static struct client_caps g_ccap; tui32 g_completion_id = 1; tui32 g_clientID; /* unique client ID - announced by client */ tui32 g_device_id; /* unique device ID - announced by client */ tui16 g_client_rdp_version; /* returned by client */ struct stream *g_input_stream = NULL; /* * Local functions called from devredir_proc_device_iocompletion() */ static void devredir_proc_cid_rmdir_or_file(IRP *irp, enum NTSTATUS IoStatus); static void devredir_proc_cid_rmdir_or_file_resp(IRP *irp, enum NTSTATUS IoStatus); static void devredir_proc_cid_rename_file(IRP *irp, enum NTSTATUS IoStatus); static void devredir_proc_cid_rename_file_resp(IRP *irp, enum NTSTATUS IoStatus); static void devredir_proc_cid_lookup( IRP *irp, struct stream *s_in, enum NTSTATUS IoStatus); static void devredir_proc_cid_setattr( IRP *irp, struct stream *s_in, enum NTSTATUS IoStatus); /* Other local functions */ static void devredir_send_server_core_cap_req(void); static void devredir_send_server_clientID_confirm(void); static void devredir_send_server_user_logged_on(void); static int devredir_proc_client_core_cap_resp(struct stream *s); static int devredir_proc_client_devlist_announce_req(struct stream *s); static int devredir_proc_client_devlist_remove_req(struct stream *s); static int devredir_proc_device_iocompletion(struct stream *s); static int devredir_proc_query_dir_response(IRP *irp, struct stream *s_in, tui32 DeviceId, tui32 CompletionId, enum NTSTATUS IoStatus); static void devredir_cvt_slash(char *path); static int devredir_string_ends_with(const char *string, char c); /*****************************************************************************/ int devredir_init(void) { struct stream *s; int bytes; tui32 clientID; /* get a random number that will act as a unique clientID */ g_random((char *) &clientID, sizeof(clientID)); /* setup stream */ xstream_new(s, 1024); /* initiate drive redirection protocol by sending Server Announce Req */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_SERVER_ANNOUNCE); xstream_wr_u16_le(s, 0x0001); /* server major ver */ xstream_wr_u16_le(s, 0x000C); /* server minor ver - pretend 2 b Win 7 */ xstream_wr_u32_le(s, clientID); /* unique ClientID */ /* send data to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); return 0; } /*****************************************************************************/ int devredir_deinit(void) { scard_deinit(); return 0; } /*****************************************************************************/ #ifdef USE_DEVEL_LOGGING /* * Convert a COMPLETION_TYPE to a string */ static const char *completion_type_to_str(enum COMPLETION_TYPE cid) { return (cid == CID_CREATE_DIR_REQ) ? "CID_CREATE_DIR_REQ" : (cid == CID_DIRECTORY_CONTROL) ? "CID_DIRECTORY_CONTROL" : (cid == CID_CREATE_REQ) ? "CID_CREATE_REQ" : (cid == CID_OPEN_REQ) ? "CID_OPEN_REQ" : (cid == CID_READ) ? "CID_READ" : (cid == CID_WRITE) ? "CID_WRITE" : (cid == CID_CLOSE) ? "CID_CLOSE" : (cid == CID_FILE_CLOSE) ? "CID_FILE_CLOSE" : (cid == CID_RMDIR_OR_FILE) ? "CID_RMDIR_OR_FILE" : (cid == CID_RMDIR_OR_FILE_RESP) ? "CID_RMDIR_OR_FILE_RESP" : (cid == CID_RENAME_FILE) ? "CID_RENAME_FILE" : (cid == CID_RENAME_FILE_RESP) ? "CID_RENAME_FILE_RESP" : (cid == CID_LOOKUP) ? "CID_LOOKUP" : (cid == CID_SETATTR) ? "CID_SETATTR" : /* default */ ""; }; #endif /*****************************************************************************/ /* * Convert Windows permissions to Linux permissions. * * We can't currently support group or other permissions as separate from the * owner (not that there's much point). We'll assume our caller will provide * a umask if appropriate */ static tui32 WindowsToLinuxFilePerm(tui32 wperm) { tui32 result; if (wperm & W_FILE_ATTRIBUTE_DIRECTORY) { result = S_IFDIR | 0555; /* dirs are always readable and executable */ } else { result = S_IFREG | 0444; /* files are always readable */ if (wperm & W_FILE_ATTRIBUTE_SYSTEM) { result |= 0111; /* Executable */ } } if ((wperm & W_FILE_ATTRIBUTE_READONLY) == 0) { result |= 0222; } return result; } /*****************************************************************************/ static tui32 LinuxToWindowsFilePerm(tui32 lperm) { tui32 result = 0; /* Writeable flag is common to files and directories */ if ((lperm & S_IWUSR) == 0) { result |= W_FILE_ATTRIBUTE_READONLY; } if (lperm & S_IFDIR) { result |= W_FILE_ATTRIBUTE_DIRECTORY; } else { /* For normal files the system attribute is used to store the owner executable bit */ if (lperm & S_IXUSR) { result |= W_FILE_ATTRIBUTE_SYSTEM; } if (result == 0) { /* See MS-FSCC section 2.6 */ result = W_FILE_ATTRIBUTE_NORMAL; } } return result; } /** * @brief process incoming data * * @return 0 on success, -1 on failure *****************************************************************************/ int devredir_data_in(struct stream *s, int chan_id, int chan_flags, int length, int total_length) { struct stream *ls; tui16 comp_type; tui16 pktID; tui16 minor_ver; int rv = 0; /* * handle packet fragmentation */ if ((chan_flags & 3) == 3) { /* all data contained in one packet */ ls = s; } else { /* is this is the first packet? */ if (chan_flags & 1) { xstream_new(g_input_stream, total_length); } xstream_copyin(g_input_stream, s->p, length); /* in last packet, chan_flags & 0x02 will be true */ if ((chan_flags & 2) == 0) { return 0; } s_mark_end(g_input_stream); g_input_stream->p = g_input_stream->data; ls = g_input_stream; } /* read header from incoming data */ if (!s_check_rem_and_log(ls, 4, "Parsing [MS-RDPEFS] RDPDR_HEADER")) { rv = -1; goto done; } xstream_rd_u16_le(ls, comp_type); xstream_rd_u16_le(ls, pktID); /* for now we only handle core type, not printers */ if (comp_type != RDPDR_CTYP_CORE) { LOG_DEVEL(LOG_LEVEL_ERROR, "invalid component type in response; expected 0x%x got 0x%x", RDPDR_CTYP_CORE, comp_type); rv = -1; goto done; } /* figure out what kind of response we have gotten */ switch (pktID) { case PAKID_CORE_CLIENTID_CONFIRM: if (!s_check_rem_and_log(ls, 6, "Parsing [MS-RDPEFS] DR_CORE_CLIENT_ANNOUNCE_RSP")) { rv = -1; } else { xstream_seek(ls, 2); /* major version, we ignore it */ xstream_rd_u16_le(ls, minor_ver); xstream_rd_u32_le(ls, g_clientID); g_client_rdp_version = minor_ver; switch (minor_ver) { case RDP_CLIENT_50: break; case RDP_CLIENT_51: break; case RDP_CLIENT_52: break; case RDP_CLIENT_60_61: break; } } break; case PAKID_CORE_CLIENT_NAME: /* client is telling us its computer name; do we even care? */ /* See 3.3.5.1.6 for sequencing rules */ if (g_client_rdp_version >= RDP_CLIENT_51) { /* let client know our capabilities */ devredir_send_server_core_cap_req(); } /* send confirm clientID */ devredir_send_server_clientID_confirm(); break; case PAKID_CORE_CLIENT_CAPABILITY: rv = devredir_proc_client_core_cap_resp(ls); if (rv == 0) { if ((g_ccap.extended_pdu & RDPDR_USER_LOGGEDON_PDU) != 0) { /* Tell client to announce remaining devices */ devredir_send_server_user_logged_on(); } else if (g_client_rdp_version >= RDP_CLIENT_51) { /* See 3.3.5.1.7 */ devredir_send_server_clientID_confirm(); } } break; case PAKID_CORE_DEVICELIST_ANNOUNCE: rv = devredir_proc_client_devlist_announce_req(ls); break; case PAKID_CORE_DEVICELIST_REMOVE: rv = devredir_proc_client_devlist_remove_req(ls); break; case PAKID_CORE_DEVICE_IOCOMPLETION: rv = devredir_proc_device_iocompletion(ls); break; default: LOG_DEVEL(LOG_LEVEL_ERROR, "got unknown response 0x%x", pktID); break; } done: if (g_input_stream) { xstream_free(g_input_stream); g_input_stream = NULL; } return rv; } /*****************************************************************************/ int devredir_get_wait_objs(tbus *objs, int *count, int *timeout) { if (g_ccap.smartcard_redir_supported) { return scard_get_wait_objs(objs, count, timeout); } return 0; } /*****************************************************************************/ int devredir_check_wait_objs(void) { if (g_ccap.smartcard_redir_supported) { return scard_check_wait_objs(); } return 0; } /** * @brief let client know our capabilities *****************************************************************************/ static void devredir_send_server_core_cap_req(void) { struct stream *s; int bytes; xstream_new(s, 1024); /* setup header */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_SERVER_CAPABILITY); xstream_wr_u16_le(s, 5); /* num of caps we are sending */ xstream_wr_u16_le(s, 0x0000); /* padding */ /* setup general capability */ xstream_wr_u16_le(s, CAP_GENERAL_TYPE); /* CapabilityType */ xstream_wr_u16_le(s, 44); /* CapabilityLength - len of this */ /* CAPABILITY_SET in bytes, inc */ /* the header */ xstream_wr_u32_le(s, 2); /* Version */ xstream_wr_u32_le(s, 2); /* O.S type */ xstream_wr_u32_le(s, 0); /* O.S version */ xstream_wr_u16_le(s, 1); /* protocol major version */ xstream_wr_u16_le(s, g_client_rdp_version); /* protocol minor version */ xstream_wr_u32_le(s, 0xffff); /* I/O code 1 */ xstream_wr_u32_le(s, 0); /* I/O code 2 */ xstream_wr_u32_le(s, 7); /* Extended PDU */ xstream_wr_u32_le(s, 0); /* extra flags 1 */ xstream_wr_u32_le(s, 0); /* extra flags 2 */ xstream_wr_u32_le(s, 2); /* special type device cap */ /* setup printer capability */ xstream_wr_u16_le(s, CAP_PRINTER_TYPE); xstream_wr_u16_le(s, 8); xstream_wr_u32_le(s, 1); /* setup serial port capability */ xstream_wr_u16_le(s, CAP_PORT_TYPE); xstream_wr_u16_le(s, 8); xstream_wr_u32_le(s, 1); /* setup file system capability */ xstream_wr_u16_le(s, CAP_DRIVE_TYPE); /* CapabilityType */ xstream_wr_u16_le(s, 8); /* CapabilityLength - len of this */ /* CAPABILITY_SET in bytes, inc */ /* the header */ xstream_wr_u32_le(s, 2); /* Version */ /* setup smart card capability */ xstream_wr_u16_le(s, CAP_SMARTCARD_TYPE); xstream_wr_u16_le(s, 8); xstream_wr_u32_le(s, 1); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } static void devredir_send_server_clientID_confirm(void) { struct stream *s; int bytes; xstream_new(s, 1024); /* setup stream */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_CLIENTID_CONFIRM); xstream_wr_u16_le(s, 0x0001); xstream_wr_u16_le(s, g_client_rdp_version); xstream_wr_u32_le(s, g_clientID); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } static void devredir_send_server_user_logged_on(void) { struct stream *s; int bytes; xstream_new(s, 1024); /* setup stream */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_USER_LOGGEDON); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } static void devredir_send_server_device_announce_resp(tui32 device_id, enum NTSTATUS result_code) { struct stream *s; int bytes; xstream_new(s, 1024); /* setup stream */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_DEVICE_REPLY); xstream_wr_u32_le(s, device_id); xstream_wr_u32_le(s, (tui32)result_code); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } /** * Create a file system object * * See [MS-RDPEFS] 2.2.1.4.1 (DR_CREATE_REQ) * * @return 0 on success, -1 on failure *****************************************************************************/ static int devredir_send_drive_create_request(tui32 device_id, const char *path, tui32 DesiredAccess, tui32 CreateOptions, tui32 FileAttributes, tui32 CreateDisposition, tui32 completion_id) { struct stream *s; int bytes; int path_len; unsigned int unicode_byte_count; tui32 SharedAccess; LOG_DEVEL(LOG_LEVEL_DEBUG, "device_id=%d path=\"%s\"" " DesiredAccess=0x%x CreateDisposition=0x%x" " FileAttributes=0x%x CreateOptions=0x%x" " CompletionId=%d", device_id, path, DesiredAccess, CreateDisposition, FileAttributes, CreateOptions, completion_id); /* Find number of bytes required for Unicode path + terminator */ path_len = strlen(path) + 1; // includes terminator unicode_byte_count = utf8_as_utf16_word_count(path, path_len) * 2; xstream_new(s, (int)(64 + unicode_byte_count)); /* FILE_SHARE_DELETE allows files to be renamed while in use (in some circumstances) */ SharedAccess = SA_FILE_SHARE_READ | SA_FILE_SHARE_WRITE | SA_FILE_SHARE_DELETE; devredir_insert_DeviceIoRequest(s, device_id, 0, completion_id, IRP_MJ_CREATE, IRP_MN_NONE); xstream_wr_u32_le(s, DesiredAccess); /* DesiredAccess */ xstream_wr_u32_le(s, 0); /* AllocationSize high unused */ xstream_wr_u32_le(s, 0); /* AllocationSize low unused */ xstream_wr_u32_le(s, FileAttributes); /* FileAttributes */ xstream_wr_u32_le(s, SharedAccess); /* SharedAccess */ xstream_wr_u32_le(s, CreateDisposition); /* CreateDisposition */ xstream_wr_u32_le(s, CreateOptions); /* CreateOptions */ xstream_wr_u32_le(s, unicode_byte_count);/* PathLength */ out_utf8_as_utf16_le(s, path, path_len); /* Path */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); return 0; } /** * Close a request previously created by devredir_send_drive_create_request() *****************************************************************************/ static int devredir_send_drive_close_request(tui16 Component, tui16 PacketId, tui32 DeviceId, tui32 FileId, tui32 CompletionId, enum IRP_MJ MajorFunction, enum IRP_MN MinorFunc, int pad_len) { struct stream *s; int bytes; xstream_new(s, 1024); devredir_insert_DeviceIoRequest(s, DeviceId, FileId, CompletionId, MajorFunction, MinorFunc); if (pad_len) { xstream_seek(s, pad_len); } /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); LOG_DEVEL(LOG_LEVEL_DEBUG, "sent close request; expect CID_FILE_CLOSE"); return 0; } /** * @brief ask client to enumerate directory * * See [MS-RDPEFS] 2.2.3.3.10 (DR_DRIVE_QUERY_DIRECTORY_REQ) *****************************************************************************/ static void devredir_send_drive_dir_request(IRP *irp, tui32 device_id, tui32 InitialQuery, char *Path) { struct stream *s; int bytes; unsigned int path_len; unsigned int unicode_byte_count; /* during Initial Query, Path cannot be NULL */ if (InitialQuery && Path != NULL) { /* Find number of words required for Unicode path */ path_len = strlen(Path) + 1; // includes terminator unicode_byte_count = utf8_as_utf16_word_count(Path, path_len) * 2; } else { path_len = 0; unicode_byte_count = 0; } xstream_new(s, (int)(64 + unicode_byte_count)); irp->completion_type = CID_DIRECTORY_CONTROL; devredir_insert_DeviceIoRequest(s, device_id, irp->FileId, irp->CompletionId, IRP_MJ_DIRECTORY_CONTROL, IRP_MN_QUERY_DIRECTORY); xstream_wr_u32_le(s, FileDirectoryInformation); /* FsInformationClass */ xstream_wr_u8(s, InitialQuery); /* InitialQuery */ xstream_wr_u32_le(s, unicode_byte_count); /* PathLength */ xstream_seek(s, 23); /* Padding */ out_utf8_as_utf16_le(s, Path, path_len); /* Path */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } /****************************************************************************** ** process data received from client ** ******************************************************************************/ /** * Process a GENERAL_CAPS_SET packet from the client * @param s Stream. CAPABILITY_HEADER is already read * @param cap_len Amount of data left in stream for the packet * @return 0 for success, -1 otherwise * * Caller is responsible for skipping unused data from this * capability in the input stream. */ static int process_client_general_caps_set(struct stream *s, unsigned int cap_len, struct client_caps *ccap) { int rv = -1; // Data we don't check at the start of the packet #define PACKET_SKIP_LENGTH ( \ 4 + /* osType */ \ 4 + /* osVersion */ \ 2 + /* protocolMajorVersion */ \ 2 + /* protocolMinorVersion */ \ 4 + /* ioCode1 */ \ 4) /* ioCode2 */ if (cap_len < (PACKET_SKIP_LENGTH + 4)) { LOG(LOG_LEVEL_ERROR, "[MS-RDPEFS] GENERAL_CAPS_SET: Short packet (%u bytes) encountered", cap_len); } else { xstream_seek(s, PACKET_SKIP_LENGTH); xstream_rd_u32_le(s, ccap->extended_pdu); rv = 0; } return rv; #undef PACKET_SKIP_LENGTH } /** * @brief process client's response to our core_capability_req() msg * * @param s stream containing client's response * @return 0 for success, -1 otherwise *****************************************************************************/ static int devredir_proc_client_core_cap_resp(struct stream *s) { #define CAPABILITY_HEADER_LEN 8 int i; tui16 num_caps; tui16 cap_type; tui16 cap_len; tui32 cap_version; // Reset to defaults memset(&g_ccap, 0, sizeof(g_ccap)); g_ccap.drive_redir_version = 1; if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CORE_CAPABLITY_RSP")) { return -1; } xstream_rd_u16_le(s, num_caps); xstream_seek(s, 2); /* padding */ for (i = 0; i < num_caps; i++) { if (!s_check_rem_and_log(s, CAPABILITY_HEADER_LEN, "Parsing [MS-RDPEFS] CAPABILITY_HEADER")) { return -1; } xstream_rd_u16_le(s, cap_type); xstream_rd_u16_le(s, cap_len); xstream_rd_u32_le(s, cap_version); /* Convert the length to a remaining length. Underflow is possible, * but this is an unsigned type so that's OK */ cap_len -= CAPABILITY_HEADER_LEN; if (!s_check_rem_and_log(s, cap_len, "Parsing [MS-RDPEFS] CAPABILITY_HEADER data")) { return -1; } // Save our stream position. iso_hdr is otherwise // unused in this stream s_push_layer(s, iso_hdr, 0); switch (cap_type) { case CAP_GENERAL_TYPE: LOG_DEVEL(LOG_LEVEL_DEBUG, "got CAP_GENERAL_TYPE"); if (process_client_general_caps_set(s, cap_len, &g_ccap) < 0) { return -1; } break; case CAP_PRINTER_TYPE: LOG_DEVEL(LOG_LEVEL_DEBUG, "got CAP_PRINTER_TYPE"); g_ccap.printer_redir_supported = 1; break; case CAP_PORT_TYPE: LOG_DEVEL(LOG_LEVEL_DEBUG, "got CAP_PORT_TYPE"); g_ccap.port_redir_supported = 1; break; case CAP_DRIVE_TYPE: LOG_DEVEL(LOG_LEVEL_DEBUG, "got CAP_DRIVE_TYPE"); g_ccap.drive_redir_supported = 1; if (cap_version == 2) { g_ccap.drive_redir_version = 2; } break; case CAP_SMARTCARD_TYPE: LOG_DEVEL(LOG_LEVEL_DEBUG, "got CAP_SMARTCARD_TYPE"); g_ccap.smartcard_redir_supported = (scard_init() == 0); break; } s_pop_layer(s, iso_hdr); // Move back to start of capability data xstream_seek(s, cap_len); } return 0; #undef CAPABILITY_HEADER_LEN } static int devredir_proc_client_devlist_announce_req(struct stream *s) { unsigned int i; int j; tui32 device_count; tui32 device_type; tui32 device_data_len; char preferred_dos_name[9]; enum NTSTATUS response_status; /* get number of devices being announced */ if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CORE_DEVICELIST_ANNOUNCE_REQ")) { return -1; } xstream_rd_u32_le(s, device_count); LOG_DEVEL(LOG_LEVEL_DEBUG, "num of devices announced: %d", device_count); for (i = 0; i < device_count; i++) { if (!s_check_rem_and_log(s, 4 + 4 + 8 + 4, "Parsing [MS-RDPEFS] DEVICE_ANNOUNCE")) { return -1; } xstream_rd_u32_le(s, device_type); xstream_rd_u32_le(s, g_device_id); /* get preferred DOS name * DOS names that are 8 chars long are not NULL terminated */ for (j = 0; j < 8; j++) { preferred_dos_name[j] = *s->p++; } preferred_dos_name[8] = 0; /* Assume this device isn't supported by us */ response_status = STATUS_NOT_SUPPORTED; /* Read the device data length from the stream */ xstream_rd_u32_le(s, device_data_len); if (device_data_len > 0 && !s_check_rem_and_log(s, device_data_len, "Parsing [MS-RDPEFS] DEVICE_ANNOUNCE devdata")) { return -1; } switch (device_type) { case RDPDR_DTYP_FILESYSTEM: /* At present we don't use the full name - see * [MS-RDPEFS] 2.2.3.1 for details of the contents */ xstream_skip_u8(s, device_data_len); LOG(LOG_LEVEL_INFO, "Detected remote drive '%s'", preferred_dos_name); LOG_DEVEL(LOG_LEVEL_DEBUG, "device_type=FILE_SYSTEM device_id=0x%x dosname=%s " "device_data_len=%d", g_device_id, preferred_dos_name, device_data_len); response_status = STATUS_SUCCESS; /* create share directory in xrdp file system; */ /* think of this as the mount point for this share */ xfuse_create_share(g_device_id, preferred_dos_name); break; case RDPDR_DTYP_SMARTCARD: xstream_skip_u8(s, device_data_len); LOG(LOG_LEVEL_INFO, "Detected remote smartcard '%s'", preferred_dos_name); LOG_DEVEL(LOG_LEVEL_DEBUG, "device_type=SMARTCARD device_id=0x%x dosname=%s", g_device_id, preferred_dos_name); response_status = STATUS_SUCCESS; scard_device_announce(g_device_id); break; default: { /* All other devices are unsupported */ const char *description = (device_type == RDPDR_DTYP_SERIAL) ? "serial port" : (device_type == RDPDR_DTYP_PARALLEL) ? "parallel port" : (device_type == RDPDR_DTYP_PRINT) ? "printer" : /* default */ "unknown device"; xstream_skip_u8(s, device_data_len); LOG(LOG_LEVEL_INFO, "Detected remote %s '%s' (not supported)", description, preferred_dos_name); LOG_DEVEL(LOG_LEVEL_DEBUG, "description=%s dosname=%s device_id=0x%x", description, preferred_dos_name, g_device_id); } break; } /* Tell the client wheth or not we're supporting this one */ devredir_send_server_device_announce_resp(g_device_id, response_status); } return 0; } static int devredir_proc_client_devlist_remove_req(struct stream *s) { unsigned int i; tui32 device_count; tui32 device_id; /* get number of devices being announced */ if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_DEVICELIST_REMOVE")) { return -1; } xstream_rd_u32_le(s, device_count); if (!s_check_rem_and_log(s, 4 * device_count, "Parsing [MS-RDPEFS] DR_DEVICELIST_REMOVE list")) { return -1; } LOG_DEVEL(LOG_LEVEL_DEBUG, "num of devices removed: %d", device_count); { for (i = 0; i < device_count; i++) { xstream_rd_u32_le(s, device_id); xfuse_delete_share(device_id); } } return 0; } static int devredir_proc_device_iocompletion(struct stream *s) { IRP *irp = NULL; tui32 DeviceId; tui32 CompletionId; tui32 IoStatus32; tui32 Length; enum COMPLETION_TYPE comp_type; if (!s_check_rem_and_log(s, 12, "Parsing [MS-RDPEFS] DR_DEVICE_IOCOMPLETION")) { return -1; } xstream_rd_u32_le(s, DeviceId); xstream_rd_u32_le(s, CompletionId); xstream_rd_u32_le(s, IoStatus32); enum NTSTATUS IoStatus = (enum NTSTATUS) IoStatus32; /* Needed by C++ */ if ((irp = devredir_irp_find(CompletionId)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "IRP with completion ID %d not found", CompletionId); } else if (irp->callback) { /* Callback has been set - call it */ (*irp->callback)(s, irp, DeviceId, CompletionId, IoStatus); } else { comp_type = (enum COMPLETION_TYPE) irp->completion_type; /* Log something about the IRP */ if (IoStatus == STATUS_SUCCESS || IoStatus == STATUS_NO_MORE_FILES || (IoStatus == STATUS_NO_SUCH_FILE && comp_type == CID_LOOKUP)) { /* Successes or common occurrences - debug logging only */ LOG_DEVEL(LOG_LEVEL_DEBUG, "got %s", completion_type_to_str(comp_type)); } else { LOG_DEVEL(LOG_LEVEL_ERROR, "CompletionType = %s, IoStatus=%08x " "Pathname = %s", completion_type_to_str(comp_type), IoStatus, (irp->pathname) ? irp->pathname : ""); } switch (comp_type) { case CID_CREATE_DIR_REQ: if (IoStatus != STATUS_SUCCESS) { xfuse_devredir_cb_enum_dir_done( (struct state_dirscan *) irp->fuse_info, IoStatus); devredir_irp_delete(irp); } else { if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CREATE_RSP")) { return -1; } xstream_rd_u32_le(s, irp->FileId); devredir_send_drive_dir_request(irp, DeviceId, 1, irp->pathname); } break; case CID_CREATE_REQ: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CREATE_RSP")) { return -1; } xstream_rd_u32_le(s, irp->FileId); xfuse_devredir_cb_create_file( (struct state_create *) irp->fuse_info, IoStatus, DeviceId, irp->FileId); if (irp->gen.create.creating_dir || IoStatus != STATUS_SUCCESS) { devredir_irp_delete(irp); } break; case CID_OPEN_REQ: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CREATE_RSP")) { return -1; } xstream_rd_u32_le(s, irp->FileId); xfuse_devredir_cb_open_file((struct state_open *) irp->fuse_info, IoStatus, DeviceId, irp->FileId); if (IoStatus != STATUS_SUCCESS) { devredir_irp_delete(irp); } break; case CID_READ: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_READ_RSP")) { return -1; } xstream_rd_u32_le(s, Length); if (!s_check_rem_and_log(s, Length, "Parsing [MS-RDPEFS] DR_READ_RSP")) { return -1; } xfuse_devredir_cb_read_file((struct state_read *) irp->fuse_info, IoStatus, s->p, Length); devredir_irp_delete(irp); break; case CID_WRITE: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_WRITE_RSP")) { return -1; } xstream_rd_u32_le(s, Length); xfuse_devredir_cb_write_file((struct state_write *) irp->fuse_info, IoStatus, irp->gen.write.offset, Length); devredir_irp_delete(irp); break; case CID_CLOSE: devredir_irp_delete(irp); break; case CID_FILE_CLOSE: xfuse_devredir_cb_file_close((struct state_close *) irp->fuse_info); devredir_irp_delete(irp); break; case CID_DIRECTORY_CONTROL: if (devredir_proc_query_dir_response(irp, s, DeviceId, CompletionId, IoStatus) != 0) { return -1; } break; case CID_RMDIR_OR_FILE: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CREATE_RSP")) { return -1; } xstream_rd_u32_le(s, irp->FileId); devredir_proc_cid_rmdir_or_file(irp, IoStatus); break; case CID_RMDIR_OR_FILE_RESP: devredir_proc_cid_rmdir_or_file_resp(irp, IoStatus); break; case CID_RENAME_FILE: if (!s_check_rem_and_log(s, 4, "Parsing [MS-RDPEFS] DR_CREATE_RSP")) { return -1; } xstream_rd_u32_le(s, irp->FileId); devredir_proc_cid_rename_file(irp, IoStatus); break; case CID_RENAME_FILE_RESP: devredir_proc_cid_rename_file_resp(irp, IoStatus); break; case CID_LOOKUP: devredir_proc_cid_lookup(irp, s, IoStatus); break; case CID_SETATTR: devredir_proc_cid_setattr(irp, s, IoStatus); break; default: LOG_DEVEL(LOG_LEVEL_ERROR, "got unknown CompletionID: DeviceId=0x%x " "CompletionId=0x%x IoStatus=0x%x", DeviceId, CompletionId, IoStatus); break; } } return 0; } /** * Parse a query directory response * * See:- * - [MS-RDPEFS] 2.2.3.4.10 (DR_DRIVE_QUERY_DIRECTORY_RSP) * - [MS-FSCC] 2.4.10 (FileDirectoryInformation) * * return 0 on success, -1 on failure */ static int devredir_proc_query_dir_response(IRP *irp, struct stream *s_in, tui32 DeviceId, tui32 CompletionId, enum NTSTATUS IoStatus) { // Size of FILE_DIRECTORY_INFORMATION structure with a particular // filename length #define FILE_DIRECTORY_INFORMATION_SIZE(FileNameLength) \ ((4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4) + FileNameLength) xstream_seek(s_in, 4); /* Length */ if (IoStatus == STATUS_SUCCESS) { /* process FILE_DIRECTORY_INFORMATION structures */ while (1) { /* Can we read up to the filename? */ if (!s_check_rem_and_log(s_in, FILE_DIRECTORY_INFORMATION_SIZE(0), "FILE_DIRECTORY_INFORMATION")) { return -1; } char filename[256]; tui64 LastAccessTime; tui64 LastWriteTime; tui64 EndOfFile; tui32 NextEntryOffset; tui32 FileAttributes; tui32 FileNameLength; struct file_attr fattr; unsigned int flen; xstream_rd_u32_le(s_in, NextEntryOffset); xstream_seek(s_in, 4); /* FileIndex */ xstream_seek(s_in, 8); /* CreationTime */ xstream_rd_u64_le(s_in, LastAccessTime); xstream_rd_u64_le(s_in, LastWriteTime); xstream_seek(s_in, 8); /* ChangeTime */ xstream_rd_u64_le(s_in, EndOfFile); xstream_seek(s_in, 8); /* AllocationSize */ xstream_rd_u32_le(s_in, FileAttributes); xstream_rd_u32_le(s_in, FileNameLength); if (!s_check_rem_and_log(s_in, FileNameLength, "FILE_DIRECTORY_INFORMATION:FileName")) { return -1; } flen = in_utf16_le_fixed_as_utf8(s_in, FileNameLength / 2, filename, sizeof(filename)); if (flen > sizeof(filename)) { LOG(LOG_LEVEL_WARNING, "Buffer was %d bytes too small for filename", (int)(flen - sizeof(filename))); } //LOG_DEVEL(LOG_LEVEL_DEBUG, "LastAccessTime: 0x%llx", LastAccessTime); //LOG_DEVEL(LOG_LEVEL_DEBUG, "LastWriteTime: 0x%llx", LastWriteTime); //LOG_DEVEL(LOG_LEVEL_DEBUG, "EndOfFile: %lld", EndOfFile); //LOG_DEVEL(LOG_LEVEL_DEBUG, "FileAttributes: 0x%x", FileAttributes); //LOG_DEVEL(LOG_LEVEL_DEBUG, "FileNameLength: %d", FileNameLength); LOG_DEVEL(LOG_LEVEL_DEBUG, "FileName: %s", filename); fattr.mode = WindowsToLinuxFilePerm(FileAttributes); fattr.size = (size_t) EndOfFile; fattr.atime = WINDOWS_TO_LINUX_TIME(LastAccessTime); fattr.mtime = WINDOWS_TO_LINUX_TIME(LastWriteTime); /* add this entry to xrdp file system */ xfuse_devredir_cb_enum_dir_add_entry( (struct state_dirscan *) irp->fuse_info, filename, &fattr); /* From [MS-FSCC] * {NextEntryOffset} MUST be zero if no other entries follow * this one. An implementation MUST use this value to determine * the location of the next entry (if multiple entries are * present in a buffer). */ if (NextEntryOffset == 0) { break; } int pad = NextEntryOffset - FILE_DIRECTORY_INFORMATION_SIZE(FileNameLength); if (pad > 0) { if (!s_check_rem_and_log(s_in, pad, "FILE_DIRECTORY_INFORMATION:pad")) { return -1; } xstream_seek(s_in, pad); } } /* Ask for more directory entries */ devredir_send_drive_dir_request(irp, DeviceId, 0, NULL); } else { if (IoStatus == STATUS_NO_MORE_FILES) { IoStatus = STATUS_SUCCESS; } xfuse_devredir_cb_enum_dir_done((struct state_dirscan *)irp->fuse_info, IoStatus); irp->completion_type = CID_CLOSE; devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } return 0; #undef FILE_DIRECTORY_INFORMATION_SIZE } /** * FUSE calls this function whenever it wants us to enumerate a dir * * @param fusep opaque data struct that we just pass back to FUSE when done * @param device_id device_id of the redirected share * @param path the dir path to enumerate * * @return 0 on success, -1 on failure *****************************************************************************/ int devredir_get_dir_listing(struct state_dirscan *fusep, tui32 device_id, const char *path) { tui32 DesiredAccess; tui32 CreateOptions; tui32 CreateDisposition; int rval = -1; IRP *irp; /* * We need to be able to append two additional characters to the * path after we create the IRP */ if ((irp = devredir_irp_with_pathnamelen_new(strlen(path) + 2)) != NULL) { /* convert / to windows compatible \ */ strcpy(irp->pathname, path); devredir_cvt_slash(irp->pathname); irp->CompletionId = g_completion_id++; irp->completion_type = CID_CREATE_DIR_REQ; irp->DeviceId = device_id; irp->fuse_info = fusep; DesiredAccess = DA_FILE_READ_DATA | DA_SYNCHRONIZE; CreateOptions = CO_FILE_DIRECTORY_FILE | CO_FILE_SYNCHRONOUS_IO_NONALERT; CreateDisposition = CD_FILE_OPEN; rval = devredir_send_drive_create_request(device_id, irp->pathname, DesiredAccess, CreateOptions, 0, CreateDisposition, irp->CompletionId); LOG_DEVEL(LOG_LEVEL_DEBUG, "looking for device_id=%d path=%s", device_id, irp->pathname); /* when we get a response to devredir_send_drive_create_request(), we * call devredir_send_drive_dir_request(), which needs the following * at the end of the path argument */ if (devredir_string_ends_with(irp->pathname, '\\')) { strcat(irp->pathname, "*"); } else { strcat(irp->pathname, "\\*"); } } return rval; } /** * FUSE calls this function whenever it wants us to lookup a file or directory * * @param fusep opaque data struct that we just pass back to FUSE when done * @param device_id device_id of the redirected share * @param path the name of the directory containing the file * @param file the filename * * @return 0 on success, -1 on failure *****************************************************************************/ int devredir_lookup_entry(struct state_lookup *fusep, tui32 device_id, const char *path) { tui32 DesiredAccess; tui32 CreateOptions; tui32 CreateDisposition; int rval = -1; IRP *irp; LOG_DEVEL(LOG_LEVEL_DEBUG, "fusep=%p", fusep); if ((irp = devredir_irp_with_pathname_new(path)) != NULL) { /* convert / to windows compatible \ */ devredir_cvt_slash(irp->pathname); /* * Allocate an IRP to open the file, read the basic attributes, * read the standard attributes, and then close the file */ irp->CompletionId = g_completion_id++; irp->completion_type = CID_LOOKUP; irp->DeviceId = device_id; irp->gen.lookup.state = E_LOOKUP_GET_FH; irp->fuse_info = fusep; DesiredAccess = DA_FILE_READ_ATTRIBUTES | DA_SYNCHRONIZE; CreateOptions = 0; CreateDisposition = CD_FILE_OPEN; LOG_DEVEL(LOG_LEVEL_DEBUG, "lookup for device_id=%d path=%s CompletionId=%d", device_id, irp->pathname, irp->CompletionId); rval = devredir_send_drive_create_request(device_id, irp->pathname, DesiredAccess, CreateOptions, 0, CreateDisposition, irp->CompletionId); } return rval; } /** * FUSE calls this function whenever it wants us to set the attributes for * a file or directory. * * @param fusep opaque data struct that we just pass back to FUSE when done * @param device_id device_id of the redirected share * @param filename the name of the file * @param fattr the file attributes to set for the file * @param to_set Which bits of the file attributes have changed * * @return 0 on success, -1 on failure *****************************************************************************/ int devredir_setattr_for_entry(struct state_setattr *fusep, tui32 device_id, const char *filename, const struct file_attr *fattr, tui32 to_set) { tui32 DesiredAccess; tui32 CreateOptions; tui32 CreateDisposition; int rval = -1; IRP *irp; LOG_DEVEL(LOG_LEVEL_DEBUG, "fusep=%p", fusep); if ((irp = devredir_irp_with_pathname_new(filename)) != NULL) { /* convert / to windows compatible \ */ devredir_cvt_slash(irp->pathname); /* * Allocate an IRP to open the file, update the attributes * and close the file. */ irp->CompletionId = g_completion_id++; irp->completion_type = CID_SETATTR; irp->DeviceId = device_id; irp->fuse_info = fusep; irp->gen.setattr.state = E_SETATTR_GET_FH; irp->gen.setattr.to_set = to_set; irp->gen.setattr.fattr = *fattr; /* * Don't set DA_FILE_WRITE_DATA unless we're changing the * EndOfFile pointer. Otherwise we can't change the attributes * of read-only files! */ DesiredAccess = DA_FILE_WRITE_ATTRIBUTES; if (to_set & TO_SET_SIZE) { DesiredAccess |= DA_FILE_WRITE_DATA; } CreateOptions = 0; CreateDisposition = CD_FILE_OPEN; LOG_DEVEL(LOG_LEVEL_DEBUG, "lookup for device_id=%d path=%s", device_id, irp->pathname); rval = devredir_send_drive_create_request(device_id, irp->pathname, DesiredAccess, CreateOptions, 0, CreateDisposition, irp->CompletionId); } return rval; } int devredir_file_create(struct state_create *fusep, tui32 device_id, const char *path, int mode) { tui32 DesiredAccess; tui32 CreateOptions; tui32 FileAttributes = 0; tui32 CreateDisposition; int rval = -1; IRP *irp; LOG_DEVEL(LOG_LEVEL_DEBUG, "device_id=%d path=%s mode=0%o", device_id, path, mode); if ((irp = devredir_irp_with_pathname_new(path)) != NULL) { /* convert / to windows compatible \ */ devredir_cvt_slash(irp->pathname); irp->completion_type = CID_CREATE_REQ; irp->CompletionId = g_completion_id++; irp->DeviceId = device_id; irp->fuse_info = fusep; DesiredAccess = 0x0016019f; /* got this value from windows */ FileAttributes = LinuxToWindowsFilePerm(mode); if (mode & S_IFDIR) { LOG_DEVEL(LOG_LEVEL_DEBUG, "creating dir"); CreateOptions = CO_FILE_DIRECTORY_FILE | CO_FILE_SYNCHRONOUS_IO_NONALERT; irp->gen.create.creating_dir = 1; } else { LOG_DEVEL(LOG_LEVEL_DEBUG, "creating file"); CreateOptions = 0x44; /* got this value from windows */ irp->gen.create.creating_dir = 0; } //CreateDisposition = CD_FILE_CREATE; CreateDisposition = 0x02; /* got this value from windows */ rval = devredir_send_drive_create_request(device_id, path, DesiredAccess, CreateOptions, FileAttributes, CreateDisposition, irp->CompletionId); } return rval; } int devredir_file_open(struct state_open *fusep, tui32 device_id, const char *path, int flags) { tui32 DesiredAccess; tui32 CreateOptions; tui32 FileAttributes = 0; tui32 CreateDisposition; int rval = -1; IRP *irp; LOG_DEVEL(LOG_LEVEL_DEBUG, "device_id=%d path=%s flags=0%x", device_id, path, flags); if ((irp = devredir_irp_with_pathname_new(path)) != NULL) { /* convert / to windows compatible \ */ devredir_cvt_slash(irp->pathname); irp->completion_type = CID_OPEN_REQ; irp->CompletionId = g_completion_id++; irp->DeviceId = device_id; irp->fuse_info = fusep; switch (flags & O_ACCMODE) { case O_RDONLY: LOG_DEVEL(LOG_LEVEL_DEBUG, "open file in O_RDONLY"); DesiredAccess = DA_FILE_READ_DATA | DA_SYNCHRONIZE; break; case O_WRONLY: LOG_DEVEL(LOG_LEVEL_DEBUG, "open file in O_WRONLY"); DesiredAccess = DA_FILE_WRITE_DATA | DA_SYNCHRONIZE; break; default: /* * The access mode could conceivably be invalid here, * but we assume this has been checked by the caller */ LOG_DEVEL(LOG_LEVEL_DEBUG, "open file in O_RDWR"); /* without the 0x00000010 rdesktop opens files in */ /* O_RDONLY instead of O_RDWR mode */ DesiredAccess = DA_FILE_READ_DATA | DA_FILE_WRITE_DATA | DA_SYNCHRONIZE | 0x00000010; } CreateOptions = CO_FILE_SYNCHRONOUS_IO_NONALERT; CreateDisposition = CD_FILE_OPEN; // WAS 1 rval = devredir_send_drive_create_request(device_id, path, DesiredAccess, CreateOptions, FileAttributes, CreateDisposition, irp->CompletionId); } return rval; } int devredir_file_close(struct state_close *fusep, tui32 device_id, tui32 FileId) { IRP *irp; LOG_DEVEL(LOG_LEVEL_DEBUG, "entered: fusep=%p device_id=%d FileId=%d", fusep, device_id, FileId); #if 0 if ((irp = devredir_irp_new()) == NULL) { return -1; } irp->CompletionId = g_completion_id++; #else if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "no IRP found with FileId = %d", FileId); return -1; } #endif irp->completion_type = CID_FILE_CLOSE; irp->DeviceId = device_id; irp->fuse_info = fusep; return devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, device_id, FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } /** * Remove (delete) a directory or file *****************************************************************************/ int devredir_rmdir_or_file(struct state_remove *fusep, tui32 device_id, const char *path) { tui32 DesiredAccess; tui32 CreateOptions; tui32 CreateDisposition; int rval = -1; IRP *irp; if ((irp = devredir_irp_with_pathname_new(path)) != NULL) { /* convert / to windows compatible \ */ devredir_cvt_slash(irp->pathname); irp->CompletionId = g_completion_id++; irp->completion_type = CID_RMDIR_OR_FILE; irp->DeviceId = device_id; irp->fuse_info = fusep; //DesiredAccess = DA_DELETE | DA_FILE_READ_ATTRIBUTES | DA_SYNCHRONIZE; DesiredAccess = 0x00100080; /* got this value from windows */ //CreateOptions = CO_FILE_DELETE_ON_CLOSE | CO_FILE_DIRECTORY_FILE | // CO_FILE_SYNCHRONOUS_IO_NONALERT; CreateOptions = 0x020; /* got this value from windows */ //CreateDisposition = CD_FILE_OPEN; // WAS 1 CreateDisposition = 0x01; /* got this value from windows */ rval = devredir_send_drive_create_request(device_id, path, DesiredAccess, CreateOptions, 0, CreateDisposition, irp->CompletionId); } return rval; } /** * Read data from previously opened file * * Errors are reported via xfuse_devredir_cb_read_file() *****************************************************************************/ void devredir_file_read(struct state_read *fusep, tui32 DeviceId, tui32 FileId, tui32 Length, tui64 Offset) { struct stream *s; IRP *irp; IRP *new_irp; int bytes; xstream_new(s, 1024); /* Check we've got an open IRP for this file already */ if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "no IRP found with FileId = %d", FileId); xfuse_devredir_cb_read_file(fusep, STATUS_UNSUCCESSFUL, NULL, 0); xstream_free(s); } /* create a new IRP for this request */ else if ((new_irp = devredir_irp_new()) == NULL) { /* system out of memory */ xfuse_devredir_cb_read_file(fusep, STATUS_UNSUCCESSFUL, NULL, 0); xstream_free(s); } else { new_irp->DeviceId = DeviceId; new_irp->FileId = FileId; new_irp->completion_type = CID_READ; new_irp->CompletionId = g_completion_id++; new_irp->fuse_info = fusep; devredir_insert_DeviceIoRequest(s, DeviceId, FileId, new_irp->CompletionId, IRP_MJ_READ, IRP_MN_NONE); xstream_wr_u32_le(s, Length); xstream_wr_u64_le(s, Offset); xstream_seek(s, 20); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } } /** * Read data from previously opened file * * Errors are reported via xfuse_devredir_cb_write_file() *****************************************************************************/ void devredir_file_write(struct state_write *fusep, tui32 DeviceId, tui32 FileId, const char *buf, int Length, tui64 Offset) { struct stream *s; IRP *irp; IRP *new_irp; int bytes; LOG_DEVEL(LOG_LEVEL_DEBUG, "DeviceId=%d FileId=%d Length=%d Offset=%lld", DeviceId, FileId, Length, (long long)Offset); xstream_new(s, 1024 + Length); if ((irp = devredir_irp_find_by_fileid(FileId)) == NULL) { LOG_DEVEL(LOG_LEVEL_ERROR, "no IRP found with FileId = %d", FileId); xfuse_devredir_cb_write_file(fusep, STATUS_UNSUCCESSFUL, 0, 0); xstream_free(s); } /* create a new IRP for this request */ else if ((new_irp = devredir_irp_new()) == NULL) { /* system out of memory */ xfuse_devredir_cb_write_file(fusep, STATUS_UNSUCCESSFUL, 0, 0); xstream_free(s); } else { new_irp->DeviceId = DeviceId; new_irp->FileId = FileId; new_irp->completion_type = CID_WRITE; new_irp->CompletionId = g_completion_id++; new_irp->fuse_info = fusep; /* Offset needed after write to calculate new EOF */ new_irp->gen.write.offset = Offset; devredir_insert_DeviceIoRequest(s, DeviceId, FileId, new_irp->CompletionId, IRP_MJ_WRITE, IRP_MN_NONE); xstream_wr_u32_le(s, Length); xstream_wr_u64_le(s, Offset); xstream_seek(s, 20); /* padding */ /* now insert real data */ xstream_copyin(s, buf, Length); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } } int devredir_file_rename(struct state_rename *fusep, tui32 device_id, const char *old_name, const char *new_name) { tui32 DesiredAccess; tui32 CreateOptions; tui32 FileAttributes = 0; tui32 CreateDisposition; int rval = -1; IRP *irp; unsigned int len; LOG_DEVEL(LOG_LEVEL_DEBUG, "device_id=%d old_name=%s new_name=%s", device_id, old_name, new_name); /* * Allocate an IRP with enough space for both the old and new names. * We'll store the new name after the old name:- * * | n | a | m | e | 1 | \0 | n | a | m | e | 2 | \0 | * ^ ^ * irp->pathname ----+ | * irp->gen.rename.new_name ------------------+ */ len = strlen(old_name) + 1 + strlen(new_name); if ((irp = devredir_irp_with_pathnamelen_new(len)) != NULL) { /* Set up pointer to new name string */ irp->gen.rename.new_name = irp->pathname + strlen(old_name) + 1; /* Copy both strings, and change'/' to '\\' characters */ strcpy(irp->pathname, old_name); devredir_cvt_slash(irp->pathname); strcpy(irp->gen.rename.new_name, new_name); devredir_cvt_slash(irp->gen.rename.new_name); irp->completion_type = CID_RENAME_FILE; irp->CompletionId = g_completion_id++; irp->DeviceId = device_id; irp->fuse_info = fusep; DesiredAccess = DA_FILE_WRITE_ATTRIBUTES | DA_DELETE; CreateOptions = 0; CreateDisposition = CD_FILE_OPEN; // WAS 1 rval = devredir_send_drive_create_request(device_id, old_name, DesiredAccess, CreateOptions, FileAttributes, CreateDisposition, irp->CompletionId); } return rval; } /****************************************************************************** ** miscellaneous stuff ** ******************************************************************************/ void devredir_insert_DeviceIoRequest(struct stream *s, tui32 DeviceId, tui32 FileId, tui32 CompletionId, enum IRP_MJ MajorFunction, enum IRP_MN MinorFunction) { /* setup DR_DEVICE_IOREQUEST header */ xstream_wr_u16_le(s, RDPDR_CTYP_CORE); xstream_wr_u16_le(s, PAKID_CORE_DEVICE_IOREQUEST); xstream_wr_u32_le(s, DeviceId); xstream_wr_u32_le(s, FileId); xstream_wr_u32_le(s, CompletionId); xstream_wr_u32_le(s, MajorFunction); xstream_wr_u32_le(s, MinorFunction); } /** * Convert / to windows compatible \ *****************************************************************************/ static void devredir_cvt_slash(char *path) { char *cptr = path; while (*cptr != 0) { if (*cptr == '/') { *cptr = '\\'; } cptr++; } } static int devredir_string_ends_with(const char *string, char c) { size_t len; len = strlen(string); return (len > 0 && string[len - 1] == c) ? 1 : 0; } static void devredir_proc_cid_rmdir_or_file(IRP *irp, enum NTSTATUS IoStatus) { struct stream *s; int bytes; if (IoStatus != STATUS_SUCCESS) { xfuse_devredir_cb_rmdir_or_file((struct state_remove *) irp->fuse_info, IoStatus); devredir_irp_delete(irp); return; } xstream_new(s, 1024); irp->completion_type = CID_RMDIR_OR_FILE_RESP; devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_SET_INFORMATION, IRP_MN_NONE); xstream_wr_u32_le(s, FileDispositionInformation); xstream_wr_u32_le(s, 0); /* length is zero */ xstream_seek(s, 24); /* padding */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); return; } static void devredir_proc_cid_rmdir_or_file_resp(IRP *irp, enum NTSTATUS IoStatus) { xfuse_devredir_cb_rmdir_or_file((struct state_remove *)irp->fuse_info, IoStatus); if (IoStatus != STATUS_SUCCESS) { devredir_irp_delete(irp); return; } irp->completion_type = CID_CLOSE; devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } /** * Rename a file on response to the create drive request * * See :- * - [MS-RDPEFS] 2.2.3.3.9 (DR_DRIVE_SET_INFORMATION_REQ)` * - [MS-RDPEFS] 2.2.3.3.9.1 (RDP_FILE_RENAME_INFORMATION) *****************************************************************************/ static void devredir_proc_cid_rename_file(IRP *irp, enum NTSTATUS IoStatus) { struct stream *s; int bytes; unsigned int flen; /* FileNameLength */ unsigned int unicode_byte_count; /* Bytes to represent new name in UTF-16 */ unsigned int sblen; /* SetBuffer length */ if (IoStatus != STATUS_SUCCESS) { LOG_DEVEL(LOG_LEVEL_DEBUG, "rename returned with IoStatus=0x%x", IoStatus); xfuse_devredir_cb_rename_file((struct state_rename *)irp->fuse_info, IoStatus); devredir_irp_delete(irp); return; } /* Find number of words required for Unicode path */ flen = strlen(irp->gen.rename.new_name) + 1; // includes terminator unicode_byte_count = utf8_as_utf16_word_count(irp->gen.rename.new_name, flen) * 2; /* Length of RDP_FILE_RENAME_INFORMATION struct */ sblen = (1 + 1 + 4) + unicode_byte_count; xstream_new(s, (int)(64 + unicode_byte_count)); irp->completion_type = CID_RENAME_FILE_RESP; devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_SET_INFORMATION, IRP_MN_NONE); xstream_wr_u32_le(s, FileRenameInformation); xstream_wr_u32_le(s, sblen); /* number of bytes after padding */ xstream_seek(s, 24); /* padding */ xstream_wr_u8(s, 1); /* ReplaceIfExists */ xstream_wr_u8(s, 0); /* RootDirectory */ xstream_wr_u32_le(s, unicode_byte_count); /* FileNameLength */ /* filename in Unicode */ out_utf8_as_utf16_le(s, irp->gen.rename.new_name, flen); /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); return; } static void devredir_proc_cid_rename_file_resp(IRP *irp, enum NTSTATUS IoStatus) { xfuse_devredir_cb_rename_file((struct state_rename *)irp->fuse_info, IoStatus); if (IoStatus != STATUS_SUCCESS) { devredir_irp_delete(irp); return; } irp->completion_type = CID_CLOSE; devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } /* * Re-uses the specified IRP to issue a request to get file attributes * of varying types * * References : [MS-RDPEFS] 2.2.3.3.9 [MS-FSCC] 2.4 *****************************************************************************/ static void issue_lookup(IRP *irp, int lookup_type) { struct stream *s; int bytes; bytes = lookup_type == FileBasicInformation ? FILE_BASIC_INFORMATION_SIZE : lookup_type == FileStandardInformation ? FILE_STD_INFORMATION_SIZE : 0; xstream_new(s, 1024); devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_QUERY_INFORMATION, IRP_MN_NONE); xstream_wr_u32_le(s, lookup_type); xstream_wr_u32_le(s, bytes); /* buffer length */ xstream_seek(s, 24); /* padding */ xstream_seek(s, bytes); /* buffer */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } /* * Parses an incoming FileBasicInformation structure *****************************************************************************/ static void lookup_read_basic_attributes(IRP *irp, struct stream *s_in) { tui64 LastAccessTime; tui64 LastWriteTime; tui32 FileAttributes; LOG_DEVEL(LOG_LEVEL_DEBUG, "processing FILE_BASIC_INFORMATION"); xstream_seek(s_in, 8); /* CreationTime */ xstream_rd_u64_le(s_in, LastAccessTime); xstream_rd_u64_le(s_in, LastWriteTime); xstream_seek(s_in, 8); /* ChangeTime */ xstream_rd_u32_le(s_in, FileAttributes); //LOG_DEVEL(LOG_LEVEL_DEBUG, "LastAccessTime: 0x%llx", // (unsigned long long)LastAccessTime); //LOG_DEVEL(LOG_LEVEL_DEBUG, "LastWriteTime: 0x%llx", // (unsigned long long)LastWriteTime); //LOG_DEVEL(LOG_LEVEL_DEBUG, "ChangeTime: 0x%llx", // (unsigned long long)ChangeTime); //LOG_DEVEL(LOG_LEVEL_DEBUG, "FileAttributes: 0x%x", (unsigned int)FileAttributes); /* Save the basic attributes in the IRP */ irp->gen.lookup.fattr.mode = WindowsToLinuxFilePerm(FileAttributes); irp->gen.lookup.fattr.atime = WINDOWS_TO_LINUX_TIME(LastAccessTime); irp->gen.lookup.fattr.mtime = WINDOWS_TO_LINUX_TIME(LastWriteTime); } /* * Parses an incoming FileStandardInformation structure *****************************************************************************/ static void lookup_read_standard_attributes(IRP *irp, struct stream *s_in) { tui64 EndOfFile; LOG_DEVEL(LOG_LEVEL_DEBUG, "processing FILE_STD_INFORMATION"); xstream_seek(s_in, 8); /* AllocationSize */ xstream_rd_u64_le(s_in, EndOfFile); //LOG_DEVEL(LOG_LEVEL_DEBUG, "EndOfFile: %lld", // (unsigned long long)EndOfFile); irp->gen.lookup.fattr.size = EndOfFile; } /* * Completes a lookup request and returns status to the caller. * * Unless IoStatus is STATUS_SUCCESS, the lookup has failed. *****************************************************************************/ static void lookup_done(IRP *irp, enum NTSTATUS IoStatus) { LOG_DEVEL(LOG_LEVEL_DEBUG, "Lookup with completion_id=%d returning 0x%x", irp->CompletionId, IoStatus); xfuse_devredir_cb_lookup_entry((struct state_lookup *)irp->fuse_info, IoStatus, &irp->gen.lookup.fattr); if (irp->FileId == 0) { /* Open failed - no file handle */ devredir_irp_delete(irp); } else { /* Close the file handle */ irp->completion_type = CID_CLOSE; devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } } /* * lookup has a mini state machine built-in, as it needs to issue * multiple I/O requests, but unlike lookup these are not always the same. */ static void devredir_proc_cid_lookup(IRP *irp, struct stream *s_in, enum NTSTATUS IoStatus) { tui32 Length; LOG_DEVEL(LOG_LEVEL_DEBUG, "entry state is %d", irp->gen.lookup.state); if (IoStatus != STATUS_SUCCESS) { /* This is common to all setattr states */ LOG_DEVEL(LOG_LEVEL_DEBUG, "last lookup returned with IoStatus=0x%08x", IoStatus); lookup_done(irp, IoStatus); } else { /* Read and validate any data we've got queued up */ switch (irp->gen.lookup.state) { case E_LOOKUP_GET_FH: /* We've been sent the file ID */ xstream_rd_u32_le(s_in, irp->FileId); issue_lookup(irp, FileBasicInformation); irp->gen.lookup.state = E_LOOKUP_CHECK_BASIC; break; case E_LOOKUP_CHECK_BASIC: /* Returned length what we expected? */ xstream_rd_u32_le(s_in, Length); if (Length != FILE_BASIC_INFORMATION_SIZE) { LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_BASIC_INFORMATION length" "%d, got len=%d", FILE_BASIC_INFORMATION_SIZE, Length); IoStatus = STATUS_UNSUCCESSFUL; lookup_done(irp, IoStatus); } else { lookup_read_basic_attributes(irp, s_in); issue_lookup(irp, FileStandardInformation); irp->gen.lookup.state = E_LOOKUP_CHECK_EOF; } break; case E_LOOKUP_CHECK_EOF: /* Returned length what we expected? */ xstream_rd_u32_le(s_in, Length); if (Length != FILE_STD_INFORMATION_SIZE) { LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_STD_INFORMATION length" "%d, got len=%d", FILE_STD_INFORMATION_SIZE, Length); IoStatus = STATUS_UNSUCCESSFUL; } else { lookup_read_standard_attributes(irp, s_in); } lookup_done(irp, IoStatus); break; } } } /* * Re-uses the specified IRP to issue a request to set basic file attributes * * References : [MS-RDPEFS] 2.2.3.3.9 [MS-FSCC] 2.4.7 *****************************************************************************/ static void issue_setattr_basic(IRP *irp) { struct stream *s; int bytes; const struct file_attr *fattr = &irp->gen.setattr.fattr; tui32 to_set = irp->gen.setattr.to_set; tui32 FileAttributes = 0; tui64 atime = 0; tui64 mtime = 0; if (to_set & TO_SET_MODE) { FileAttributes = LinuxToWindowsFilePerm(fattr->mode); } if (to_set & TO_SET_ATIME) { atime = LINUX_TO_WINDOWS_TIME(fattr->atime); } if (to_set & TO_SET_MTIME) { mtime = LINUX_TO_WINDOWS_TIME(fattr->mtime); } xstream_new(s, 1024); devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_SET_INFORMATION, IRP_MN_NONE); xstream_wr_u32_le(s, FileBasicInformation); xstream_wr_u32_le(s, FILE_BASIC_INFORMATION_SIZE); /* buffer length */ xstream_seek(s, 24); /* padding */ xstream_wr_u64_le(s, 0LL); /* CreationTime */ xstream_wr_u64_le(s, atime); /* LastAccessTime */ xstream_wr_u64_le(s, mtime); /* LastWriteTime */ xstream_wr_u64_le(s, 0LL); /* ChangeTime */ xstream_wr_u32_le(s, FileAttributes); /* FileAttributes */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } /* * Re-uses the specified IRP to issue a request to set file EOF * * References : [MS-RDPEFS] 2.2.3.3.9 [MS-FSCC] 2.4.13 *****************************************************************************/ static void issue_setattr_eof(IRP *irp) { struct stream *s; int bytes; xstream_new(s, 1024); devredir_insert_DeviceIoRequest(s, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_SET_INFORMATION, IRP_MN_NONE); xstream_wr_u32_le(s, FileEndOfFileInformation); xstream_wr_u32_le(s, FILE_END_OF_FILE_INFORMATION_SIZE); /* buffer length */ xstream_seek(s, 24); /* padding */ xstream_wr_u64_le(s, (tui64)irp->gen.setattr.fattr.size); /* File size */ /* send to client */ bytes = xstream_len(s); send_channel_data(g_rdpdr_chan_id, s->data, bytes); xstream_free(s); } /* * Completes a setattr request and returns status to the caller. *****************************************************************************/ static void setattr_done(IRP *irp, enum NTSTATUS IoStatus) { xfuse_devredir_cb_setattr((struct state_setattr *) irp->fuse_info, IoStatus); if (irp->FileId == 0) { /* Open failed - no file handle */ devredir_irp_delete(irp); } else { /* Close the file handle */ irp->completion_type = CID_CLOSE; devredir_send_drive_close_request(RDPDR_CTYP_CORE, PAKID_CORE_DEVICE_IOREQUEST, irp->DeviceId, irp->FileId, irp->CompletionId, IRP_MJ_CLOSE, IRP_MN_NONE, 32); } } /* * setattr has a mini state machine built-in, as it needs to issue * multiple I/O requests, but unlike lookup these are not always the same. */ static void devredir_proc_cid_setattr(IRP *irp, struct stream *s_in, enum NTSTATUS IoStatus) { #define TO_SET_BASIC_ATTRS (TO_SET_MODE | \ TO_SET_ATIME | TO_SET_MTIME) tui32 Length; LOG_DEVEL(LOG_LEVEL_DEBUG, "entry state is %d", irp->gen.setattr.state); if (IoStatus != STATUS_SUCCESS) { /* This is common to all setattr states */ LOG_DEVEL(LOG_LEVEL_DEBUG, "last setattr returned with IoStatus=0x%08x", IoStatus); setattr_done(irp, IoStatus); } else { /* Read and validate any data we've got queued up */ switch (irp->gen.setattr.state) { case E_SETATTR_GET_FH: /* We've been sent the file ID */ xstream_rd_u32_le(s_in, irp->FileId); break; case E_SETATTR_CHECK_BASIC: /* Returned length what we expected? */ xstream_rd_u32_le(s_in, Length); if (Length != FILE_BASIC_INFORMATION_SIZE) { LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_BASIC_INFORMATION length" "%d, got len=%d", FILE_BASIC_INFORMATION_SIZE, Length); } /* Clear the basic bits so we don't end up in here again */ irp->gen.setattr.to_set &= ~TO_SET_BASIC_ATTRS; break; case E_SETATTR_CHECK_EOF: /* Returned length what we expected? */ xstream_rd_u32_le(s_in, Length); if (Length != FILE_END_OF_FILE_INFORMATION_SIZE) { LOG_DEVEL(LOG_LEVEL_ERROR, "Expected FILE_END_OF_FILE_INFORMATION length" "%d, got len=%d", FILE_END_OF_FILE_INFORMATION_SIZE, Length); } /* Clear the size bits so we don't end up in here again */ irp->gen.setattr.to_set &= ~TO_SET_SIZE; break; } /* Work out the next call to issue */ if (irp->gen.setattr.to_set & TO_SET_BASIC_ATTRS) { issue_setattr_basic(irp); irp->gen.setattr.state = E_SETATTR_CHECK_BASIC; } else if (irp->gen.setattr.to_set & TO_SET_SIZE) { issue_setattr_eof(irp); irp->gen.setattr.state = E_SETATTR_CHECK_EOF; } else { setattr_done(irp, IoStatus); } } #undef TO_SET_BASIC_ATTRS }