// // Created by wolverindev on 26.01.20. // #include #include #include #include #include #include #include "../../build.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../../server/file/FileServer.h" #include "../../server/VoiceServer.h" #include "../voice/VoiceClient.h" #include "PermissionManager.h" #include "../../InstanceHandler.h" #include "../../server/QueryServer.h" #include "../file/FileClient.h" #include "../music/MusicClient.h" #include "../query/QueryClient.h" #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include #include #include #include "helpers.h" #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::experimental::filesystem; using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; #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=\/ inline void cmd_filelist_append_files(ServerId sid, Command &command, vector> files) { int index = 0; logTrace(sid, "Sending file list for path {}", command["path"].string()); for (const auto& fileEntry : files) { logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory"); command[index]["name"] = fileEntry->name; command[index]["datetime"] = std::chrono::duration_cast(fileEntry->lastChanged.time_since_epoch()).count(); command[index]["type"] = fileEntry->type; if (fileEntry->type == file::FileType::FILE) command[index]["size"] = static_pointer_cast(fileEntry)->fileSize; else command[index]["size"] = 0; index++; } } #define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"} command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_REQ_FSERVER; std::string code = cmd["return_code"].size() > 0 ? cmd["return_code"].string() : ""; Command fileList(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfilelist" : ""); Command fileListFinished("notifyfilelistfinished"); fileList["path"] = cmd["path"].as(); if(!code.empty()) fileList["return_code"] = code; fileListFinished["path"] = cmd["path"].as(); fileList["cid"] = cmd["cid"].as(); fileListFinished["cid"] = cmd["cid"].as(); 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_browse_power, permission::i_ft_file_browse_power, true); cmd_filelist_append_files( this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as())) ); } else { if (cmd["path"].as() == "/icons" || cmd["path"].as() == "/icons/") { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->iconDirectory(this->server))); } else if (cmd["path"].as() == "/") { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->avatarDirectory(this->server))); } else { logMessage(this->getServerId(), "{} Requested file list for unknown path/name: path: {} name: {}", cmd["path"].string(), cmd["name"].string()); return command_result{error::not_implemented}; } } if (fileList[0].has("name")) { if(dynamic_cast(this)) { dynamic_cast(this)->sendCommand0(fileList.build(), false, true); /* We need to process this directly else the order could get shuffled up! */ this->sendCommand(fileListFinished); } else { this->sendCommand(fileList); if(this->getType() != CLIENT_QUERY) this->sendCommand(fileListFinished); } return command_result{error::ok}; } else { return command_result{error::database_empty_result}; } } //ftcreatedir cid=4 cpw dirname=\/TestDir return_code=1:17 command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); CMD_REQ_FSERVER; 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_directory_create_power, permission::i_ft_directory_create_power, true); auto dir = serverInstance->getFileServer()->createDirectory(cmd["dirname"], serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as())); if (!dir) return command_result{error::file_invalid_path, "could not create dir"}; return command_result{error::ok}; } command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); CMD_REQ_FSERVER; std::vector> files; 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_delete_power, permission::i_ft_file_delete_power, true); for (int index = 0; index < cmd.bulkCount(); index++) files.push_back(serverInstance->getFileServer()->findFile(cmd[index]["name"].as(), serverInstance->getFileServer()->resolveDirectory(this->server, channel))); } else { if (cmd["name"].string().find("/icon_") == 0 && cmd["path"].string().empty()) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->iconDirectory(this->server))); } else if (cmd["name"].string().find("/avatar_") == 0 && cmd["path"].string().empty()) { if (cmd["name"].string() != "/avatar_") { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_avatar_delete_other, 1); auto uid = cmd["name"].string().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}); } cmd["name"] = "/avatar_" + avId; } else { cmd["name"] = "/avatar_" + this->getAvatarId(); this->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); } files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->avatarDirectory(this->server))); } else { logError(this->getServerId(), "Unknown requested file to delete: {}", cmd["path"].as()); return command_result{error::not_implemented}; } } for (const auto &file : files) { if (!file) continue; if (!serverInstance->getFileServer()->deleteFile(file)) { logCritical(this->getServerId(), "Could not delete file {}/{}", file->path, file->name); } } return command_result{error::ok}; } command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) { CMD_REQ_SERVER; CMD_REQ_FSERVER; std::shared_ptr directory = nullptr; 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); directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); } else { if (cmd["path"].as().empty() && cmd["name"].as().find("/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}; directory = serverInstance->getFileServer()->iconDirectory(this->server); } 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}; directory = serverInstance->getFileServer()->avatarDirectory(this->server); cmd["name"] = "/avatar_" + this->getAvatarId(); } else { cerr << "Unknown requested directory: " << cmd["path"].as() << endl; return command_result{error::not_implemented}; } } if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen cerr << "Invalid upload file path!" << endl; return command_result{error::file_invalid_path, "could not resolve directory"}; } { 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(); for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) server_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) server_used_quota += trans->remaining_bytes(); 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(); for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) client_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) client_used_quota += trans->remaining_bytes(); 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["overwrite"].as() && cmd["resume"].as()) return command_result{error::file_overwrite_excludes_resume}; if (serverInstance->getFileServer()->findFile(cmd["name"].as(), directory) && !(cmd["overwrite"].as() || cmd["resume"].as())) return command_result{error::file_already_exists, "file already exists"}; //Request: clientftfid=1 serverftfid=6 ftkey=itRNdsIOvcBiBg\/Xj4Ge51ZSrsShHuid port=30033 seekpos=0 //Reqpose: notifystartupload clientftfid=4079 serverftfid=1 ftkey=aX9CFQbfaddHpOYxhQiSLu\/BumfVtPyP port=30033 seekpos=0 proto=1 string error = "success"; auto key = serverInstance->getFileServer()->generateUploadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].string(), cmd["size"].as(), 0, _this.lock()); //TODO implement resume! if (!key) return command_result{error::vs_critical}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartupload" : ""); result["clientftfid"] = cmd["clientftfid"].as(); result["ftkey"] = key->key; auto bindings = serverInstance->getFileServer()->list_bindings(); if(!bindings.empty()) { result["port"] = net::port(bindings[0]->address); string ip = ""; for(auto& entry : bindings) { if(net::is_anybind(entry->address)) { ip = ""; break; } ip += net::to_string(entry->address, false) + ","; } if(!ip.empty()) result["ip"] = ip; } else { return command_result{error::server_is_not_running, "file server is not bound to any address"}; } result["seekpos"] = 0; result["proto"] = 1; result["serverftfid"] = key->key_id; //TODO generate! this->sendCommand(result); return command_result{error::ok}; } command_result ConnectedClient::handleCommandFTInitDownload(Command &cmd) { CMD_REQ_SERVER; CMD_REQ_FSERVER; std::shared_ptr directory = nullptr; if(!cmd[0].has("path")) cmd["path"] = ""; if (cmd[0].has("cid") && cmd["cid"] != (ChannelId) 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_download_power, permission::i_ft_file_download_power, true); directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); } else { if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { directory = serverInstance->getFileServer()->iconDirectory(this->server); } else if (cmd["path"].as().empty() && cmd["name"].as().find("/avatar_") == 0) { //TODO fix avatar download not working directory = serverInstance->getFileServer()->avatarDirectory(this->server); } else { cerr << "Unknown requested directory: " << cmd["path"].as() << endl; return command_result{error::not_implemented}; } } if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen cerr << "Invalid download file path!" << endl; return command_result{error::file_invalid_path, "could not resolve directory"}; } if (!serverInstance->getFileServer()->findFile(cmd["name"].as(), directory)) return command_result{error::file_not_found, "file not exists"}; string error = "success"; auto key = serverInstance->getFileServer()->generateDownloadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].as(), 0, _this.lock()); //TODO implement resume! if (!key) return command_result{error::vs_critical, "cant generate key (" + error + ")"}; { auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as(); server_used_quota += key->size; for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) server_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) server_used_quota += trans->remaining_bytes(); 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_download_per_client, 0); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); client_used_quota += key->size; for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) client_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) client_used_quota += trans->remaining_bytes(); 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}; } Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartdownload" : ""); result["clientftfid"] = cmd["clientftfid"].as(); result["proto"] = 1; result["serverftfid"] = key->key_id; result["ftkey"] = key->key; auto bindings = serverInstance->getFileServer()->list_bindings(); if(!bindings.empty()) { result["port"] = net::port(bindings[0]->address); string ip = ""; for(auto& entry : bindings) { if(net::is_anybind(entry->address)) { ip = ""; break; } ip += net::to_string(entry->address, false) + ","; } if(!ip.empty()) result["ip"] = ip; } else { return command_result{error::server_is_not_running, "file server is not bound to any address"}; } result["size"] = key->size; this->sendCommand(result); return command_result{error::ok}; } /* * 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) { CMD_REQ_SERVER; CMD_REQ_FSERVER; CMD_RESET_IDLE; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfileinfo" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto& request = cmd[index]; std::shared_ptr file; if (request.has("cid") && request["cid"].as() != 0) { //Channel auto channel = this->server->channelTree->findChannel(request["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_browse_power, permission::i_ft_file_browse_power, true); file = serverInstance->getFileServer()->findFile(request["name"],serverInstance->getFileServer()->resolveDirectory(this->server, channel, request["path"])); } else { std::shared_ptr directory; if (!request.has("path") || request["path"].as() == "/") { directory = serverInstance->getFileServer()->avatarDirectory(this->server); } else if (request["path"].as() == "/icons" || request["path"].as() == "/icons/") { directory = serverInstance->getFileServer()->iconDirectory(this->server); } else { cerr << "Unknown requested directory: '" << request["path"].as() << "'" << endl; return command_result{error::not_implemented}; } if(!directory) continue; file = serverInstance->getFileServer()->findFile(cmd["name"].as(), directory); } if(!file) continue; result[result_index]["cid"] = request["cid"].as(); result[result_index]["name"] = request["name"]; result[result_index]["path"] = request["path"]; result[result_index]["type"] = file->type; result[result_index]["datetime"] = duration_cast(file->lastChanged.time_since_epoch()).count(); if (file->type == file::FileType::FILE) result[result_index]["size"] = static_pointer_cast(file)->fileSize; else result[result_index]["size"] = 0; result_index++; } if(result_index == 0) return command_result{error::database_empty_result}; this->sendCommand(result); return command_result{error::ok}; }