Teaspeak-Server/server/src/client/command_handler/file.cpp

1093 lines
51 KiB
C++

//
// Created by wolverindev on 26.01.20.
//
#include <memory>
#include <bitset>
#include <algorithm>
#include <openssl/sha.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 "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include "helpers.h"
#include <files/FileServer.h>
#include <misc/sassert.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/rnd.h>
#include <log/LogUtils.h>
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<directory_query_response_t> query_result{};
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
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<ChannelId>(), 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<std::chrono::seconds>(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<ChannelId>());
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<std::tuple<uint64_t, std::string>> file_log_info{};
auto file_path = cmd["path"].string();
std::shared_ptr<file::ExecuteResponse<file::filesystem::FileDeleteError, file::filesystem::FileDeleteResponse>> 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<ChannelId>());
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<std::string> 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<std::string> 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<std::string> 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::ClientProperties>{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::ClientProperties>{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<std::string>());
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<file::ExecuteResponse<file::filesystem::FileInfoError, file::filesystem::FileInfoResponse>> info_response{};
if (cmd[0].has("cid") && cmd["cid"] != 0) {
std::shared_ptr<BasicChannel> currentChannel{};
error::type error_error{error::ok};
permission::PermissionType permission_error{permission::ok};
std::vector<std::tuple<ChannelId, std::string>> 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<ChannelId>());
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<std::string> 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<std::string> 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<std::string>());
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<std::chrono::seconds>(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<file::ExecuteResponse<file::transfer::TransferInitError, std::shared_ptr<file::transfer::Transfer>>> 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<size_t>();
info.override_exiting = cmd["overwrite"].as<bool>();
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<bool>() && info.override_exiting)
return ts::command_result{error::file_overwrite_excludes_resume};
{
auto server_quota = this->server->properties()[property::VIRTUALSERVER_UPLOAD_QUOTA].as_unchecked<ssize_t>();
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as_unchecked<size_t>();
server_used_quota += cmd["size"].as<uint64_t>();
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<size_t>();
client_used_quota += cmd["size"].as<uint64_t>();
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<ChannelId>());
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<size_t>()))
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<std::string>().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<size_t>()))
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<file::ExecuteResponse<file::transfer::TransferInitError, std::shared_ptr<file::transfer::Transfer>>> transfer_response{};
{
auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as_unchecked<ssize_t>();
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as_unchecked<size_t>();
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<size_t>();
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<size_t>();
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<ChannelId>());
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<std::string>().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<std::string>().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<ChannelId>();
auto target_channel_id = cmd[0].has("tcid") ? cmd["tcid"].as<ChannelId>() : 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};
}