// // Created by wolverindev on 26.01.20. // #include #include #include #include #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 "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include "../../manager/ActionLogger.h" #include "helpers.h" #include #include #include #include #include #include using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; constexpr static auto kFileAPITimeout = std::chrono::milliseconds{500}; constexpr static auto kMaxClientTransfers = 10; //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}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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("empty", file.empty); bulk.put_unchecked("datetime", std::chrono::duration_cast(file.modified_at.time_since_epoch()).count()); if(bulk_index >= 16 && this->getType() != ClientType::CLIENT_QUERY) { 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(); std::shared_lock channel_tree_lock{this->server->channel_tree_mutex}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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}; channel_tree_lock.unlock(); 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}; } } serverInstance->action_logger()->file_logger.log_file_directory_create(this->getServerId(), this->ref(), channel->channelId(), cmd["dirname"].string()); 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); std::vector> file_log_info{}; auto file_path = cmd["path"].string(); std::shared_ptr> delete_response{}; if (cmd[0].has("cid") && cmd["cid"] != 0) { std::shared_lock channel_tree_lock{this->server->channel_tree_mutex}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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()); file_log_info.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { delete_files.push_back(file_path + "/" + cmd[index]["name"].string()); file_log_info.emplace_back(channel->channelId(), file_path + "/" + 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()); file_log_info.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); file_log_info.emplace_back(0, 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()); file_log_info.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); file_log_info.emplace_back(0, "/avatar_" + avId); } else { this->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(this->ref(), deque{property::CLIENT_FLAG_AVATAR}); delete_files.push_back("/avatar_" + this->getAvatarId()); file_log_info.emplace_back(0, "/avatar_" + this->getAvatarId()); } } 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(size_t index{0}; index < file_status.delete_results.size(); index++) { const auto& file = file_status.delete_results[index]; const auto& log_file_info = file_log_info[index]; while(response.response(bulk_index).error_code() != error::ok) bulk_index++; using Status = file::filesystem::FileDeleteResponse::StatusType; switch (file.status) { case Status::SUCCESS: serverInstance->action_logger()->file_logger.log_file_delete(this->getServerId(), this->ref(), std::get<0>(log_file_info), std::get<1>(log_file_info)); /* 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(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& 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) { std::shared_ptr currentChannel{}; error::type error_error{error::ok}; permission::PermissionType permission_error{permission::ok}; std::vector> info_files{}; info_files.reserve(cmd.bulkCount()); for(size_t index{0}; index < cmd.bulkCount(); index++) { if(cmd[index].has("cid")) { error_error = error::ok; permission_error = permission::ok; currentChannel = this->server->channelTree->findChannel(cmd[index]["cid"].as()); if(currentChannel) { const auto password = cmd[index]["cpw"].string(); if (!currentChannel->verify_password(std::make_optional(password), this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, currentChannel->channelId()))) { if(password.empty()) error_error = error::channel_invalid_password; else permission_error = permission::b_ft_ignore_password; } else if(!currentChannel->permission_granted(permission::i_ft_needed_file_browse_power, this->calculate_permission(permission::i_ft_file_browse_power, currentChannel->channelId()), true)) { permission_error = permission::i_ft_file_browse_power; } } else { error_error = error::channel_invalid_id; } } if(error_error != error::ok) { response.set_result(index, ts::command_result{error_error}); continue; } if(permission_error != permission::ok) { response.set_result(index, ts::command_result{permission_error}); continue; } info_files.emplace_back(currentChannel->channelId(), cmd[index]["name"].string()); } info_response = file_system.query_channel_info(virtual_file_server, info_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 { delete_files.push_back("/avatar_" + this->getAvatarId()); } } 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 as_list = cmd.hasParm("as-list"); 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++; if(notify_index == 0) { notify_file_info.reset(); notify_file_info.put_unchecked(0, "return_code", cmd["return_code"].string()); } 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("empty", file.info.empty); bulk.put_unchecked("datetime", std::chrono::duration_cast(file.info.modified_at.time_since_epoch()).count()); if(notify_index > 20 && as_list) { this->sendCommand(notify_file_info); notify_index = 0; } } if(notify_index > 0) { this->sendCommand(notify_file_info); } if(as_list && this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) { ts::command_builder notify{this->notify_response_command("notifyfileinfofinished")}; notify.put_unchecked(0, "return_code", cmd["return_code"].string()); this->sendCommand(notify); } 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["path"].string() + "/" + 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_unchecked(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as_unchecked(); 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_unchecked(); 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}; } ChannelId log_channel_id{0}; if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel std::shared_lock channel_tree_lock{this->server->channel_tree_mutex}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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); log_channel_id = channel->channelId(); } else { if (cmd["path"].string().empty() && cmd["name"].string().starts_with("/icon_")) { 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", transfer->transfer_key); result.put_unchecked(0, "seekpos", transfer->file_offset); result.put_unchecked(0, "proto", "1"); this->sendCommand(result); serverInstance->action_logger()->file_logger.log_file_upload(this->getServerId(), this->ref(), log_channel_id, info.file_path); 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_unchecked(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as_unchecked(); 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_unchecked(); 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_client_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["path"].string() + "/" + cmd["name"].string(); info.client_unique_id = this->getUid(); info.client_id = this->getClientId(); info.max_concurrent_transfers = kMaxClientTransfers; ChannelId log_channel_id{0}; if(cmd[0].has("cid") && cmd["cid"] != 0) { //Channel std::shared_lock channel_tree_lock{this->server->channel_tree_mutex}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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); log_channel_id = channel->channelId(); } else { if (cmd["path"].as().empty() && cmd["name"].string().starts_with("/icon_")) { 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().starts_with("/avatar")) { 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", transfer->transfer_key); 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); serverInstance->action_logger()->file_logger.log_file_download(this->getServerId(), this->ref(), log_channel_id, info.file_path); 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; std::shared_lock channel_tree_lock{this->server->channel_tree_mutex}; auto channel = this->server->channelTree->findChannel(channel_id); if (!channel) return command_result{error::channel_invalid_id}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_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}; auto channel_password = cmd["cpw"].optional_string(); if (!channel->verify_password(channel_password, this->getType() != ClientType::CLIENT_QUERY) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) { return channel_password.has_value() ? command_result{error::channel_invalid_password} : command_result{permission::b_ft_ignore_password}; } ACTION_REQUIRES_CHANNEL_PERMISSION(targetChannel, permission::i_ft_needed_file_rename_power, permission::i_ft_file_rename_power, true); } channel_tree_lock.unlock(); 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}; } } serverInstance->action_logger()->file_logger.log_file_rename(this->getServerId(), this->ref(), channel_id, cmd["oldname"].string(), target_channel_id, cmd["newname"].string()); 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}; }