// // Created by wolverindev on 26.01.20. // #include #include #include #include #include "../../build.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../../server/VoiceServer.h" #include "../voice/VoiceClient.h" #include "../../InstanceHandler.h" #include "../../server/QueryServer.h" #include "../music/MusicClient.h" #include "../query/QueryClient.h" #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include "helpers.h" #include #include #include #include #include #include #include #include using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; constexpr static auto kFileAPITimeout = std::chrono::milliseconds{500}; constexpr static auto kMaxClientTransfers = 10; #define QUERY_PASSWORD_LENGTH 12 //ftgetfilelist cid=1 cpw path=\/ return_code=1:x //Answer: //1 .. n // notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0 //notifyfilelistfinished cid=1 path=\/ command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) { using directory_query_response_t = file::filesystem::AbstractProvider::directory_query_response_t; using DirectoryEntry = file::filesystem::DirectoryEntry; CMD_REQ_SERVER; CMD_RESET_IDLE; auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto& file_system = file::server()->file_system(); auto directory_path = cmd["path"].string(); std::shared_ptr query_result{}; if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; if(!channel->permission_granted(permission::i_ft_needed_file_browse_power, this->calculate_permission(permission::i_ft_file_browse_power, channel->channelId()), true)) return command_result{permission::i_ft_file_browse_power}; query_result = file_system.query_channel_directory(virtual_file_server, cmd["cid"].as(), cmd["path"].string()); } else { if (directory_path == "/icons" || directory_path == "/icons/") { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_icon_manage, 0))) return command_result{permission::b_icon_manage}; query_result = file_system.query_icon_directory(virtual_file_server); } else if (directory_path == "/") { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_icon_manage, 0))) return command_result{permission::b_icon_manage}; query_result = file_system.query_avatar_directory(virtual_file_server); } else { debugMessage(this->getServerId(), "{} Requested file list for unknown path/name: path: {} name: {}", cmd["path"].string(), cmd["name"].string()); return command_result{error::parameter_invalid, "path"}; } } if(!query_result->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!query_result->succeeded()) { debugMessage(this->getServerId(), "{} Failed to query directory: {} / {}", CLIENT_STR_LOG_PREFIX, file::filesystem::directory_query_error_messages[(int) query_result->error().error_type], query_result->error().error_message); using ErrorType = file::filesystem::DirectoryQueryErrorType; switch(query_result->error().error_type) { case ErrorType::UNKNOWN: case ErrorType::FAILED_TO_LIST_FILES: return command_result{error::vs_critical, query_result->error().error_message}; case ErrorType::PATH_IS_A_FILE: case ErrorType::PATH_EXCEEDS_ROOT_PATH: return command_result{error::file_invalid_path}; case ErrorType::PATH_DOES_NOT_EXISTS: /* directory has not been created because there are no files */ if(directory_path.empty() || directory_path == "/") return command_result{error::database_empty_result}; return command_result{error::file_not_found}; default: assert(false); return command_result{error::vs_critical}; } } const auto& files = query_result->response(); if(files.empty()) return command_result{error::database_empty_result}; auto return_code = cmd["return_code"].size() > 0 ? cmd["return_code"].string() : ""; { ts::command_builder notify_file_list{this->notify_response_command("notifyfilelist")}; size_t bulk_index{0}; for(const auto& file : files) { if(bulk_index == 0) { notify_file_list.reset(); notify_file_list.put_unchecked(0, "path", cmd["path"].string()); notify_file_list.put_unchecked(0, "cid", cmd["cid"].string()); if(!return_code.empty()) notify_file_list.put_unchecked(0, "return_code", return_code); } auto bulk = notify_file_list.bulk(bulk_index++); switch(file.type) { case DirectoryEntry::DIRECTORY: bulk.put_unchecked("type", "0"); break; case DirectoryEntry::FILE: bulk.put_unchecked("type", "1"); break; case DirectoryEntry::UNKNOWN: bulk.reset(); bulk_index--; continue; } bulk.put_unchecked("name", file.name); bulk.put_unchecked("size", file.size); bulk.put_unchecked("datetime", std::chrono::duration_cast(file.modified_at.time_since_epoch()).count()); if(bulk_index >= 16) { this->sendCommand(notify_file_list); bulk_index = 0; } } if(bulk_index > 0) this->sendCommand(notify_file_list); } if(this->getExternalType() != ClientType::CLIENT_QUERY) { ts::command_builder notify_file_list_finished{this->notify_response_command("notifyfilelistfinished")}; notify_file_list_finished.put_unchecked(0, "path", cmd["path"].string()); notify_file_list_finished.put_unchecked(0, "cid", cmd["cid"].string()); if(!return_code.empty()) notify_file_list_finished.put_unchecked(0, "return_code", return_code); this->sendCommand(notify_file_list_finished); } return command_result{error::ok}; } //ftcreatedir cid=4 cpw dirname=\/TestDir return_code=1:17 command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { using ErrorType = file::filesystem::DirectoryModifyErrorType; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto& file_system = file::server()->file_system(); auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; if(!channel->permission_granted(permission::i_ft_needed_directory_create_power, this->calculate_permission(permission::i_ft_directory_create_power, channel->channelId()), true)) return command_result{permission::i_ft_directory_create_power}; auto create_result = file_system.create_channel_directory(virtual_file_server, channel->channelId(), cmd["dirname"].string()); if(!create_result->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!create_result->succeeded()) { debugMessage(this->getServerId(), "{} Failed to create channel directory: {} / {}", CLIENT_STR_LOG_PREFIX, (int) create_result->error().error_type, create_result->error().error_message); switch(create_result->error().error_type) { case ErrorType::UNKNOWN: case ErrorType::FAILED_TO_CREATE_DIRECTORIES: { auto error_detail = std::to_string((int) create_result->error().error_type); if(!create_result->error().error_message.empty()) error_detail += "/" + create_result->error().error_message; return command_result{error::file_io_error, error_detail}; } case ErrorType::PATH_ALREADY_EXISTS: return command_result{error::file_already_exists}; case ErrorType::PATH_EXCEEDS_ROOT_PATH: return command_result{error::file_invalid_path}; } } return command_result{error::ok}; } command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { using ErrorType = file::filesystem::FileDeleteErrorType; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto& file_system = file::server()->file_system(); ts::command_result_bulk response{}; response.emplace_result_n(cmd.bulkCount(), error::ok); auto file_path = cmd["path"].string(); std::shared_ptr> delete_response{}; if (cmd[0].has("cid") && cmd["cid"] != 0) { auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; if(!channel->permission_granted(permission::i_ft_needed_file_delete_power, this->calculate_permission(permission::i_ft_file_delete_power, channel->channelId()), true)) return command_result{permission::i_ft_file_delete_power}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) delete_files.push_back(cmd[index]["name"].string()); delete_response = file_system.delete_channel_files(virtual_file_server, channel->channelId(), delete_files); } else { auto first_entry_name = cmd["name"].string(); if (first_entry_name.find("/icon_") == 0 && file_path.empty()) { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_icon_manage, 0))) return command_result{permission::b_icon_manage}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { auto file_name = cmd[index]["name"].string(); if(!file_name.starts_with("/icon_")) { response.set_result(index, ts::command_result{error::parameter_constraint_violation}); continue; } delete_files.push_back(file_name); } delete_response = file_system.delete_icons(virtual_file_server, delete_files); } else if (first_entry_name.starts_with("/avatar_") && file_path.empty()) { enum PermissionTestState { SUCCEEDED, FAILED, UNSET } permission_delete_other{PermissionTestState::UNSET}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { auto file_name = cmd[index]["name"].string(); if(!file_name.starts_with("/avatar_")) { response.set_result(index, ts::command_result{error::parameter_constraint_violation}); continue; } if (file_name != "/avatar_") { if(permission_delete_other == PermissionTestState::UNSET) { if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_avatar_delete_other, 0))) permission_delete_other = PermissionTestState::SUCCEEDED; else permission_delete_other = PermissionTestState::FAILED; } if(permission_delete_other != PermissionTestState::SUCCEEDED) { response.set_result(index, ts::command_result{permission::b_client_avatar_delete_other}); continue; } auto uid = file_name.substr(strlen("/avatar_")); auto avId = hex::hex(base64::decode(uid), 'a', 'q'); auto cls = this->server->findClientsByUid(uid); for (const auto &cl : cls) { cl->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(cl, deque{property::CLIENT_FLAG_AVATAR}); } delete_files.push_back("/avatar_" + avId); } else { this->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); } } delete_response = file_system.delete_avatars(virtual_file_server, delete_files); } else { logError(this->getServerId(), "Unknown requested file to delete: {}", cmd["path"].as()); return command_result{error::not_implemented}; } } if(!delete_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!delete_response->succeeded()) { debugMessage(this->getServerId(), "{} Failed to create channel directory: {} / {}", CLIENT_STR_LOG_PREFIX, (int) delete_response->error().error_type, delete_response->error().error_message); switch(delete_response->error().error_type) { case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) delete_response->error().error_type); if(!delete_response->error().error_message.empty()) error_detail += "/" + delete_response->error().error_message; return command_result{error::vs_critical, error_detail}; } } } const auto& file_status = delete_response->response(); size_t bulk_index{0}; for(const auto& file : file_status.delete_results) { while(response.response(bulk_index).error_code() != error::ok) bulk_index++; using Status = file::filesystem::FileDeleteResponse::StatusType; switch (file.status) { case Status::SUCCESS: /* we already emplaced success */ break; case Status::PATH_EXCEEDS_ROOT_PATH: response.set_result(bulk_index, ts::command_result{error::file_invalid_path}); break; case Status::PATH_DOES_NOT_EXISTS: response.set_result(bulk_index, ts::command_result{error::file_not_found}); break; case Status::SOME_FILES_ARE_LOCKED: response.set_result(bulk_index, ts::command_result{error::file_already_in_use, file.error_detail}); break; case Status::FAILED_TO_DELETE_FILES: response.set_result(bulk_index, ts::command_result{error::file_io_error, file.error_detail}); break; } bulk_index++; } while(response.length() > bulk_index && response.response(bulk_index).type() == command_result_type::error && response.response(bulk_index).error_code() != error::ok) bulk_index++; assert(bulk_index == cmd.bulkCount()); return command_result{std::move(response)}; } /* * Usage: ftgetfileinfo cid={channelID} cpw={channelPassword} name={filePath}... Permissions: i_ft_file_browse_power i_ft_needed_file_browse_power Description: Displays detailed information about one or more specified files stored in a channels file repository. Example: ftgetfileinfo cid=2 cpw= path=\/Pic1.PNG|cid=2 cpw= path=\/Pic2.PNG cid=2 path=\/ name=Stuff size=0 datetime=1259415210 type=0|name=Pic1.PNG size=563783 datetime=1259425462 type=1|name=Pic2.PNG ... error id=0 msg=ok */ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) { using ErrorType = file::filesystem::FileInfoErrorType; using DirectoryEntry = file::filesystem::DirectoryEntry; CMD_RESET_IDLE; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto& file_system = file::server()->file_system(); ts::command_result_bulk response{}; response.emplace_result_n(cmd.bulkCount(), error::ok); auto file_path = cmd["path"].string(); std::shared_ptr> info_response{}; if (cmd[0].has("cid") && cmd["cid"] != 0) { auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; if(!channel->permission_granted(permission::i_ft_needed_file_browse_power, this->calculate_permission(permission::i_ft_file_browse_power, channel->channelId()), true)) return command_result{permission::i_ft_file_browse_power}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) delete_files.push_back(cmd[index]["name"].string()); info_response = file_system.query_channel_info(virtual_file_server, channel->channelId(), delete_files); } else { auto first_entry_name = cmd["name"].string(); if (first_entry_name.find("/icon_") == 0 && file_path.empty()) { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_icon_manage, 0))) return command_result{permission::b_icon_manage}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { auto file_name = cmd[index]["name"].string(); if(!file_name.starts_with("/icon_")) { response.set_result(index, ts::command_result{error::parameter_constraint_violation}); continue; } delete_files.push_back(file_name); } info_response = file_system.query_icon_info(virtual_file_server, delete_files); } else if (first_entry_name.starts_with("/avatar_") && file_path.empty()) { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_icon_manage, 0))) return command_result{permission::b_icon_manage}; std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { auto file_name = cmd[index]["name"].string(); if(!file_name.starts_with("/avatar_")) { response.set_result(index, ts::command_result{error::parameter_constraint_violation}); continue; } if (file_name != "/avatar_") { auto uid = file_name.substr(strlen("/avatar_")); auto avId = hex::hex(base64::decode(uid), 'a', 'q'); delete_files.push_back("/avatar_" + avId); } else { this->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); } } info_response = file_system.query_avatar_info(virtual_file_server, delete_files); } else { logError(this->getServerId(), "Unknown requested file to query info: {}", cmd["path"].as()); return command_result{error::parameter_invalid}; } } if(!info_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!info_response->succeeded()) { debugMessage(this->getServerId(), "{} Failed to execute file info query: {} / {}", CLIENT_STR_LOG_PREFIX, (int) info_response->error().error_type, info_response->error().error_message); switch(info_response->error().error_type) { case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) info_response->error().error_type); if(!info_response->error().error_message.empty()) error_detail += "/" + info_response->error().error_message; return command_result{error::vs_critical, error_detail}; } } } const auto& file_status = info_response->response(); size_t bulk_index{0}; ts::command_builder notify_file_info{this->notify_response_command("notifyfileinfo")}; size_t notify_index{0}; for(const auto& file : file_status.file_info) { while(response.response(bulk_index).error_code() != error::ok) bulk_index++; using Status = file::filesystem::FileInfoResponse::StatusType; switch (file.status) { case Status::SUCCESS: /* we already emplaced success */ break; case Status::PATH_EXCEEDS_ROOT_PATH: response.set_result(bulk_index, ts::command_result{error::file_invalid_path}); break; case Status::PATH_DOES_NOT_EXISTS: case Status::UNKNOWN_FILE_TYPE: response.set_result(bulk_index, ts::command_result{error::file_not_found}); break; case Status::FAILED_TO_QUERY_INFO: response.set_result(bulk_index, ts::command_result{error::file_io_error, file.error_detail}); break; } bulk_index++; auto bulk = notify_file_info.bulk(notify_index++); switch(file.info.type) { case DirectoryEntry::DIRECTORY: bulk.put_unchecked("type", "0"); break; case DirectoryEntry::FILE: bulk.put_unchecked("type", "1"); break; case DirectoryEntry::UNKNOWN: bulk.reset(); notify_index--; continue; } bulk.put_unchecked("name", file.info.name); bulk.put_unchecked("size", file.info.size); bulk.put_unchecked("datetime", std::chrono::duration_cast(file.info.modified_at.time_since_epoch()).count()); } if(notify_index > 0) this->sendCommand(notify_file_info); while(response.length() > bulk_index && response.response(bulk_index).type() == command_result_type::error && response.response(bulk_index).error_code() != error::ok) bulk_index++; assert(bulk_index == cmd.bulkCount()); return command_result{std::move(response)}; } /* ftinitupload clientftfid={clientFileTransferID} name={filePath} cid={channelID} cpw={channelPassword} size={fileSize} overwrite={1|0} resume={1|0} [proto=0-1] */ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) { CMD_REQ_SERVER; auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; if(!cmd[0].has("path")) cmd["path"] = ""; file::transfer::AbstractProvider::TransferInfo info{}; std::shared_ptr>> transfer_response{}; info.max_bandwidth = -1; { auto max_quota = this->calculate_permission(permission::i_ft_max_bandwidth_upload, this->getClientId()); if(max_quota.has_value) info.max_bandwidth = max_quota.value; } info.file_offset = 0; info.expected_file_size = cmd["size"].as(); info.override_exiting = cmd["overwrite"].as(); info.file_path = cmd["name"].string(); info.client_unique_id = this->getUid(); info.client_id = this->getClientId(); info.max_concurrent_transfers = kMaxClientTransfers; /* TODO: Save last file offset and resume */ if(cmd["resume"].as() && info.override_exiting) return ts::command_result{error::file_overwrite_excludes_resume}; { auto server_quota = this->server->properties()[property::VIRTUALSERVER_UPLOAD_QUOTA].as(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as(); server_used_quota += cmd["size"].as(); if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_upload_per_client, 0); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].as(); client_used_quota += cmd["size"].as(); if(client_quota.has_value && !client_quota.has_infinite_power() && (client_quota.value < 0 || client_quota.value * 1024 * 1024 < (int64_t) client_used_quota)) return command_result{error::file_transfer_client_quota_exceeded}; } if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_upload_power, permission::i_ft_file_upload_power, true); transfer_response = file::server()->file_transfer().initialize_channel_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, virtual_file_server, channel->channelId(), info); } else { if (cmd["path"].string().empty() && cmd["name"].string().starts_with("/icon_") == 0) { auto max_size = this->calculate_permission(permission::i_max_icon_filesize, 0); if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as())) return command_result{permission::i_max_icon_filesize}; transfer_response = file::server()->file_transfer().initialize_icon_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, virtual_file_server, info); } else if (cmd["path"].as().empty() && cmd["name"].string() == "/avatar") { auto max_size = this->calculate_permission(permission::i_client_max_avatar_filesize, 0); if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as())) return command_result{permission::i_client_max_avatar_filesize}; info.file_path = "/avatar_" + this->getAvatarId(); transfer_response = file::server()->file_transfer().initialize_avatar_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, virtual_file_server, info); } else { return command_result{error::parameter_invalid, "name"}; } } if(!transfer_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!transfer_response->succeeded()) { using ErrorType = file::transfer::TransferInitError; debugMessage(this->getServerId(), "{} Failed to initialize file upload: {} / {}", CLIENT_STR_LOG_PREFIX, (int) transfer_response->error().error_type, transfer_response->error().error_message); switch(transfer_response->error().error_type) { case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) transfer_response->error().error_type); if(!transfer_response->error().error_message.empty()) error_detail += "/" + transfer_response->error().error_message; return command_result{error::vs_critical, error_detail}; } case ErrorType::IO_ERROR: return command_result{error::file_io_error, transfer_response->error().error_message}; case ErrorType::FILE_IS_NOT_A_FILE: case ErrorType::INVALID_FILE_TYPE: case ErrorType::FILE_DOES_NOT_EXISTS: return command_result{error::file_not_found}; case ErrorType::SERVER_QUOTA_EXCEEDED: return command_result{error::file_transfer_server_quota_exceeded}; case ErrorType::CLIENT_QUOTA_EXCEEDED: return command_result{error::file_transfer_client_quota_exceeded}; case ErrorType::SERVER_TOO_MANY_TRANSFERS: return command_result{error::file_server_transfer_limit_reached}; case ErrorType::CLIENT_TOO_MANY_TRANSFERS: return command_result{error::file_client_transfer_limit_reached}; } } auto transfer = transfer_response->response(); if(transfer->server_addresses.empty()) { logError(0, "{} Received transfer without any addresses!", CLIENT_STR_LOG_PREFIX); return ts::command_result{error::vs_critical}; } transfer->client_transfer_id = cmd["clientftfid"]; ts::command_builder result{this->notify_response_command("notifystartupload")}; result.put_unchecked(0, "clientftfid", cmd["clientftfid"].string()); result.put_unchecked(0, "serverftfid", transfer->server_transfer_id); auto used_address = transfer->server_addresses[0]; result.put_unchecked(0, "ip", used_address.hostname); result.put_unchecked(0, "port", used_address.port); result.put_unchecked(0, "ftkey", std::string_view{transfer->transfer_key, TRANSFER_KEY_LENGTH}); result.put_unchecked(0, "seekpos", transfer->file_offset); result.put_unchecked(0, "proto", "1"); this->sendCommand(result); return command_result{error::ok}; } /* ftinitdownload clientftfid={clientFileTransferID} name={filePath} cid={channelID} cpw={channelPassword} seekpos={seekPosition} [proto=0-1] */ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) { CMD_REQ_SERVER; auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; if(!cmd[0].has("path")) cmd["path"] = ""; file::transfer::AbstractProvider::TransferInfo info{}; std::shared_ptr>> transfer_response{}; { auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as(); if(server_quota >= 0) { if((size_t) server_quota * 1024 * 1024 <= server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; info.download_server_quota_limit = server_quota * 1024 * 1024 - server_used_quota; } auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_download_per_client, 0); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); if(client_quota.has_value) { if(client_quota.value > 0) { if((size_t) client_quota.value * 1024 * 1024 <= client_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; info.download_client_quota_limit = client_quota.value * 1024 * 1024 - client_used_quota; } else if(client_quota.value != -1) { return command_result{error::file_transfer_client_quota_exceeded}; } } } info.max_bandwidth = -1; { auto max_quota = this->calculate_permission(permission::i_ft_max_bandwidth_download, this->getClientId()); if(max_quota.has_value) info.max_bandwidth = max_quota.value; } info.file_offset = cmd["seekpos"].as(); info.override_exiting = false; info.file_path = cmd["name"].string(); info.client_unique_id = this->getUid(); info.client_id = this->getClientId(); info.max_concurrent_transfers = kMaxClientTransfers; if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_download_power, permission::i_ft_file_download_power, true); transfer_response = file::server()->file_transfer().initialize_channel_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, virtual_file_server, channel->channelId(), info); } else { if (cmd["path"].as().empty() && cmd["name"].string().find("/icon_") == 0) { transfer_response = file::server()->file_transfer().initialize_icon_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, virtual_file_server, info); } else if (cmd["path"].as().empty() && cmd["name"].string() == "/avatar") { info.file_path = "/avatar_" + this->getAvatarId(); transfer_response = file::server()->file_transfer().initialize_avatar_transfer(file::transfer::Transfer::DIRECTION_DOWNLOAD, virtual_file_server, info); } else { return command_result{error::parameter_invalid, "name"}; } } if(!transfer_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!transfer_response->succeeded()) { using ErrorType = file::transfer::TransferInitError; debugMessage(this->getServerId(), "{} Failed to initialize file download: {} / {}", CLIENT_STR_LOG_PREFIX, (int) transfer_response->error().error_type, transfer_response->error().error_message); switch(transfer_response->error().error_type) { case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) transfer_response->error().error_type); if(!transfer_response->error().error_message.empty()) error_detail += "/" + transfer_response->error().error_message; return command_result{error::vs_critical, error_detail}; } case ErrorType::IO_ERROR: return command_result{error::file_io_error, transfer_response->error().error_message}; case ErrorType::FILE_IS_NOT_A_FILE: case ErrorType::INVALID_FILE_TYPE: case ErrorType::FILE_DOES_NOT_EXISTS: return command_result{error::file_not_found}; case ErrorType::SERVER_QUOTA_EXCEEDED: return command_result{error::file_transfer_server_quota_exceeded}; case ErrorType::CLIENT_QUOTA_EXCEEDED: return command_result{error::file_transfer_client_quota_exceeded}; case ErrorType::SERVER_TOO_MANY_TRANSFERS: return command_result{error::file_server_transfer_limit_reached}; case ErrorType::CLIENT_TOO_MANY_TRANSFERS: return command_result{error::file_client_transfer_limit_reached}; } } auto transfer = transfer_response->response(); if(transfer->server_addresses.empty()) { logError(0, "{} Received transfer without any addresses!", CLIENT_STR_LOG_PREFIX); return ts::command_result{error::vs_critical}; } transfer->client_transfer_id = cmd["clientftfid"]; ts::command_builder result{this->notify_response_command("notifystartdownload")}; result.put_unchecked(0, "clientftfid", cmd["clientftfid"].string()); result.put_unchecked(0, "serverftfid", transfer->server_transfer_id); auto used_address = transfer->server_addresses[0]; result.put_unchecked(0, "ip", used_address.hostname); result.put_unchecked(0, "port", used_address.port); result.put_unchecked(0, "ftkey", std::string_view{transfer->transfer_key, TRANSFER_KEY_LENGTH}); result.put_unchecked(0, "proto", "1"); result.put_unchecked(0, "size", transfer->expected_file_size); result.put_unchecked(0, "seekpos", transfer->file_offset); this->sendCommand(result); return command_result{error::ok}; } /* * Usage: ftrenamefile cid={channelID} cpw={channelPassword} [tcid={targetChannelID}] [tcpw={targetChannelPassword}] oldname={oldFilePath} newname={newFilePath} i_ft_file_rename_power i_ft_needed_file_rename_power */ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) { CMD_RESET_IDLE; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto channel_id = cmd["cid"].as(); auto target_channel_id = cmd[0].has("tcid") ? cmd["tcid"].as() : channel_id; auto channel = this->server->channelTree->findChannel(channel_id); if (!channel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_rename_power, permission::i_ft_file_rename_power, true); if(target_channel_id != channel_id) { auto targetChannel = this->server->channelTree->findChannel(target_channel_id); if (!targetChannel) return command_result{error::channel_invalid_id}; if (!channel->passwordMatch(cmd["tcpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) return cmd["tcpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; ACTION_REQUIRES_CHANNEL_PERMISSION(targetChannel, permission::i_ft_needed_file_rename_power, permission::i_ft_file_rename_power, true); } auto rename_response = file::server()->file_system().rename_channel_file(virtual_file_server, channel_id, cmd["oldname"].string(), target_channel_id, cmd["newname"].string()); if(!rename_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!rename_response->succeeded()) { using ErrorType = file::filesystem::FileModifyErrorType; debugMessage(this->getServerId(), "{} Failed to rename file: {} / {}", CLIENT_STR_LOG_PREFIX, (int) rename_response->error().error_type, rename_response->error().error_message); switch(rename_response->error().error_type) { case ErrorType::UNKNOWN: case ErrorType::FAILED_TO_RENAME_FILE: case ErrorType::FAILED_TO_DELETE_FILES: case ErrorType::FAILED_TO_CREATE_DIRECTORIES: { auto error_detail = std::to_string((int) rename_response->error().error_type); if(!rename_response->error().error_message.empty()) error_detail += "/" + rename_response->error().error_message; return command_result{error::vs_critical, error_detail}; } case ErrorType::PATH_EXCEEDS_ROOT_PATH: case ErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH: case ErrorType::PATH_DOES_NOT_EXISTS: return command_result{error::file_invalid_path}; case ErrorType::TARGET_PATH_ALREADY_EXISTS: return command_result{error::file_already_exists}; case ErrorType::SOME_FILES_ARE_LOCKED: return command_result{error::file_already_in_use, rename_response->error().error_message}; } } return command_result{error::ok}; } //clid=2 path=files\/virtualserver_1\/channel_5 name=image.iso // size=673460224 sizedone=450756 clientftfid=2 // serverftfid=6 sender=0 status=1 current_speed=60872.8 average_speed runtime command_result ConnectedClient::handleCommandFTList(ts::Command &cmd) { CMD_RESET_IDLE; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(25); ACTION_REQUIRES_PERMISSION(permission::b_ft_transfer_list, 1, 0); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto list_response = file::server()->file_transfer().list_transfer(); //FIXME: Only for the appropriate servers! if(!list_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!list_response->succeeded()) { using ErrorType = file::transfer::TransferListError; debugMessage(this->getServerId(), "{} Failed to list current transfers: {}", CLIENT_STR_LOG_PREFIX, (int) list_response->error()); switch(list_response->error()) { case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) list_response->error()); return command_result{error::vs_critical, error_detail}; } } } const auto& transfers = list_response->response(); if(transfers.empty()) return command_result{error::database_empty_result}; ts::command_builder notify{this->notify_response_command("notifyftlist")}; size_t bulk_index{0}; for(const auto& transfer : transfers) { auto bulk = notify.bulk(bulk_index++); bulk.put_unchecked("clientftfid", transfer.client_transfer_id); bulk.put_unchecked("serverftfid", transfer.server_transfer_id); bulk.put_unchecked("sender", transfer.direction == file::transfer::Transfer::DIRECTION_DOWNLOAD); bulk.put_unchecked("clid", transfer.client_id); bulk.put_unchecked("cluid", transfer.client_unique_id); bulk.put_unchecked("path", transfer.file_path); bulk.put_unchecked("name", transfer.file_name); bulk.put_unchecked("size", transfer.expected_size); bulk.put_unchecked("sizedone", transfer.size_done); bulk.put_unchecked("status", (int) transfer.status); bulk.put_unchecked("runtime", transfer.runtime.count()); bulk.put_unchecked("current_speed", transfer.current_speed); bulk.put_unchecked("average_speed", transfer.average_speed); } this->sendCommand(notify); return command_result{error::ok}; } //ftstop serverftfid='2' clientftfid='4096' delete='0' command_result ConnectedClient::handleCommandFTStop(ts::Command &cmd) { CMD_RESET_IDLE; CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto virtual_file_server = file::server()->find_virtual_server(this->getServerId()); if(!virtual_file_server) return command_result{error::file_virtual_server_not_registered}; auto stop_response = file::server()->file_transfer().stop_transfer(virtual_file_server, cmd["serverftfid"], false); if(!stop_response->wait_for(kFileAPITimeout)) return command_result{error::file_api_timeout}; if(!stop_response->succeeded()) { using ErrorType = file::transfer::TransferActionError::Type; switch (stop_response->error().error_type) { case ErrorType::UNKNOWN_TRANSFER: /* not known, so not stopped but it has the same result as it would have been stopped */ return command_result{error::ok}; case ErrorType::UNKNOWN: { auto error_detail = std::to_string((int) stop_response->error().error_type); if(!stop_response->error().error_message.empty()) error_detail += "/" + stop_response->error().error_message; return command_result{error::vs_critical, error_detail}; } } } return command_result{error::ok}; }