From 68cfab1ac940cd50b148f510615bb030f0869e92 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 28 Jun 2020 14:01:14 +0200 Subject: [PATCH] Added the action logging system --- server/CMakeLists.txt | 3 + server/src/InstanceHandler.cpp | 8 + server/src/InstanceHandler.h | 7 +- server/src/TS3ServerClientManager.cpp | 10 +- server/src/TS3ServerHeartbeat.cpp | 2 +- server/src/VirtualServer.h | 3 +- server/src/client/ConnectedClient.h | 6 +- server/src/client/SpeakingClient.cpp | 5 + .../src/client/command_handler/bulk_parsers.h | 65 +- server/src/client/command_handler/channel.cpp | 276 +++++- server/src/client/command_handler/client.cpp | 47 +- server/src/client/command_handler/file.cpp | 40 +- server/src/client/command_handler/misc.cpp | 175 +++- server/src/client/command_handler/music.cpp | 44 +- server/src/client/command_handler/server.cpp | 154 ++- .../src/client/query/QueryClientCommands.cpp | 25 +- server/src/client/voice/VoiceClient.cpp | 7 + server/src/client/web/WebClient.cpp | 5 + server/src/manager/ActionLogger.cpp | 449 +++++++++ server/src/manager/ActionLogger.h | 843 ++++++++++++++++ server/src/manager/ActionLoggerImpl.cpp | 898 ++++++++++++++++++ shared | 2 +- 22 files changed, 2988 insertions(+), 86 deletions(-) create mode 100644 server/src/manager/ActionLogger.cpp create mode 100644 server/src/manager/ActionLogger.h create mode 100644 server/src/manager/ActionLoggerImpl.cpp diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f506abb..00270e9 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -141,6 +141,9 @@ set(SERVER_SOURCE_FILES src/snapshots/groups.cpp src/snapshots/deploy.cpp + src/manager/ActionLogger.cpp + src/manager/ActionLoggerImpl.cpp + src/manager/ConversationManager.cpp src/client/SpeakingClientHandshake.cpp src/client/command_handler/music.cpp src/client/command_handler/file.cpp) diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index bf9b8c2..0407702 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -26,6 +26,7 @@ #endif #include #include +#include "./manager/ActionLogger.h" #undef _POSIX_SOURCE @@ -49,6 +50,13 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) { } this->dbHelper = new DatabaseHelper(this->getSql()); + this->action_logger_ = std::make_unique(); + if(!this->action_logger_->initialize(error_message)) { + logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message); + logCritical(LOG_INSTANCE, "Action log has been disabled."); + this->action_logger_->finalize(); + } + this->_properties = new Properties(); this->_properties->register_property_type(); this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort; diff --git a/server/src/InstanceHandler.h b/server/src/InstanceHandler.h index dbdc2b6..b88ae90 100644 --- a/server/src/InstanceHandler.h +++ b/server/src/InstanceHandler.h @@ -27,6 +27,10 @@ namespace ts { class FileServerHandler; } + namespace log { + class ActionLogger; + } + class InstanceHandler { public: explicit InstanceHandler(SqlDataManager*); @@ -51,6 +55,7 @@ namespace ts { BanManager* banManager(){ return this->banMgr; } ssl::SSLManager* sslManager(){ return this->sslMgr; } sql::SqlManager* getSql(){ return sql->sql(); } + log::ActionLogger* action_logger() { return &*this->action_logger_; } file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; } std::chrono::time_point getStartTimestamp(){ return startTimestamp; } @@ -120,7 +125,7 @@ namespace ts { BanManager* banMgr = nullptr; ssl::SSLManager* sslMgr = nullptr; file::FileServerHandler* file_server_handler_{nullptr}; - + std::unique_ptr action_logger_{nullptr}; ts::Properties* _properties = nullptr; diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 9a60e19..364fc0b 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "InstanceHandler.h" using namespace std; @@ -323,7 +324,7 @@ void VirtualServer::notify_client_kick( * * Note: channel cant be a ref because the channel itself gets deleted! */ -void VirtualServer::delete_channel(shared_ptr channel, const shared_ptr &invoker, const std::string& kick_message, unique_lock &tree_lock) { +void VirtualServer::delete_channel(shared_ptr channel, const shared_ptr &invoker, const std::string& kick_message, unique_lock &tree_lock, bool temp_delete) { if(!tree_lock.owns_lock()) tree_lock.lock(); if(channel->deleted) @@ -358,7 +359,12 @@ void VirtualServer::delete_channel(shared_ptr channel, const tree_lock.lock(); /* no clients left within that tree */ command_locks.clear(); - auto channel_ids = this->channelTree->delete_channel_root(channel); + auto deleted_channels = this->channelTree->delete_channel_root(channel); + log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION}; + for(const auto& deleted_channel : deleted_channels) { + serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED); + } + this->forEachClient([&](const shared_ptr& client) { unique_lock client_channel_lock(client->channel_lock); client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker); diff --git a/server/src/TS3ServerHeartbeat.cpp b/server/src/TS3ServerHeartbeat.cpp index 193fd6f..6cba48e 100644 --- a/server/src/TS3ServerHeartbeat.cpp +++ b/server/src/TS3ServerHeartbeat.cpp @@ -187,7 +187,7 @@ void VirtualServer::executeServerTick() { if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay - this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock); + this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true); if(channel_lock.owns_lock()) channel_lock.unlock(); } diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index c112185..01827f1 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -270,7 +270,8 @@ namespace ts { std::shared_ptr /* target channel */, const std::shared_ptr& /* invoker */, const std::string& /* kick message */, - std::unique_lock& /* tree lock */ + std::unique_lock& /* tree lock */, + bool temporary_auto_delete ); void send_text_message(const std::shared_ptr& /* channel */, const std::shared_ptr& /* sender */, const std::string& /* message */); diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index fb1def3..f62511c 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -416,13 +416,13 @@ namespace ts { command_result handleCommandChannelDelPerm(Command&); //Server group manager management - command_result handleCommandServerGroupCopy(Command&); command_result handleCommandServerGroupAdd(Command&); + command_result handleCommandServerGroupCopy(Command&); command_result handleCommandServerGroupRename(Command&); command_result handleCommandServerGroupDel(Command&); command_result handleCommandServerGroupClientList(Command&); - command_result handleCommandServerGroupDelClient(Command&); command_result handleCommandServerGroupAddClient(Command&); + command_result handleCommandServerGroupDelClient(Command&); command_result handleCommandServerGroupPermList(Command&); command_result handleCommandServerGroupAddPerm(Command&); command_result handleCommandServerGroupDelPerm(Command&); @@ -598,6 +598,8 @@ namespace ts { command_result handleCommandLogView(Command&); //CMD_TODO handleCommandLogAdd + command_result handleCommandLogQuery(Command&); + command_result handleCommandLogAdd(Command&); command_result handleCommandListFeatureSupport(Command &cmd); diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 815c952..764901d 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -11,6 +11,7 @@ #include "src/InstanceHandler.h" #include "StringVariable.h" #include "misc/timer.h" +#include "../manager/ActionLogger.h" using namespace std::chrono; using namespace ts; @@ -800,11 +801,15 @@ void SpeakingClient::processJoin() { } } debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings)); + + serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name()); } void SpeakingClient::processLeave() { auto ownLock = _this.lock(); auto server = this->getServer(); + + auto channel = this->currentChannel; if(server){ logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort())); { diff --git a/server/src/client/command_handler/bulk_parsers.h b/server/src/client/command_handler/bulk_parsers.h index b25c8ec..2b3c818 100644 --- a/server/src/client/command_handler/bulk_parsers.h +++ b/server/src/client/command_handler/bulk_parsers.h @@ -6,10 +6,12 @@ #include #include #include "./helpers.h" +#include "../../manager/ActionLogger.h" namespace ts::command::bulk_parser { template class PermissionBulkParser { + friend class PermissionBulkParser; public: explicit PermissionBulkParser(ts::ParameterBulk& bulk) { if(bulk.has("permid")) { @@ -45,7 +47,7 @@ namespace ts::command::bulk_parser { } } PermissionBulkParser(const PermissionBulkParser&) = delete; - PermissionBulkParser(PermissionBulkParser&&) = default; + PermissionBulkParser(PermissionBulkParser&&) noexcept = default; ~PermissionBulkParser() { this->error_.release_data(); @@ -78,9 +80,9 @@ namespace ts::command::bulk_parser { inline void apply_to(const std::shared_ptr& manager, permission::v2::PermissionUpdateType mode) const { if(this->is_grant_permission()) { - manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode); + this->old_value = manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode); } else { - manager->set_permission( + this->old_value = manager->set_permission( this->permission_type(), { this->value(), 0 }, mode, @@ -91,11 +93,61 @@ namespace ts::command::bulk_parser { } } + inline void log_update( + server::log::PermissionActionLogger& logger, + ServerId sid, + const std::shared_ptr& issuer, + server::log::PermissionTarget target, + permission::v2::PermissionUpdateType mode, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name + ) const { + if(this->is_grant_permission()) { + switch (mode) { + case permission::v2::delete_value: + if(!this->old_value.flags.grant_set) + return; + + logger.log_permission_remove_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant); + break; + + case permission::v2::set_value: + if(this->old_value.flags.grant_set) { + logger.log_permission_edit_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant, this->value_); + } else { + logger.log_permission_add_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->value_); + } + break; + case permission::v2::do_nothing: + break; + } + } else { + switch (mode) { + case permission::v2::delete_value: + if(!this->old_value.flags.value_set) + return; + + logger.log_permission_remove_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip); + break; + + case permission::v2::set_value: + if(this->old_value.flags.value_set) { + logger.log_permission_edit_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip, this->value_, this->flag_negated_, this->flag_skip_); + } else { + logger.log_permission_add_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip); + } + break; + case permission::v2::do_nothing: + break; + } + } + } + inline void apply_to_channel(const std::shared_ptr& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const { if(this->is_grant_permission()) { - manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode); + this->old_value = manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode); } else { - manager->set_channel_permission( + this->old_value = manager->set_channel_permission( this->permission_type(), channel_id, { this->value(), true }, @@ -122,6 +174,7 @@ namespace ts::command::bulk_parser { permission::PermissionValue value_{0}; ts::command_result error_{error::ok}; + mutable permission::v2::PermissionContainer old_value{}; #ifndef NDEBUG bool error_released_{false}; #endif @@ -131,7 +184,7 @@ namespace ts::command::bulk_parser { class PermissionBulksParser { public: PermissionBulksParser(const PermissionBulksParser&) = delete; - PermissionBulksParser(PermissionBulksParser&&) = default; + PermissionBulksParser(PermissionBulksParser&&) noexcept = default; template struct FilteredPermissionIterator : public base_iterator { diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index 2177688..d1208ce 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -15,6 +15,7 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" #include #include "helpers.h" @@ -138,11 +139,30 @@ command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); - if(cmd["type"].as() == GroupType::GROUP_TYPE_NORMAL && !this->server) return command_result{error::parameter_invalid, "You cant create normal channel groups on the template server"}; - if(cmd["name"].string().empty()) return command_result{error::parameter_invalid, "invalid group name"}; + + log::GroupType log_group_type; + if(cmd["type"].as() == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; + } else if(cmd["type"].as() == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + } else { + if(!this->server) + return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"}; + log_group_type = log::GroupType::NORMAL; + } + + if(cmd["name"].string().empty()) + return command_result{error::parameter_invalid, "invalid group name"}; + for(const auto& gr : group_manager->availableServerGroups(true)) - if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "Group already exists"}; + if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) + return command_result{error::parameter_invalid, "Group already exists"}; + auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as(), cmd["name"].string()); + serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, group->groupId(), group->name(), 0, ""); + if (group) { group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing); if(this->server) @@ -212,6 +232,28 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { if(!group_manager->copyGroupPermissions(source_group, target_group)) return command_result{error::vs_critical, "failed to copy group permissions"}; + log::GroupType log_group_type; + switch (target_group->type()) { + case GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + case GroupType::GROUP_TYPE_NORMAL: + log_group_type = log::GroupType::NORMAL; + break; + + default: + return command_result{error::parameter_invalid, "type"}; + } + + serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name()); + + global_update = !this->server || !group_manager->isLocalGroup(target_group); } else { //Copy a new group @@ -223,6 +265,24 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { return command_result{result}; } + log::GroupType log_group_type; + switch (target_type) { + case GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + case GroupType::GROUP_TYPE_NORMAL: + log_group_type = log::GroupType::NORMAL; + break; + + default: + return command_result{error::parameter_invalid, "type"}; + } + if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; @@ -232,6 +292,8 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { auto target_group_id = group_manager->copyGroup(source_group, target_type, cmd["name"], target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId()); if(target_group_id == 0) return command_result{error::vs_critical, "failed to copy group"}; + serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, log_group_type, target_group_id, cmd["name"], source_group->groupId(), source_group->name()); if(this->getType() == ClientType::CLIENT_QUERY) { Command notify(""); @@ -256,10 +318,26 @@ command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto channel_group = group_manager->findGroup(cmd["cgid"].as()); - if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) + return command_result{error::parameter_invalid, "invalid channel group id"}; ACTION_REQUIRES_GROUP_PERMISSION(channel_group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + auto type = channel_group->type(); + log::GroupType log_group_type; + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + } else { + log_group_type = log::GroupType::NORMAL; + } + + auto old_name = channel_group->name(); group_manager->renameGroup(channel_group, cmd["name"].string()); + serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::CHANNEL, log_group_type, channel_group->groupId(), channel_group->name(), old_name); + if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); @@ -285,21 +363,37 @@ command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { } if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->groupId()) return command_result{error::parameter_invalid, "Could not delete instance default channel group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->groupId()) return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"}; + auto type = channel_group->type(); + log::GroupType log_group_type; + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + } else { + log_group_type = log::GroupType::NORMAL; + } + if (!cmd["force"].as()) if (!group_manager->listGroupMembers(channel_group, false).empty()) return command_result{error::database_empty_result, "group not empty!"}; - if (group_manager->deleteGroup(channel_group) && this->server) { - this->server->forEachClient([&](shared_ptr cl) { - if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) { - if(cl->update_cached_permissions()) /* update cached calculated permissions */ - cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - } - cl->notifyChannelGroupList(); - }); + if (group_manager->deleteGroup(channel_group)) { + serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, channel_group->groupId(), channel_group->name()); + if(this->server) { + this->server->forEachClient([&](shared_ptr cl) { + if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + cl->notifyChannelGroupList(); + }); + } } return command_result{error::ok}; @@ -414,6 +508,15 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { bool updateList{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CHANNEL_GROUP, + permission::v2::PermissionUpdateType::set_value, + 0, "", + channelGroup->groupId(), channelGroup->name() + ); + updateList |= ppermission.is_group_property(); } @@ -453,6 +556,14 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { bool updateList{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CHANNEL_GROUP, + permission::v2::PermissionUpdateType::delete_value, + 0, "", + channelGroup->groupId(), channelGroup->name() + ); updateList |= ppermission.is_group_property(); } @@ -685,24 +796,50 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { ); } - for (auto &prop : cmd[0].keys()) { - if (prop == "channel_flag_default") continue; - if (prop == "channel_order") continue; - if (prop == "channel_name") continue; - if (prop == "cpid") continue; - if (prop == "cid") continue; + /* log channel create */ + { + log::ChannelType log_channel_type; + switch (created_channel->channelType()) { + case ChannelType::permanent: + log_channel_type = log::ChannelType::PERMANENT; + break; - const auto &property = property::find(prop); + case ChannelType::semipermanent: + log_channel_type = log::ChannelType::SEMI_PERMANENT; + break; + + case ChannelType::temporary: + log_channel_type = log::ChannelType::TEMPORARY; + break; + } + + serverInstance->action_logger()->channel_logger.log_channel_create(this->getServerId(), this->ref(), created_channel->channelId(), log_channel_type); + } + + for (auto &property_name : cmd[0].keys()) { + if (property_name == "channel_flag_default") continue; + if (property_name == "channel_order") continue; + if (property_name == "channel_name") continue; + if (property_name == "cpid") continue; + if (property_name == "cid") continue; + + const auto &property = property::find(property_name); if(property == property::CHANNEL_UNDEFINED) { - logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop); + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + property_name); continue; } - if(!property.validate_input(cmd[prop].as())) { - logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as() + "', Property: '" + std::string{property.name} + "')"); + if(!property.validate_input(cmd[property_name].as())) { + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[property_name].as() + "', Property: '" + std::string{property.name} + "')"); continue; } - created_channel->properties()[property] = cmd[prop].as(); + auto prop = created_channel->properties()[property]; + auto old_value = prop.value(); + auto new_value = cmd[property_name].as(); + if(old_value == new_value) + continue; + prop = new_value; + serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), created_channel->channelId(), property, old_value, new_value); } if(created_channel->parent()) { if(created_channel->parent()->channelType() > created_channel->channelType()) @@ -738,6 +875,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!"); channel_admin_group = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); } + + /* FIXME: Log group assignment */ this->server->groups->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel); if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB)) @@ -776,11 +915,15 @@ command_result ConnectedClient::handleCommandChannelDelete(Command &cmd) { if(!clients.empty()) ACTION_REQUIRES_PERMISSION(permission::b_channel_delete_flag_force, 1, channel->channelId()); - this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock); + this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock, false); } else { - auto channel_ids = channel_tree->deleteChannelRoot(channel); - this->notifyChannelDeleted(channel_ids, this->ref()); + auto deleted_channel_ids = channel_tree->deleteChannelRoot(channel); + for(const auto& channelId : deleted_channel_ids) { + serverInstance->action_logger()->channel_logger.log_channel_delete(0, this->ref(), channelId, channel->channelId() == channelId ? log::ChannelDeleteReason::USER_ACTION : log::ChannelDeleteReason::PARENT_DELETED); + } + this->notifyChannelDeleted(deleted_channel_ids, this->ref()); } + return command_result{error::ok}; } @@ -1108,9 +1251,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { for(const property::PropertyDescription* key : keys) { if(*key == property::CHANNEL_ORDER) { /* TODO: May move that up because if it fails may some other props have already be applied */ + auto old_channel_order = channel->channelOrder(); if (!channel_tree->change_order(channel, cmd[std::string{key->name}])) return command_result{error::channel_invalid_order, "Can't change order id"}; + auto channel_parent = channel->parent() ? channel->parent()->channelId() : 0; + serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(), + channel_parent, channel_parent, + old_channel_order, channel->channelOrder()); + if(this->server) { auto parent = channel->hasParent() ? channel_tree->findLinkedChannel(channel->parent()->channelId()) : nullptr; auto previous = channel_tree->findLinkedChannel(channel->previousChannelId()); @@ -1144,6 +1293,9 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { } }); } + + /* property has already been updated as well the log has also been written */ + continue; } else if(*key == property::CHANNEL_FLAG_DEFAULT) { old_default_channel = channel_tree->getDefaultChannel(); if(old_default_channel == channel) { @@ -1216,7 +1368,14 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as(), 0}, permission::v2::set_value, permission::v2::do_nothing); } - channel->properties()[*key] = cmd[std::string{key->name}].string(); + auto prop = channel->properties()[*key]; + auto old_value = prop.value(); + auto new_value = cmd[std::string{key->name}].string(); + if(old_value == new_value) + continue; + + prop = new_value; + serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), channel->channelId(), *key, old_value, new_value); } if(this->server) { vector key_vector; @@ -1283,9 +1442,16 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { if(channel->parent() == parent && channel->channelOrder() == (order ? order->channelId() : 0)) return command_result{error::ok}; - if (channel->parent() != parent) + auto old_parent_channel_id = channel->parent() ? channel->parent()->channelId() : 0; + auto old_channel_order = channel->channelOrder(); + + bool change_parent{channel->parent() != parent}; + bool change_order{(order ? order->channelId() : 0) != channel->channelOrder()}; + + if (change_parent) ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_parent, 1, channel_id); - if ((order ? order->channelId() : 0) != channel->channelOrder()) + + if (change_order) ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_sortorder, 1, channel_id); { @@ -1331,6 +1497,24 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { } while ((current_channel = dynamic_pointer_cast(current_channel->parent()))); } + /* log all the updates */ + serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(), + old_parent_channel_id, channel->parent() ? channel->parent()->channelId() : 0, + old_channel_order, channel->channelOrder()); + + for(const auto& type_update : channel_type_updates) { + serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(), + property::describe(property::CHANNEL_FLAG_PERMANENT), + "", + type_update->properties()[property::CHANNEL_FLAG_PERMANENT].value() + ); + serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(), + property::describe(property::CHANNEL_FLAG_SEMI_PERMANENT), + "", + type_update->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].value() + ); + } + if(this->server) { auto self_rev = this->ref(); this->server->forEachClient([&](const shared_ptr& client) { @@ -1442,8 +1626,17 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { auto permission_manager = channel->permissions(); auto updateClients = false, update_join_permissions = false, update_channel_properties = false; + auto channelId = channel->channelId(); for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CHANNEL, + permission::v2::PermissionUpdateType::set_value, + channelId, channel->name(), + 0, "" + ); updateClients |= ppermission.is_client_view_property(); update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; @@ -1480,8 +1673,17 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { auto permission_manager = channel->permissions(); auto updateClients = false, update_join_permissions = false, update_channel_properties = false; + auto channelId = channel->channelId(); for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CHANNEL, + permission::v2::PermissionUpdateType::delete_value, + channelId, channel->name(), + 0, "" + ); updateClients |= ppermission.is_client_view_property(); update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; @@ -1586,6 +1788,14 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) bool update_view{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId()); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CLIENT_CHANNEL, + permission::v2::PermissionUpdateType::delete_value, + cldbid, "", + channel->channelId(), channel->name() + ); update_view |= ppermission.is_client_view_property(); } @@ -1646,6 +1856,14 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) bool update_view{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId()); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CLIENT_CHANNEL, + permission::v2::PermissionUpdateType::set_value, + cldbid, "", + channel->channelId(), channel->name() + ); update_view |= ppermission.is_client_view_property(); } diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index 63318f4..be5d15e 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -15,6 +15,7 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" #include #include "helpers.h" @@ -77,6 +78,7 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) { result.emplace_result(error::client_invalid_id); continue; } + if (client->getType() == CLIENT_MUSIC) { result.emplace_result(error::client_invalid_type); continue; @@ -101,11 +103,16 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) { } for(auto& client : clients) { + auto old_channel = client->getChannel(); + if(!old_channel) continue; + if (target_channel) { this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), target_channel); + serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), target_channel->channelId(), target_channel->name(), old_channel->channelId(), old_channel->name()); } else { this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), nullptr); client->close_connection(system_clock::now() + seconds(1)); + serverInstance->action_logger()->client_channel_logger.log_client_kick(this->getServerId(), this->ref(), client->ref(), 0, "", old_channel->channelId(), old_channel->name()); } } @@ -257,6 +264,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { true, server_channel_w_lock ); + + serverInstance->action_logger()->client_channel_logger.log_client_move(this->getServerId(), this->ref(), client->ref(), channel->channelId(), channel->name(), oldChannel->channelId(), oldChannel->name()); + if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr& channel) { return &*channel == &*oldChannel; }) == channels.end()) channels.push_back(oldChannel); } @@ -266,7 +276,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { server_channel_w_lock.lock(); if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as() == 0) if(this->server->getClientsByChannelRoot(oldChannel, false).empty()) - this->server->delete_channel(dynamic_pointer_cast(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock); + this->server->delete_channel(dynamic_pointer_cast(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock, true); if(server_channel_w_lock.owns_lock()) server_channel_w_lock.unlock(); } @@ -701,8 +711,24 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) { client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); } - client->properties()[key.first] = cmd[0][key.second].value(); + + auto property = client->properties()[key.first]; + auto old_value = property.value(); + auto new_value = cmd[0][key.second].value(); + if(old_value == new_value) + continue; + + property = new_value; updates.push_back(key.first); + + serverInstance->action_logger()->client_edit_logger.log_client_edit( + this->getServerId(), + this->ref(), + client, + *key.first, + old_value, + new_value + ); } if(update_talk_rights) client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]); @@ -954,6 +980,15 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { bool update_channels{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CLIENT, + permission::v2::PermissionUpdateType::set_value, + cldbid, "", + 0, "" + ); + update_channels |= ppermission.is_client_view_property(); } @@ -989,6 +1024,14 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { bool update_channels{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::CLIENT, + permission::v2::PermissionUpdateType::delete_value, + cldbid, "", + 0, "" + ); update_channels |= ppermission.is_client_view_property(); } diff --git a/server/src/client/command_handler/file.cpp b/server/src/client/command_handler/file.cpp index 0da9405..23aefd9 100644 --- a/server/src/client/command_handler/file.cpp +++ b/server/src/client/command_handler/file.cpp @@ -19,6 +19,7 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" #include "helpers.h" #include @@ -185,6 +186,7 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { 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_lock}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; @@ -194,6 +196,7 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { 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)) @@ -217,6 +220,8 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { } } + serverInstance->action_logger()->file_logger.log_file_directory_create(this->getServerId(), this->ref(), channel->channelId(), cmd["dirname"].string()); + return command_result{error::ok}; } @@ -234,9 +239,12 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { 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_lock}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; @@ -249,8 +257,11 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { std::vector delete_files{}; delete_files.reserve(cmd.bulkCount()); - for(size_t index{0}; index < cmd.bulkCount(); index++) + 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 { @@ -261,6 +272,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { 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_")) { @@ -269,6 +281,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { } 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); @@ -281,6 +294,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { 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_")) { @@ -311,9 +325,12 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { } 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.lock(), deque{property::CLIENT_FLAG_AVATAR}); + delete_files.push_back("/avatar_" + this->getAvatarId()); + file_log_info.emplace_back(0, "/avatar_" + this->getAvatarId()); } } @@ -341,13 +358,17 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { const auto& file_status = delete_response->response(); size_t bulk_index{0}; - for(const auto& file : file_status.delete_results) { + 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; @@ -491,8 +512,7 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) { auto avId = hex::hex(base64::decode(uid), 'a', 'q'); delete_files.push_back("/avatar_" + avId); } else { - this->properties()[property::CLIENT_FLAG_AVATAR] = ""; - this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); + delete_files.push_back("/avatar_" + this->getAvatarId()); } } @@ -641,8 +661,9 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) { 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_lock}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; @@ -652,6 +673,7 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) { 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); @@ -727,6 +749,7 @@ command_result ConnectedClient::handleCommandFTInitUpload(ts::Command &cmd) { 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}; } @@ -781,7 +804,9 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) { 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_lock}; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id}; @@ -791,6 +816,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) { 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); @@ -859,6 +885,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(ts::Command &cmd) { 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}; } @@ -881,6 +908,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) { 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_lock}; auto channel = this->server->channelTree->findChannel(channel_id); if (!channel) return command_result{error::channel_invalid_id}; @@ -900,6 +928,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) { 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()); @@ -933,6 +962,7 @@ command_result ConnectedClient::handleCommandFTRenameFile(ts::Command &cmd) { } } + 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}; } diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index 7a80a2b..a2bf62a 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -23,6 +23,7 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" #include #include #include @@ -211,6 +212,9 @@ command_result ConnectedClient::handleCommand(Command &cmd) { else if (command == "help") return this->handleCommandHelp(cmd); else if (command == "logview") return this->handleCommandLogView(cmd); + else if (command == "logquery") return this->handleCommandLogQuery(cmd); + else if (command == "logadd") return this->handleCommandLogAdd(cmd); + else if (command == "servergroupautoaddperm") return this->handleCommandServerGroupAutoAddPerm(cmd); else if (command == "servergroupautodelperm") return this->handleCommandServerGroupAutoDelPerm(cmd); @@ -448,23 +452,28 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) } } + std::shared_ptr old_group; { - auto old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false); + old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false); if(old_group) { auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id); - if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) { + if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) { if(target_cldbid != this->getClientDatabaseId()) return command_result{permission::i_channel_group_member_remove_power}; auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id); - if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true)) + if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true)) return command_result{permission::i_channel_group_self_remove_power}; } } } this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel); + + std::shared_ptr connected_client{}; for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) { + connected_client = targetClient; + unique_lock client_channel_lock_w(targetClient->channel_lock); auto updates = this->server->groups->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */ client_channel_lock_w.unlock(); @@ -487,6 +496,21 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) targetClient->updateChannelClientProperties(false, true); } + if(old_group) { + serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, + old_group->group->groupId(), old_group->group->name(), + target_cldbid, connected_client ? connected_client->getDisplayName() : "" + ); + } + if(serverGroup != this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL)) { + serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(this->getServerId(), + this->ref(), log::GroupTarget::CHANNEL, + serverGroup->groupId(), serverGroup->name(), + target_cldbid, connected_client ? connected_client->getDisplayName() : "" + ); + } + return command_result{error::ok}; } @@ -684,8 +708,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) { bool banned = false; if(existing) { if(existing->invokerDbId == this->getClientDatabaseId()) { - if(existing->until == until) return command_result{error::database_duplicate_entry}; - else { + if(existing->until == until) { + return command_result{error::database_duplicate_entry}; + } else { existing->until = until; serverInstance->banManager()->updateBan(existing); banned = true; @@ -694,7 +719,9 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) { serverInstance->banManager()->unban(existing); } } - if(!banned) serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until); + if(!banned) { + serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until); + } for(auto server : (this->server ? std::deque>{this->server} : serverInstance->getVoiceServerManager()->serverInstances())) server->testBanStateChange(_this.lock()); @@ -1934,19 +1961,21 @@ command_result ConnectedClient::handleCommandPermReset(ts::Command& cmd) { command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(50); - auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy"); - string log_path; ServerId target_server = cmd[0].has("instance") && cmd["instance"].as() ? (ServerId) 0 : this->getServerId(); - string server_identifier; - if(target_server > 0) - server_identifier = to_string(target_server); - else server_identifier = "[A-Z]{0,7}"; - if(target_server == 0) ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1); else ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1); + auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy"); +#if 0 + string log_path; + string server_identifier; + if(target_server > 0) + server_identifier = to_string(target_server); + else + server_identifier = "[A-Z]{0,7}"; + for(const auto& sink : logger::logger(target_server)->sinks()) { if(dynamic_pointer_cast(sink)) { log_path = dynamic_pointer_cast(sink)->filename(); @@ -2077,7 +2106,127 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { } } this->sendCommand(result); +#else + constexpr static std::array log_output{ + "The command 'logview' is not supported anymore.", + "In order to lookup the server actions use 'logquery'.", + "", + "If you need to lookup the TeaSpeak - Server logs, please visit the 'logs/' folder,", + "located at your TeaSpeak installation folder. All logs could be found there." + }; + command_builder result{this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""}; + size_t index{0}; + if(lagacy) { + for(const auto& message : log_output) { + std::string line{"2020-06-27 00:00.0000|CRITICAL|Server Instance | |"}; + line += message; + result.put_unchecked(index++, "l", line); + } + } else { + for(const auto& message : log_output) { + std::string line{"[2020-06-27 00:00:00][ERROR] "}; + line += message; + result.put_unchecked(index++, "l", line); + } + } + this->sendCommand(result); +#endif + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandLogQuery(ts::Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(50); + + uint64_t target_server = (cmd[0].has("instance") && cmd["instance"].as()) || cmd.hasParm("instance") ? (ServerId) 0 : this->getServerId(); + if(target_server == 0) { + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1); + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1); + } + + std::chrono::system_clock::time_point timestamp_begin{}, timestamp_end{}; + + if(cmd[0].has("begin")) + timestamp_begin += std::chrono::milliseconds{cmd["begin"].as()}; + + if(cmd[0].has("end")) + timestamp_end += std::chrono::milliseconds{cmd["end"].as()}; + + if(timestamp_begin <= timestamp_end && timestamp_begin.time_since_epoch().count() != 0) + return command_result{error::parameter_constraint_violation, "begin > end"}; + + size_t limit{100}; + if(cmd[0].has("limit")) + limit = std::min((size_t) 2000, cmd["limit"].as()); + + std::vector groups{}; + if(cmd[0].has("groups")) { + auto groups_string = cmd["groups"].string(); + size_t offset{0}, findex; + do { + findex = groups_string.find(',', offset); + + auto group_name = groups_string.substr(offset, findex - offset); + offset = findex + 1; + + + size_t index{0}; + for(; index < (size_t) log::LoggerGroup::MAX; index++) { + auto group = static_cast(index); + if(log::kLoggerGroupName[(int) group] == group_name) { + if(std::find(groups.begin(), groups.end(), group) != groups.end()) + return command_result{error::parameter_invalid, "groups"}; + + groups.push_back(group); + } + } + + if(index == (size_t) log::LoggerGroup::MAX) + return command_result{error::parameter_invalid, "groups"}; + } while(offset != 0); + } + + auto result = serverInstance->action_logger()->query(groups, target_server, timestamp_begin, timestamp_end, limit); + if(result.empty()) + return command_result{error::database_empty_result}; + + command_builder notify{this->notify_response_command("notifylogquery")}; + size_t index{0}; + size_t threshold = this->getType() == ClientType::CLIENT_QUERY ? (size_t) -1 : 4096; /* limit each command to 4096 bytes */ + for(log::LogEntryInfo& entry : result) { + notify.set_bulk(index, std::move(entry.info)); + if(index == 0) + notify.put_unchecked(0, "sid", this->getServerId()); + + if(notify.current_size() > threshold) { + this->sendCommand(notify); + notify.reset(); + index = 0; + } + index++; + } + if(index> 0) + this->sendCommand(notify); + + if(this->getType() != ClientType::CLIENT_QUERY) + this->sendCommand(command_builder{"notifylogqueryfinished"}); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandLogAdd(ts::Command& cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(50); + + uint64_t target_server = cmd[0].has("instance") && cmd["instance"].as() ? (ServerId) 0 : this->getServerId(); + if(target_server == 0) { + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_add, 1); + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_add, 1); + } + + serverInstance->action_logger()->custom_logger.add_log_message(target_server, this->ref(), cmd["msg"]); return command_result{error::ok}; } diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index f2a3efc..3933172 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -561,8 +561,17 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); - for(const auto& ppermission : pparser.iterate_valid_permissions()) + for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::PLAYLIST, + permission::v2::PermissionUpdateType::set_value, + playlist->playlist_id(), "", + 0, "" + ); + } return pparser.build_command_result(); } @@ -582,8 +591,17 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { if(!pparser.validate(this->ref(), 0)) return pparser.build_command_result(); - for(const auto& ppermission : pparser.iterate_valid_permissions()) + for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::PLAYLIST, + permission::v2::PermissionUpdateType::delete_value, + playlist->playlist_id(), "", + 0, "" + ); + } return pparser.build_command_result(); } @@ -705,8 +723,17 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command & if(!pparser.validate(this->ref(), this->getClientDatabaseId())) return pparser.build_command_result(); - for(const auto& ppermission : pparser.iterate_valid_permissions()) + for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::PLAYLIST_CLIENT, + permission::v2::PermissionUpdateType::set_value, + playlist->playlist_id(), "", + client_id, "" + ); + } return pparser.build_command_result(); } @@ -730,8 +757,17 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command & if(!pparser.validate(this->ref(), this->getClientDatabaseId())) return pparser.build_command_result(); - for(const auto& ppermission : pparser.iterate_valid_permissions()) + for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::PLAYLIST_CLIENT, + permission::v2::PermissionUpdateType::delete_value, + playlist->playlist_id(), "", + client_id, "" + ); + } return pparser.build_command_result(); } diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp index 9b9c812..bcff40a 100644 --- a/server/src/client/command_handler/server.cpp +++ b/server/src/client/command_handler/server.cpp @@ -19,6 +19,7 @@ #include "../../weblist/WebListManager.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" +#include "../../manager/ActionLogger.h" #include "helpers.h" #include "./bulk_parsers.h" @@ -67,6 +68,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]); if(!target_server && cmd["sid"].as() != 0) return command_result{error::server_invalid_id}; } + ServerId serverId = target_server ? target_server->serverId : 0; auto cache = make_shared(); map toApplay; @@ -220,10 +222,13 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')"); continue; } - if(target_server) - target_server->properties()[info] = elm.second; - else - (*serverInstance->getDefaultServerProperties())[info] = elm.second; + + auto property = target_server ? target_server->properties()[info] : (*serverInstance->getDefaultServerProperties())[info]; + if(property.value() == elm.second) + continue; + auto old_value = property.value(); + property = elm.second; + serverInstance->action_logger()->server_edit_logger.log_server_edit(serverId, this->ref(), info, old_value, elm.second); keys.push_back(elm.first); group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP; @@ -301,23 +306,33 @@ command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) { if(cmd["name"].string().empty()) return command_result{error::parameter_invalid}; + log::GroupType log_group_type; if(cmd["type"].as() == GroupType::GROUP_TYPE_QUERY) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; } else if(cmd["type"].as() == GroupType::GROUP_TYPE_TEMPLATE) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); - } else if(!this->server) return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"}; + log_group_type = log::GroupType::TEMPLATE; + } else { + if(!this->server) + return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"}; + log_group_type = log::GroupType::NORMAL; + } auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); for(const auto& gr : group_manager->availableServerGroups(true)) if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) return command_result{error::parameter_invalid, "Group already exists"}; auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as(), cmd["name"].string()); - if (group) { - group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing); - if(this->server) - this->server->forEachClient([](shared_ptr cl) { - cl->notifyServerGroupList(); - }); - } else return command_result{error::vs_critical}; + if(!group) + return command_result{error::vs_critical}; + + group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing); + if(this->server) { + this->server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + } + serverInstance->action_logger()->group_logger.log_group_create(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, group->groupId(), group->name(), 0, ""); return command_result{error::ok}; } @@ -379,6 +394,28 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { if(!group_manager->copyGroupPermissions(source_group, target_group)) return command_result{error::vs_critical, "failed to copy group permissions"}; + + log::GroupType log_group_type; + switch (target_group->type()) { + case GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + case GroupType::GROUP_TYPE_NORMAL: + log_group_type = log::GroupType::NORMAL; + break; + + default: + return command_result{error::parameter_invalid, "type"}; + } + + serverInstance->action_logger()->group_logger.log_group_permission_copy(target_group->type() != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::SERVER, log_group_type, target_group->groupId(), target_group->name(), source_group->groupId(), source_group->name()); + global_update = !this->server || !group_manager->isLocalGroup(target_group); } else { //Copy a new group @@ -389,6 +426,23 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { if(result != permission::undefined) return command_result{result}; } + log::GroupType log_group_type; + switch (target_type) { + case GroupType::GROUP_TYPE_QUERY: + log_group_type = log::GroupType::QUERY; + break; + + case GroupType::GROUP_TYPE_TEMPLATE: + log_group_type = log::GroupType::TEMPLATE; + break; + + case GroupType::GROUP_TYPE_NORMAL: + log_group_type = log::GroupType::NORMAL; + break; + + default: + return command_result{error::parameter_invalid, "type"}; + } if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; @@ -400,6 +454,8 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { if(target_group_id == 0) return command_result{error::vs_critical, "failed to copy group"}; + serverInstance->action_logger()->group_logger.log_group_create(target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId(), + this->ref(), log::GroupTarget::SERVER, log_group_type, target_group_id, cmd["name"].string(), source_group->groupId(), source_group->name()); if(this->getType() == ClientType::CLIENT_QUERY) { Command notify(""); notify["sgid"] = target_group_id; @@ -428,13 +484,20 @@ command_result ConnectedClient::handleCommandServerGroupRename(Command &cmd) { ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); auto type = serverGroup->type(); + log::GroupType log_group_type; if(type == GroupType::GROUP_TYPE_QUERY) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + } else { + log_group_type = log::GroupType::NORMAL; } + auto old_name = serverGroup->name(); group_manager->renameGroup(serverGroup, cmd["name"].string()); + serverInstance->action_logger()->group_logger.log_group_rename(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name(), old_name); if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); @@ -456,18 +519,26 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId()) return command_result{error::parameter_invalid, "Could not delete default server group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId()) return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId()) return command_result{error::parameter_invalid, "Could not delete instance default server group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId()) return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"}; auto type = serverGroup->type(); + log::GroupType log_group_type; if(type == GroupType::GROUP_TYPE_QUERY) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + log_group_type = log::GroupType::QUERY; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + log_group_type = log::GroupType::TEMPLATE; + } else { + log_group_type = log::GroupType::NORMAL; } if (!cmd["force"].as()) @@ -475,6 +546,7 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { return command_result{error::database_empty_result, "group not empty!"}; if (group_manager->deleteGroup(serverGroup)) { + serverInstance->action_logger()->group_logger.log_group_delete(this->getServerId(), this->ref(), log::GroupTarget::SERVER, log_group_type, serverGroup->groupId(), serverGroup->name()); if(this->server) this->server->forEachClient([&](shared_ptr cl) { if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) { @@ -613,8 +685,10 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) } } + std::shared_ptr connected_client{}; for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { + connected_client = targetClient; if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) { for (const auto &client : _server->getClients()) { if(client->isClientVisible(targetClient, true) || client == targetClient) @@ -627,6 +701,13 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) } } } + for(const auto& group : applied_groups) { + serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(target_server ? target_server->getServerId() : 0, + this->ref(), log::GroupTarget::SERVER, + group->groupId(), group->name(), + target_cldbid, connected_client ? connected_client->getDisplayName() : "" + ); + } return command_result{error::ok}; } @@ -740,8 +821,10 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) } } + std::shared_ptr connected_client{}; for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { + connected_client = targetClient; ConnectedLockedClient clock{targetClient}; if(!clock) continue; @@ -757,6 +840,13 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) } } } + for(const auto& group : applied_groups) { + serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(target_server ? target_server->getServerId() : 0, + this->ref(), log::GroupTarget::SERVER, + group->groupId(), group->name(), + target_cldbid, connected_client ? connected_client->getDisplayName() : "" + ); + } return command_result{error::ok}; } @@ -803,6 +893,15 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { auto permissions = serverGroup->permissions(); for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::SERVER_GROUP, + permission::v2::PermissionUpdateType::set_value, + serverGroup->groupId(), serverGroup->name(), + 0, "" + ); + update_server_group_list |= ppermission.is_group_property(); update_talk_power |= ppermission.is_client_view_property(); } @@ -859,6 +958,15 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { bool update_talk_power{false}, update_server_group_list{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::SERVER_GROUP, + permission::v2::PermissionUpdateType::delete_value, + serverGroup->groupId(), serverGroup->name(), + 0, "" + ); + update_server_group_list |= ppermission.is_group_property(); update_talk_power |= ppermission.is_client_view_property(); } @@ -923,8 +1031,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& bool update_clients{false}, update_server_group_list{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { - for(const auto& serverGroup : groups) + for(const auto& serverGroup : groups) { ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::SERVER_GROUP, + permission::v2::PermissionUpdateType::set_value, + serverGroup->groupId(), serverGroup->name(), + 0, "" + ); + } update_server_group_list |= ppermission.is_group_property(); update_clients |= ppermission.is_client_view_property(); @@ -993,8 +1110,17 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& bool update_clients{false}, update_server_group_list{false}; for(const auto& ppermission : pparser.iterate_valid_permissions()) { - for(const auto& serverGroup : groups) + for(const auto& serverGroup : groups) { ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value); + ppermission.log_update(serverInstance->action_logger()->permission_logger, + this->getServerId(), + this->ref(), + log::PermissionTarget::SERVER_GROUP, + permission::v2::PermissionUpdateType::delete_value, + serverGroup->groupId(), serverGroup->name(), + 0, "" + ); + } update_server_group_list |= ppermission.is_group_property(); update_clients |= ppermission.is_client_view_property(); diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index e684a10..cebe471 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "src/manager/ActionLogger.h" #include "src/client/command_handler/helpers.h" @@ -154,8 +155,10 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { { threads::MutexLock lock(this->handle->loginLock); - if(!account) + if(!account) { + serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER); return command_result{error::client_invalid_password, "username or password dose not match"}; + } if (account->password != password) { if(!this->whitelisted) { @@ -168,6 +171,8 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { return command_result{error::ban_flooding}; } } + + serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast(this->ref()), username, log::QueryAuthenticateResult::INVALID_PASSWORD); return command_result{error::client_invalid_password, "username or password dose not match"}; } } @@ -242,12 +247,13 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { this->properties()[property::CLIENT_TOTALCONNECTIONS]++; this->updateChannelClientProperties(true, true); + serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast(this->ref()), username, log::QueryAuthenticateResult::SUCCESS); return command_result{error::ok}; } command_result QueryClient::handleCommandLogout(Command &) { CMD_RESET_IDLE; - if(this->properties()[property::CLIENT_LOGIN_NAME].as().empty()) return command_result{error::client_not_logged_in, "not logged in"}; + if(this->properties()[property::CLIENT_LOGIN_NAME].as().empty()) return command_result{error::client_not_logged_in}; this->properties()[property::CLIENT_LOGIN_NAME] = ""; this->query_account = nullptr; @@ -293,6 +299,7 @@ command_result QueryClient::handleCommandLogout(Command &) { } this->updateChannelClientProperties(true, true); + serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast(this->ref()), "", log::QueryAuthenticateResult::SUCCESS); return command_result{error::ok}; } @@ -315,6 +322,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { if(target && target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) return command_result{error::server_is_not_running}; if(target == this->server) return command_result{error::ok}; + auto old_server_id = this->getServerId(); if(target) { if(this->query_account && this->query_account->bound_server > 0) { if(target->getServerId() != this->query_account->bound_server) @@ -362,7 +370,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { this->update_cached_permissions(); } this->updateChannelClientProperties(true, true); - + serverInstance->action_logger()->query_logger.log_query_switch(std::dynamic_pointer_cast(this->ref()), this->properties()[property::CLIENT_LOGIN_NAME].value(), old_server_id, this->getServerId()); return command_result{error::ok}; } @@ -632,6 +640,7 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) { time_global = duration_cast(end - start); } + serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION); Command res(""); res["sid"] = server->getServerId(); @@ -656,6 +665,7 @@ command_result QueryClient::handleCommandServerDelete(Command& cmd) { auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]); if(!server) return command_result{error::server_invalid_id, "invalid bounded server"}; if(!serverInstance->getVoiceServerManager()->deleteServer(server)) return command_result{error::vs_critical}; + serverInstance->action_logger()->server_logger.log_server_delete(server->getServerId(), this->ref()); return command_result{error::ok}; } @@ -684,7 +694,9 @@ command_result QueryClient::handleCommandServerStart(Command& cmd) { ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_start_any, 1); string err; - if(!server->start(err)) return command_result{error::vs_critical, err}; + if(!server->start(err)) + return command_result{error::vs_critical, err}; + serverInstance->action_logger()->server_logger.log_server_start(server->getServerId(), this->ref()); return command_result{error::ok}; } @@ -712,6 +724,7 @@ command_result QueryClient::handleCommandServerStop(Command& cmd) { ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_stop_any, 1); server->stop("server stopped", false); + serverInstance->action_logger()->server_logger.log_server_stop(server->getServerId(), this->ref()); return command_result{error::ok}; } @@ -846,7 +859,8 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { auto str = cmd.build().substr(strlen("serversnapshotdeploy ")); if(!ignore_hash) { auto buildHash = base64::encode(digest::sha1(str)); - if(buildHash != hash) return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"}; + if(buildHash != hash) + return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"}; } unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock); @@ -874,6 +888,7 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { res["error_start"] = ""; res["started"] = true; } + serverInstance->action_logger()->server_logger.log_server_create(result->getServerId(), this->ref(), log::ServerCreateReason::SNAPSHOT_DEPLOY); } res["time"] = duration_cast(end - start).count(); diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index 9d3b469..bf89f67 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -10,6 +10,8 @@ #include "VoiceClient.h" #include "src/VirtualServer.h" #include "../../server/VoiceServer.h" +#include "src/InstanceHandler.h" +#include "src/manager/ActionLogger.h" using namespace std; using namespace std::chrono; @@ -146,6 +148,11 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas cmd["invokeruid"] = invoker->getUid(); } + auto old_channel = this->currentChannel; + if(old_channel) { + serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name()); + } + if(notify_viewer && this->server) { unique_lock channel_lock(this->server->channel_tree_lock); this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock); diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index baf67ca..5f235f6 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "../../manager/ActionLogger.h" #if defined(TCP_CORK) && !defined(TCP_NOPUSH) #define TCP_NOPUSH TCP_CORK @@ -637,6 +638,10 @@ void WebClient::handleMessage(const std::string &message) { //candidate:0 1 UDP 2122252543 192.168.43.141 34590 typ host bool WebClient::disconnect(const std::string &reason) { + auto old_channel = this->currentChannel; + if(old_channel) { + serverInstance->action_logger()->client_channel_logger.log_client_leave(this->getServerId(), this->ref(), old_channel->channelId(), old_channel->name()); + } { unique_lock server_channel_lock(this->server->channel_tree_lock); { diff --git a/server/src/manager/ActionLogger.cpp b/server/src/manager/ActionLogger.cpp new file mode 100644 index 0000000..0874127 --- /dev/null +++ b/server/src/manager/ActionLogger.cpp @@ -0,0 +1,449 @@ +// +// Created by WolverinDEV on 26/06/2020. +// + +#include "ActionLogger.h" +#include "../client/ConnectedClient.h" +#include +#include + +using namespace ts::server; +using namespace ts::server::log; + +Invoker log::kInvokerSystem{"System", "system", 0}; + +void AbstractActionLogger::do_log(ServerId sid, Action action, sql::command &command) const { + if(!this->enabled_) return; + + command.executeLater().waitAndGetLater([sid, action](const sql::result& result) { + if(!result) { + logError(sid, "Failed to log action {}: {}", kActionName[(int) action], result.fmtStr()); + } + }, sql::result{}); +} + +template <> +void TypedActionLogger<>::compile_model(sql::model &result, const std::string &command, const ActionLogger *logger) { + std::exchange(result, sql::model{logger->sql_manager(), command}); +} + +template <> +void TypedActionLogger<>::register_invoker(ActionLogger *logger, const Invoker &invoker) { + logger->register_invoker(invoker); +} + +template <> +std::string TypedActionLogger<>::generate_insert(const std::string &table_name, + const FixedHeaderKeys& header, + DatabaseColumn *payload_columns, + size_t payload_columns_length) { + std::string result{"INSERT INTO `"}; + result.reserve(256); + + result += table_name + "` ("; + result += "`" + header.timestamp.name + "`,"; + result += "`" + header.server_id.name + "`,"; + result += "`" + header.invoker_id.name + "`,"; + result += "`" + header.invoker_name.name + "`,"; + result += "`" + header.action.name + "`"; + + for(size_t index{0}; index < payload_columns_length; index++) + result += ",`" + payload_columns[index].name + "`"; + + result += ") VALUES (:h0, :h1, :h2, :h3, :h4"; + + for(size_t index{0}; index < payload_columns_length; index++) + result += ", " + TypedActionLogger::value_binding_name(index); + result += ");"; + + debugMessage(0, "Insert query: {}", result); + return result; +} + +template <> +sql::command TypedActionLogger<>::compile_query( + ActionLogger *logger, + uint64_t server_id, + const std::chrono::system_clock::time_point &begin_timestamp, + const std::chrono::system_clock::time_point &end_timestamp, size_t limit, + const std::string& table_name, + const FixedHeaderKeys& header, + DatabaseColumn *payload_columns, + size_t payload_columns_length) { + std::string command{"SELECT "}; + command.reserve(256); + + command += "`" + header.timestamp.name + "`,"; + command += "`" + header.server_id.name + "`,"; + command += "`" + header.invoker_id.name + "`,"; + command += "`" + header.invoker_name.name + "`,"; + command += "`" + header.action.name + "`"; + + for(size_t index{0}; index < payload_columns_length; index++) + command += ",`" + payload_columns[index].name + "`"; + + command += " FROM `" + table_name + "` WHERE `" + header.server_id.name + "` = " + std::to_string(server_id); + + if(begin_timestamp.time_since_epoch().count() > 0) + command += " AND `" + header.timestamp.name + "` <= " + std::to_string(std::chrono::ceil(begin_timestamp.time_since_epoch()).count()); + + if(begin_timestamp.time_since_epoch().count() > 0) + command += " AND `" + header.timestamp.name + "` >= " + std::to_string(std::chrono::floor(end_timestamp.time_since_epoch()).count()); + + command += " ORDER BY `timestamp` DESC"; + if(limit > 0) + command += " LIMIT " + std::to_string(limit); + command += ";"; + + debugMessage(0, "Log query: {}", command); + return sql::command{logger->sql_manager(), command}; +} + +template <> +bool TypedActionLogger<>::create_table( + std::string& error, + ActionLogger *logger, + const std::string& table_name, + const FixedHeaderKeys& header, + DatabaseColumn *payload_columns, + size_t payload_columns_length) { + std::string command{}; + /* ATTENTION: If I implement MySQL add "CHARACTER SET=utf8" AT THE END! */ + if(logger->sql_manager()->getType() != sql::TYPE_SQLITE) { + error = "unsupported database type"; + return false; + } + + command.reserve(256); + command += "CREATE TABLE `" + table_name + "` ("; + command += "`id` INTEGER PRIMARY KEY NOT NULL,"; + command += "`" + header.timestamp.name + "` " + header.timestamp.type + ","; + command += "`" + header.server_id.name + "` " + header.server_id.type + ","; + command += "`" + header.invoker_id.name + "` " + header.invoker_id.type + ","; + command += "`" + header.invoker_name.name + "` " + header.invoker_name.type + ","; + command += "`" + header.action.name + "` " + header.action.type; + + for(size_t index{0}; index < payload_columns_length; index++) + command += ",`" + payload_columns[index].name + "` " + payload_columns[index].type; + + command += ");"; + + auto result = sql::command{logger->sql_manager(), command}.execute(); + if(!result) { + error = result.fmtStr(); + return false; + } + return true; +} + +template <> +bool TypedActionLogger<>::parse_fixed_header(FixedHeaderValues &result, std::string *values, size_t values_length) { + if(values_length< 5) + return false; + + int64_t timestamp; + try { + timestamp = std::stoll(values[0]); + result.server_id = std::stoull(values[1]); + result.invoker.database_id = std::stoull(values[2]); + } catch (std::exception&) { + return false; + } + result.timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{timestamp}; + result.invoker.name = values[3]; + + result.action = Action::UNKNOWN; + for(size_t index{0}; index < (size_t) Action::MAX; index++) { + auto action = static_cast(index); + if(kActionName[index] == values[4]) { + result.action = action; + } + } + + return result.action != Action::UNKNOWN; +} + +template <> +void TypedActionLogger<>::bind_fixed_header(sql::command &command, const FixedHeaderValues &fhvalues) { + command.value(":h0", std::chrono::floor(fhvalues.timestamp).time_since_epoch().count()); + command.value(":h1", fhvalues.server_id); + command.value(":h2", fhvalues.invoker.database_id); + command.value(":h3", fhvalues.invoker.name); + command.value(":h4", kActionName[(int) fhvalues.action]); +} + +ActionLogger::ActionLogger() = default; +ActionLogger::~ActionLogger() { + this->finalize(); +} + +#define CURRENT_VERSION 1 +bool ActionLogger::initialize(std::string &error) { + int db_version{0}; + + this->sql_handle = new sql::sqlite::SqliteManager{}; + auto result = this->sql_handle->connect("sqlite://InstanceLogs.sqlite"); + if(!result) { + error = result.fmtStr(); + goto error_exit; + } + + { + if(sql_handle->getType() == sql::TYPE_MYSQL) { + sql::command(this->sql_handle, "SET NAMES utf8").execute(); + //sql::command(this->manager, "DEFAULT CHARSET=utf8").execute(); + } else if(sql_handle->getType() == sql::TYPE_SQLITE) { + sql::command(this->sql_handle, "PRAGMA locking_mode = EXCLUSIVE;").execute(); + sql::command(this->sql_handle, "PRAGMA synchronous = NORMAL;").execute(); + sql::command(this->sql_handle, "PRAGMA journal_mode = WAL;").execute(); + sql::command(this->sql_handle, "PRAGMA encoding = \"UTF-8\";").execute(); + } + } + + /* begin transaction, if available */ + if(this->sql_handle->getType() == sql::TYPE_SQLITE) { + result = sql::command(this->sql_handle, "BEGIN TRANSACTION;").execute(); + if(!result) { + error = "failed to begin transaction (" + result.fmtStr() + ")"; + goto error_exit; + } + } + + { + result = sql::command{this->sql_handle, "CREATE TABLE IF NOT EXISTS general(`key` VARCHAR(256), `value` TEXT);"}.execute(); + if(!result) { + error = "failed to create if not exists the general table: " + result.fmtStr(); + goto error_exit; + } + + sql::command{this->sql_handle, "SELECT `value` FROM `general` WHERE `key` = 'version';"}.query([&](int, std::string* values, std::string*) { + db_version = std::stoi(values[0]); + }); + } + + switch (db_version) { + case 0: + /* initial setup */ + case CURRENT_VERSION: + /* current version */ + break; + + default: + error = CURRENT_VERSION < db_version ? "database version is newer than supported (supported: " + std::to_string(CURRENT_VERSION) + ", database: " + std::to_string(db_version) + ")" : + "invalid database version (" + std::to_string(db_version) + ")"; + } + + if(!this->server_logger.setup(db_version, error)) { + error = "server logger: " + error; + goto error_exit; + } + + if(!this->server_edit_logger.setup(db_version, error)) { + error = "server edit logger: " + error; + goto error_exit; + } + + if(!this->channel_logger.setup(db_version, error)) { + error = "channel logger: " + error; + goto error_exit; + } + + if(!this->permission_logger.setup(db_version, error)) { + error = "permission logger: " + error; + goto error_exit; + } + + if(!this->group_logger.setup(db_version, error)) { + error = "group logger: " + error; + goto error_exit; + } + + if(!this->group_assignment_logger.setup(db_version, error)) { + error = "group assignment logger: " + error; + goto error_exit; + } + + if(!this->client_channel_logger.setup(db_version, error)) { + error = "client channel logger: " + error; + goto error_exit; + } + + if(!this->client_edit_logger.setup(db_version, error)) { + error = "client edit logger: " + error; + goto error_exit; + } + + if(!this->file_logger.setup(db_version, error)) { + error = "file logger: " + error; + goto error_exit; + } + + if(!this->custom_logger.setup(db_version, error)) { + error = "custom logger: " + error; + goto error_exit; + } + + if(!this->query_logger.setup(db_version, error)) { + error = "query logger: " + error; + goto error_exit; + } + + if(!this->query_authenticate_logger.setup(db_version, error)) { + error = "query authenticate logger: " + error; + goto error_exit; + } + + + /* update the version */ + { + std::string command{}; + if(db_version == 0) + command = "INSERT INTO `general` (`key`, `value`) VALUES ('version', :version);"; + else + command = "UPDATE `general` SET `value` = :version WHERE `key` = 'version';"; + + auto res = sql::command{this->sql_handle, command, variable{":version", CURRENT_VERSION}}.execute(); + if(!res) { + logCritical(LOG_INSTANCE, "Failed to increment action log database version!"); + goto error_exit; + } + } + + if(this->sql_handle->getType() == sql::TYPE_SQLITE) { + result = sql::command(this->sql_handle, "COMMIT;").execute(); + if(!result) { + error = "failed to commit changes"; + return false; + } + } + + /* enable all loggers */ + this->server_logger.set_enabled(true); + this->server_edit_logger.set_enabled(true); + this->channel_logger.set_enabled(true); + this->permission_logger.set_enabled(true); + this->group_logger.set_enabled(true); + this->group_assignment_logger.set_enabled(true); + this->client_channel_logger.set_enabled(true); + this->client_edit_logger.set_enabled(true); + this->file_logger.set_enabled(true); + this->custom_logger.set_enabled(true); + this->query_logger.set_enabled(true); + this->query_authenticate_logger.set_enabled(true); + return true; + + error_exit: + if(this->sql_handle && this->sql_handle->connected()) { + result = sql::command(this->sql_handle, "ROLLBACK;").execute(); + if (!result) { + logCritical(LOG_GENERAL, "Failed to rollback log database after transaction."); + } else { + debugMessage(LOG_GENERAL, "Rollbacked log database successfully."); + } + } + this->finalize(); + return false; +} + +void ActionLogger::finalize() { + if(!this->sql_handle) + return; + + this->server_logger.finalize(); + this->server_edit_logger.finalize(); + this->channel_logger.finalize(); + this->permission_logger.finalize(); + this->group_logger.finalize(); + this->group_assignment_logger.finalize(); + this->client_channel_logger.finalize(); + this->client_edit_logger.finalize(); + this->custom_logger.finalize(); + this->file_logger.finalize(); + this->query_logger.finalize(); + this->query_authenticate_logger.finalize(); + + this->sql_handle->pool->threads()->wait_for(std::chrono::seconds{1}); + this->sql_handle->disconnect(); + delete this->sql_handle; + this->sql_handle = nullptr; +} + +void ActionLogger::register_invoker(const Invoker &) {} + +std::vector ActionLogger::query( + std::vector groups, + uint64_t server, + const std::chrono::system_clock::time_point &begin, + const std::chrono::system_clock::time_point &end, + size_t limit) { + + std::vector result{}; + result.reserve(limit == 0 ? 1024 * 8 : limit * 2); + + std::vector> intresult{}; + intresult.reserve(8); + + for(size_t index{0}; index < (size_t) LoggerGroup::MAX; index++) { + auto group = static_cast(index); + if(!groups.empty() && std::find(groups.begin(), groups.end(), group) == groups.end()) + continue; + + intresult.clear(); + switch (group) { + case LoggerGroup::SERVER: + std::exchange(intresult.emplace_back(), this->server_logger.query(server, begin, end, limit)); + std::exchange(intresult.emplace_back(), this->server_edit_logger.query(server, begin, end, limit)); + std::exchange(intresult.emplace_back(), this->group_logger.query(server, begin, end, limit)); + std::exchange(intresult.emplace_back(), this->group_assignment_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::CHANNEL: + std::exchange(intresult.emplace_back(), this->channel_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::CLIENT: + std::exchange(intresult.emplace_back(), this->client_channel_logger.query(server, begin, end, limit)); + std::exchange(intresult.emplace_back(), this->client_edit_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::FILES: + std::exchange(intresult.emplace_back(), this->file_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::PERMISSION: + std::exchange(intresult.emplace_back(), this->permission_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::CUSTOM: + std::exchange(intresult.emplace_back(), this->custom_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::QUERY: + std::exchange(intresult.emplace_back(), this->query_logger.query(server, begin, end, limit)); + std::exchange(intresult.emplace_back(), this->query_authenticate_logger.query(server, begin, end, limit)); + break; + + case LoggerGroup::MAX: + default: + assert(false); + break; + } + + for(auto& qresult : intresult) { + if(qresult.empty()) + continue; + /* qresult is already sorted */ + result.insert(result.begin(), qresult.begin(), qresult.begin() + std::min(qresult.size(), limit)); + + std::sort(result.begin(), result.end(), [](const LogEntryInfo& a, const LogEntryInfo& b){ + if(a.timestamp == b.timestamp) + return &a > &b; + return a.timestamp > b.timestamp; + }); + if(limit > 0 && result.size() > limit) + result.erase(result.begin() + limit, result.end()); + } + } + + return result; +} \ No newline at end of file diff --git a/server/src/manager/ActionLogger.h b/server/src/manager/ActionLogger.h new file mode 100644 index 0000000..d121ef1 --- /dev/null +++ b/server/src/manager/ActionLogger.h @@ -0,0 +1,843 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ts::server { + class ConnectedClient; + class QueryClient; +} + +namespace ts::server::log { + struct Invoker { + std::string name{}; + std::string unique_id{}; + ClientDbId database_id{0}; + }; + extern Invoker kInvokerSystem; + + enum struct LoggerGroup { + CUSTOM, + SERVER, + CHANNEL, + PERMISSION, + CLIENT, + FILES, + QUERY, + MAX + }; + constexpr static std::array kLoggerGroupName{ + "custom", + "server", + "channel", + "permission", + "client", + "files", + "query" + }; + + + enum struct Action { + UNKNOWN, + + SERVER_CREATE, + SERVER_START, + SERVER_EDIT, + SERVER_STOP, + SERVER_DELETE, + + CHANNEL_CREATE, + CHANNEL_EDIT, + CHANNEL_DELETE, + + PERMISSION_ADD_VALUE, + PERMISSION_ADD_GRANT, + + PERMISSION_EDIT_VALUE, + PERMISSION_EDIT_GRANT, + + PERMISSION_REMOVE_VALUE, + PERMISSION_REMOVE_GRANT, + + GROUP_CREATE, + GROUP_RENAME, + GROUP_PERMISSION_COPY, + GROUP_DELETE, + + GROUP_ASSIGNMENT_ADD, + GROUP_ASSIGNMENT_REMOVE, + + CLIENT_JOIN, + CLIENT_EDIT, + CLIENT_MOVE, + CLIENT_KICK, + CLIENT_LEAVE, + + FILE_UPLOAD, + FILE_DOWNLOAD, + FILE_RENAME, + FILE_DELETE, + FILE_DIRECTORY_CREATE, + + CUSTOM_LOG, + + QUERY_AUTHENTICATE, + QUERY_JOIN, + QUERY_LEAVE, + + MAX + }; + + constexpr static std::array kActionName{ + "unknown", + "server-create", + "server-start", + "server-edit", + "server-stop", + "server-delete", + + "channel-create", + "channel-edit", + "channel-delete", + + "permission-add-value", + "permission-add-grant", + + "permission-edit-value", + "permission-edit-grant", + + "permission-remove-value", + "permission-remove-grant", + + "group-create", + "group-rename", + "group-permission-copy", + "group-delete", + + "group-assignment-add", + "group-assignment-remove", + + "client-join", + "client-edit", + "client-move", + "client-kick", + "client-leave", + + "file-upload", + "file-download", + "file-rename", + "file-delete", + "file-directory-create", + + "custom-log", + + "query-authenticate", + "query-join", + "query-leave" + }; + + struct LogEntryInfo { + std::chrono::system_clock::time_point timestamp{}; + Action action{Action::UNKNOWN}; + + standalone_command_builder_bulk info{}; + }; + + class ActionLogger; + + class AbstractActionLogger { + public: + explicit AbstractActionLogger(ActionLogger* impl) : logger_{impl} {} + + [[nodiscard]] virtual bool setup(int /* database version */, std::string& /* error */) = 0; + virtual void finalize() { this->enabled_ = false; } + + [[nodiscard]] inline ActionLogger* logger() const { return this->logger_; } + + [[nodiscard]] inline bool enabled() const { return this->enabled_; } + inline void set_enabled(bool flag) { this->enabled_ = flag; } + + [[nodiscard]] virtual std::deque query( + uint64_t /* server id */, + const std::chrono::system_clock::time_point& /* begin timestamp */, + const std::chrono::system_clock::time_point& /* end timestamp */, + size_t /* limit */ + ) = 0; + protected: + ActionLogger* logger_; + bool enabled_{false}; + + void do_log(ServerId /* server id */, Action /* action */, sql::command& /* command */) const; + }; + + struct DatabaseColumn { + std::string name{}; + std::string type{}; + }; + + struct FixedHeaderKeys { + DatabaseColumn timestamp{}; + DatabaseColumn server_id{}; + DatabaseColumn invoker_id{}; + DatabaseColumn invoker_name{}; + DatabaseColumn action{}; + }; + + template + using T2DatabaseColumn = DatabaseColumn; + + struct FixedHeaderValues { + std::chrono::system_clock::time_point timestamp{}; + ServerId server_id{0}; + Invoker invoker{}; + Action action{}; + }; + + template + class TypedActionLogger : public AbstractActionLogger { + public: + constexpr static auto kFixedHeaderColumnCount = 5; + static inline std::string value_binding_name(size_t index) { + return ":v" + std::to_string(index); + } + + static void compile_model(sql::model& result, const std::string& command, const ActionLogger* logger); + static void register_invoker(ActionLogger* logger, const Invoker& invoker); + static std::string generate_insert( + const std::string& /* table name */, + const FixedHeaderKeys& /* headers */, + DatabaseColumn* /* payload columns */, + size_t /* payload column count */ + ); + static sql::command compile_query( + ActionLogger* /* logger */, + uint64_t /* server id */, + const std::chrono::system_clock::time_point& /* begin timestamp */, + const std::chrono::system_clock::time_point& /* end timestamp */, + size_t /* limit */, + const std::string& /* table name */, + const FixedHeaderKeys& /* headers */, + DatabaseColumn* /* payload columns */, + size_t /* payload column count */ + ); + static bool create_table( + std::string& /* error */, + ActionLogger* /* logger */, + const std::string& /* table name */, + const FixedHeaderKeys& /* headers */, + DatabaseColumn* /* payload columns */, + size_t /* payload column count */ + ); + static void bind_fixed_header(sql::command& /* command */, const FixedHeaderValues& /* values */); + static bool parse_fixed_header(FixedHeaderValues& /* result */, std::string* /* values */, size_t /* values length */); + + explicit TypedActionLogger(ActionLogger* impl, std::string table_name, FixedHeaderKeys fh_keys, const T2DatabaseColumn&... keys) + : AbstractActionLogger{impl}, fh_keys{std::move(fh_keys)}, table_name{std::move(table_name)} { + this->bind_key(0, keys...); + } + + [[nodiscard]] std::deque query( + uint64_t server_id, + const std::chrono::system_clock::time_point& begin_timestamp, + const std::chrono::system_clock::time_point& end_timestamp, + size_t limit + ) override { + std::deque result{}; + + auto sql_command = TypedActionLogger<>::compile_query(this->logger_, server_id, begin_timestamp, end_timestamp, limit, this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size()); + + FixedHeaderValues header{}; + auto sql_result = sql_command.query([&](int length, std::string* values, std::string* names) { + if(length < kFixedHeaderColumnCount + sizeof...(LoggingArguments)) + return; + + if(!TypedActionLogger<>::parse_fixed_header(header, values, length)) + return; + + auto& entry = result.emplace_back(); + entry.action = header.action; + entry.timestamp = header.timestamp; + + auto& info = entry.info; + info.reserve(160); + info.put_unchecked("timestamp", std::chrono::floor(header.timestamp.time_since_epoch()).count()); + info.put_unchecked("invoker_database_id", header.invoker.database_id); + info.put_unchecked("invoker_nickname", header.invoker.name); + info.put_unchecked("action", kActionName[(int) header.action]); + + for(size_t index{0}; index < sizeof...(LoggingArguments); index++) + info.put_unchecked(this->payload_column_names[index].name, values[index + kFixedHeaderColumnCount]); + }); + + return result; + }; + protected: + void do_log(const FixedHeaderValues& fhvalues, const LoggingArguments&... vvalues) { + if(!this->enabled_) + return; + + sql::command command = this->sql_model.command(); + + TypedActionLogger<>::bind_fixed_header(command, fhvalues); + TypedActionLogger::bind_value(command, 0, vvalues...); + + AbstractActionLogger::do_log(fhvalues.server_id, fhvalues.action, command); + TypedActionLogger<>::register_invoker(this->logger_, fhvalues.invoker); + } + + bool setup(int db_version, std::string &error) override { + TypedActionLogger<>::compile_model( + this->sql_model, + TypedActionLogger<>::generate_insert(this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size()), + this->logger_); + if(db_version == 0) { + if(!TypedActionLogger<>::create_table(error, this->logger_, this->table_name, this->fh_keys, this->payload_column_names.data(), this->payload_column_names.size())) + return false; + } + return true; + } + + private: + template + static void bind_value(sql::command& result, int index, const T& value, const Rest&... vvalues) { + result.value(TypedActionLogger::value_binding_name(index), value); + TypedActionLogger::bind_value(result, index + 1, vvalues...); + } + + static void bind_value(sql::command&, int) {} + + FixedHeaderKeys fh_keys; + sql::model sql_model{nullptr}; + std::string table_name; + std::array payload_column_names{}; + + template + void bind_key(int index, const T& value, const Rest&... vkeys) { + this->payload_column_names[index] = value; + this->bind_key(index + 1, vkeys...); + } + + void bind_key(int) {} + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `property`, `value_old`, `value_new` + class ServerEditActionLogger : public TypedActionLogger { + public: + explicit ServerEditActionLogger(ActionLogger* impl); + + bool setup(int, std::string &) override; + void log_server_edit( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + const property::PropertyDescription& /* property */, + const std::string& /* old value */, + const std::string& /* new value */); + }; + + enum struct ServerCreateReason { + USER_ACTION, + SNAPSHOT_DEPLOY, + INITIAL_SERVER, + MAX + }; + constexpr static std::array kServerCreateReasonName { + "user-action", + "snapshot-deploy", + "initial-server" + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `reason` + class ServerActionLogger : public TypedActionLogger { + public: + explicit ServerActionLogger(ActionLogger* impl); + + bool setup(int, std::string &) override; + + void log_server_create( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + ServerCreateReason /* create reason */); + + void log_server_delete( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */); + + void log_server_start( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */); + + void log_server_stop( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */); + }; + + enum struct ChannelDeleteReason { + PARENT_DELETED, + USER_ACTION, + SERVER_STOP, + EMPTY, + MAX + }; + constexpr static std::array kChannelDeleteReasonName { + "parent-deleted", + "user-action", + "server-stop", + "empty" + }; + + enum struct ChannelType { + TEMPORARY, + SEMI_PERMANENT, + PERMANENT, + MAX + }; + constexpr static std::array kChannelTypeName { + "temporary", + "semi-permanent", + "permanent" + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `property` (may be the delete reason), `value_old`, `value_new` + class ChannelActionLogger : public TypedActionLogger { + public: + explicit ChannelActionLogger(ActionLogger* impl); + + bool setup(int, std::string &) override; + + void log_channel_create( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + ChannelId /* channel id */, + ChannelType /* type */); + + void log_channel_move( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + ChannelId /* channel id */, + ChannelId /* old parent channel */, + ChannelId /* new parent channel */, + ChannelId /* old channel order */, + ChannelId /* new channel order */); + + void log_channel_edit( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + ChannelId /* channel id */, + const property::PropertyDescription& /* property */, + const std::string& /* old value */, + const std::string& /* new value */); + + void log_channel_delete( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + ChannelId /* channel id */, + ChannelDeleteReason /* reason */); + }; + + enum struct PermissionTarget { + SERVER_GROUP, + CHANNEL_GROUP, + CHANNEL, + CLIENT, + CLIENT_CHANNEL, + PLAYLIST, + PLAYLIST_CLIENT, + + MAX + }; + constexpr static std::array kPermissionTargetName { + "server-group", + "channel-group", + "channel", + "client", + "client-channel", + "playlist", + "playlist-client" + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target`, `id1`, `id1_name`, `id2`, `id2_name`, `permission`, `old_value`, `old_negated`, `old_skipped`, `new_value`, `new_negated`, `new_skipped` + class PermissionActionLogger : public TypedActionLogger { + public: + explicit PermissionActionLogger(ActionLogger* impl); + + bool setup(int, std::string &) override; + + void log_permission_add_value( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* value */, + bool /* negated */, + bool /* skipped */); + + void log_permission_add_grant( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* value */); + + void log_permission_edit_value( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* old value */, + bool /* old negated */, + bool /* old skipped */, + int32_t /* new value */, + bool /* new negated */, + bool /* new skipped */); + + void log_permission_edit_grant( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* old value */, + int32_t /* new value */); + + void log_permission_remove_value( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* old value */, + bool /* old negate */, + bool /* old skip */); + + void log_permission_remove_grant( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + server::log::PermissionTarget /* target */, + uint64_t /* id1 */, const std::string& /* id1 name */, + uint64_t /* id2 */, const std::string& /* id2 name */, + const permission::PermissionTypeEntry& /* permission */, + int32_t /* old value */); + }; + + enum struct GroupType { + NORMAL, + TEMPLATE, + QUERY, + MAX + }; + constexpr static std::array kGroupTypeName { + "normal", + "template", + "query" + }; + + enum struct GroupTarget { + SERVER, + CHANNEL, + MAX + }; + constexpr static std::array kGroupTargetName { + "server", + "channel" + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `type`, `target`, `group`, `group_name`, `source`, `source_name` + class GroupActionLogger : public TypedActionLogger { + public: + explicit GroupActionLogger(ActionLogger* impl); + + bool setup(int, std::string &) override; + + void log_group_create( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + GroupType /* type */, + uint64_t /* group */, + const std::string& /* group name */, + uint64_t /* source */, + const std::string& /* source name */ + ); + + void log_group_rename( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + GroupType /* type */, + uint64_t /* group */, + const std::string& /* target name */, + const std::string& /* source name */ + ); + + void log_group_permission_copy( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + GroupType /* type */, + uint64_t /* target group */, + const std::string& /* target group name */, + uint64_t /* source */, + const std::string& /* source name */ + ); + + void log_group_delete( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + GroupType /* type */, + uint64_t /* group */, + const std::string& /* group name */ + ); + }; + + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target`, `group`, `group_name`, `client`, `client_name` + class GroupAssignmentActionLogger : public TypedActionLogger { + public: + explicit GroupAssignmentActionLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + void log_group_assignment_add( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + uint64_t /* group */, + const std::string& /* group name */, + uint64_t /* client */, + const std::string& /* client name */ + ); + + void log_group_assignment_remove( + ServerId /* server id */, + const std::shared_ptr& /* editor (may be null) */, + GroupTarget /* target */, + uint64_t /* group */, + const std::string& /* group name */, + uint64_t /* client */, + const std::string& /* client name */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target_client`, `target_client_name`, `source_channel`, `source_channel_name`, `target_channel`, `target_channel_name` + class ClientChannelActionLogger : public TypedActionLogger { + public: + explicit ClientChannelActionLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + + void log_client_join( + ServerId /* server id */, + const std::shared_ptr& /* subject */, + uint64_t /* target channel */, + const std::string& /* target channel name */ + ); + + void log_client_move( + ServerId /* server id */, + const std::shared_ptr& /* issuer (may be null) */, + const std::shared_ptr& /* subject */, + uint64_t /* target channel */, + const std::string& /* target channel name */, + uint64_t /* source channel */, + const std::string& /* source channel name */ + ); + + void log_client_kick( + ServerId /* server id */, + const std::shared_ptr& /* issuer (may be null) */, + const std::shared_ptr& /* subject */, + uint64_t /* target channel */, + const std::string& /* target channel name */, + uint64_t /* source channel */, + const std::string& /* source channel name */ + ); + + void log_client_leave( + ServerId /* server id */, + const std::shared_ptr& /* subject */, + uint64_t /* source channel */, + const std::string& /* source channel name */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `target_client`, `target_client_name`, `property`, `old_value`, `new_value` + class ClientEditActionLogger : public TypedActionLogger { + public: + explicit ClientEditActionLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + void log_client_edit( + ServerId /* server id */, + const std::shared_ptr& /* issuer (may be null) */, + const std::shared_ptr& /* subject */, + const property::PropertyDescription& /* property */, + const std::string& /* old value */, + const std::string& /* new value */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `source_channel_id`, `source_path`, `target_channel_id`, `target_path` + class FilesActionLogger : public TypedActionLogger { + public: + explicit FilesActionLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + void log_file_upload( + ServerId /* server id */, + const std::shared_ptr& /* invoker */, + uint64_t /* channel id */, + const std::string& /* path */ + ); + + void log_file_download( + ServerId /* server id */, + const std::shared_ptr& /* invoker */, + uint64_t /* channel id */, + const std::string& /* path */ + ); + + void log_file_rename( + ServerId /* server id */, + const std::shared_ptr& /* invoker */, + uint64_t /* source channel id */, + const std::string& /* source name */, + uint64_t /* target channel id */, + const std::string& /* target name */ + ); + + void log_file_delete( + ServerId /* server id */, + const std::shared_ptr& /* invoker */, + uint64_t /* channel id */, + const std::string& /* source name */ + ); + + void log_file_directory_create( + ServerId /* server id */, + const std::shared_ptr& /* invoker */, + uint64_t /* channel id */, + const std::string& /* path */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `message` + class CustomLogger : public TypedActionLogger { + public: + explicit CustomLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + + void add_log_message( + ServerId /* server id */, + const std::shared_ptr& /* issuer (may be null) */, + const std::string& /* message */ + ); + }; + + enum struct QueryAuthenticateResult { + SUCCESS, + UNKNOWN_USER, + INVALID_PASSWORD, + MAX + }; + + constexpr static std::array kQueryAuthenticateResultName { + "success", + "unknown-user", + "invalid-password" + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `result` + class QueryAuthenticateLogger : public TypedActionLogger { + public: + explicit QueryAuthenticateLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + void log_query_authenticate( + ServerId /* server id */, + const std::shared_ptr& /* query */, + const std::string& /* username (if empty he logs out) */, + QueryAuthenticateResult /* result */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `source_server` + class QueryServerLogger : public TypedActionLogger { + public: + explicit QueryServerLogger(ActionLogger *impl); + + bool setup(int, std::string &) override; + + void log_query_switch( + const std::shared_ptr& /* query */, + const std::string& /* authenticated username */, + ServerId /* source */, + ServerId /* target */ + ); + }; + + /* + VIRTUALSERVER_LOG_CLIENT, (Bans: TODO!, Updates: Done, Channel Tree: Done) + VIRTUALSERVER_LOG_QUERY, (Check)) + VIRTUALSERVER_LOG_CHANNEL, (Check) + VIRTUALSERVER_LOG_PERMISSIONS, (Check) + VIRTUALSERVER_LOG_SERVER, (Check; Groups: Check; Assignments: Check; Administrate: Check) + VIRTUALSERVER_LOG_FILETRANSFER (Check) + */ + class ActionLogger { + public: + explicit ActionLogger(); + ~ActionLogger(); + + [[nodiscard]] bool initialize(std::string& /* error */); + void finalize(); + + [[nodiscard]] inline sql::SqlManager* sql_manager() const { return this->sql_handle; } + + void register_invoker(const Invoker&); + + [[nodiscard]] virtual std::vector query( + std::vector /* groups */, + uint64_t /* server id */, + const std::chrono::system_clock::time_point& /* begin timestamp */, + const std::chrono::system_clock::time_point& /* end timestamp */, + size_t /* limit */ + ); + + ServerActionLogger server_logger{this}; + ServerEditActionLogger server_edit_logger{this}; + ChannelActionLogger channel_logger{this}; + PermissionActionLogger permission_logger{this}; + GroupActionLogger group_logger{this}; + GroupAssignmentActionLogger group_assignment_logger{this}; + ClientChannelActionLogger client_channel_logger{this}; + ClientEditActionLogger client_edit_logger{this}; + FilesActionLogger file_logger{this}; + CustomLogger custom_logger{this}; + QueryServerLogger query_logger{this}; + QueryAuthenticateLogger query_authenticate_logger{this}; + private: + sql::SqlManager* sql_handle{nullptr}; + }; +} \ No newline at end of file diff --git a/server/src/manager/ActionLoggerImpl.cpp b/server/src/manager/ActionLoggerImpl.cpp new file mode 100644 index 0000000..d99d937 --- /dev/null +++ b/server/src/manager/ActionLoggerImpl.cpp @@ -0,0 +1,898 @@ +// +// Created by WolverinDEV on 26/06/2020. +// + +#include "ActionLogger.h" +#include "../client/ConnectedClient.h" +#include "../client/query/QueryClient.h" + +using namespace ts::server; +using namespace ts::server::log; + +static FixedHeaderKeys kDefaultHeaderFields{ + .timestamp = {"timestamp", "BIGINT"}, + .server_id = {"server_id", "INT"}, + .invoker_id = {"invoker_database_id", "BIGINT"}, + .invoker_name = {"invoker_name", "VARCHAR(128)"}, + .action = {"action", "VARCHAR(64)"} +}; + +inline Invoker client_to_invoker(const std::shared_ptr& client) { + return client ? Invoker{ + .name = client->getDisplayName(), + .unique_id = client->getUid(), + .database_id = client->getClientDatabaseId() + } : kInvokerSystem; +} + +/* server action logger */ +bool ServerActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +ServerActionLogger::ServerActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_server", + kDefaultHeaderFields, + {"reason", "VARCHAR(64)"} +} { } + +void ServerActionLogger::log_server_create(ServerId sid, const std::shared_ptr &invoker, ServerCreateReason reason) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(invoker), + .action = Action::SERVER_CREATE, + }, kServerCreateReasonName[(int) reason]); +} + +void ServerActionLogger::log_server_delete(ServerId sid, const std::shared_ptr &invoker) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(invoker), + .action = Action::SERVER_DELETE, + }, ""); +} + +void ServerActionLogger::log_server_start(ServerId sid, const std::shared_ptr &invoker) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(invoker), + .action = Action::SERVER_START, + }, ""); +} + +void ServerActionLogger::log_server_stop(ServerId sid, const std::shared_ptr &invoker) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(invoker), + .action = Action::SERVER_STOP, + }, ""); +} + +/* -------------------------- server edit logger -------------------------- */ +bool ServerEditActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +ServerEditActionLogger::ServerEditActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_server_edit", + kDefaultHeaderFields, + {"property", "VARCHAR(128)"}, + {"value_old", "TEXT"}, + {"value_new", "TEXT"} +} { } + +void ServerEditActionLogger::log_server_edit(ServerId sid, + const std::shared_ptr& client, + const property::PropertyDescription &property, + const std::string &old_value, + const std::string &new_value) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::SERVER_EDIT, + }, property.name, old_value, new_value); +} + +/* -------------------------- channel logger -------------------------- */ +bool ChannelActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +ChannelActionLogger::ChannelActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_channel", + kDefaultHeaderFields, + {"channel_id", "BIGINT"}, + {"property", "VARCHAR(128)"}, + {"value_old", "TEXT"}, + {"value_new", "TEXT"} +} { } + +void ChannelActionLogger::log_channel_create(ServerId sid, const std::shared_ptr &client, ChannelId cid, ChannelType type) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::CHANNEL_CREATE, + }, cid, kChannelTypeName[(int) type], "", ""); +} + +void ChannelActionLogger::log_channel_edit(ServerId sid, + const std::shared_ptr& client, + ChannelId cid, + const property::PropertyDescription &property, + const std::string &old_value, + const std::string &new_value) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::CHANNEL_EDIT, + }, cid, property.name, old_value, new_value); +} + +void ChannelActionLogger::log_channel_move(ServerId sid, const std::shared_ptr &client, ChannelId cid, ChannelId opcid, ChannelId npcid, ChannelId oco, ChannelId nco) { + auto timestamp = std::chrono::system_clock::now(); + if(opcid != npcid) { + this->do_log({ + .timestamp = timestamp, + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::CHANNEL_EDIT, + }, cid, "channel_parent_id", std::to_string(opcid), std::to_string(npcid)); + } + + if(oco != nco) { + this->do_log({ + .timestamp = timestamp, + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::CHANNEL_EDIT, + }, cid, "channel_order", std::to_string(oco), std::to_string(nco)); + } +} + +void ChannelActionLogger::log_channel_delete(ServerId sid, const std::shared_ptr &client, ChannelId cid, ChannelDeleteReason reason) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::CHANNEL_DELETE, + }, cid, kChannelDeleteReasonName[(int) reason], "", ""); +} + +/* -------------------------- permission logger -------------------------- */ +bool PermissionActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +PermissionActionLogger::PermissionActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_permission", + kDefaultHeaderFields, + {"target", "VARCHAR(64)"}, + {"id1", "BIGINT"}, + {"id1_name", "VARCHAR(128)"}, + {"id2", "BIGINT"}, + {"id2_name", "VARCHAR(128)"}, + {"permission", "VARCHAR(64)"}, + {"old_value", "BIGINT"}, + {"old_negated", "INT(1)"}, + {"old_skipped", "INT(1)"}, + {"new_value", "BIGINT"}, + {"new_negated", "INT(1)"}, + {"new_skipped", "INT(1)"} +} { } + +void PermissionActionLogger::log_permission_add_value( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t new_value, + bool new_negated, + bool new_skipped) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_ADD_VALUE, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, 0, false, false, new_value, new_negated, new_skipped); +} + +void PermissionActionLogger::log_permission_add_grant( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t new_value) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_ADD_GRANT, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, 0, false, false, new_value, false, false); +} + +void PermissionActionLogger::log_permission_edit_value( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t old_value, + bool old_negated, + bool old_skipped, + int32_t new_value, + bool new_negated, + bool new_skipped) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_EDIT_VALUE, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, old_negated, old_skipped, new_value, new_negated, new_skipped); +} + +void PermissionActionLogger::log_permission_edit_grant( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t old_value, + int32_t new_value) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_EDIT_GRANT, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, false, false, new_value, false, false); +} + +void PermissionActionLogger::log_permission_remove_value( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t old_value, + bool old_negated, + bool old_skipped) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_REMOVE_VALUE, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, old_negated, old_skipped, 0, false, false); +} + +void PermissionActionLogger::log_permission_remove_grant( + ServerId sid, + const std::shared_ptr& client, + PermissionTarget target, + uint64_t id1, const std::string& id1_name, + uint64_t id2, const std::string& id2_name, + const permission::PermissionTypeEntry& permission, + int32_t old_value) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::PERMISSION_REMOVE_GRANT, + }, kPermissionTargetName[(int) target], id1, id1_name, id2, id2_name, permission.name, old_value, false, false, 0, false, false); +} + +/* -------------------------- group logger -------------------------- */ +constexpr auto kTableLogsGroupCreateSqlite = R"( + CREATE TABLE `logs_groups` ( + `id` INTEGER PRIMARY KEY NOT NULL, + `timestamp` BIGINT, + `server_id` INTEGER, + `invoker_database_id` BIGINT, + `invoker_name` VARCHAR(128), + `action` VARCHAR(64), + `type` VARCHAR(64), + `target` VARCHAR(64), + `group` BIGINT, + `group_name` VARCHAR(128), + `source` BIGINT, + `source_name` VARCHAR(128) + ); +)"; + +bool GroupActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +GroupActionLogger::GroupActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_groups", + kDefaultHeaderFields, + {"type", "VARCHAR(64)"}, + {"target", "VARCHAR(64)"}, + {"group", "BIGINT"}, + {"group_name", "VARCHAR(128)"}, + {"source", "BIGINT"}, + {"source_name", "VARCHAR(128)"} +} { } + +void GroupActionLogger::log_group_create( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + GroupType type, + uint64_t gid, + const std::string &name, + uint64_t sgid, + const std::string &sname) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_CREATE, + }, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, sgid, sname); +} + +void GroupActionLogger::log_group_permission_copy( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + GroupType type, + uint64_t gid, + const std::string &name, + uint64_t sgid, + const std::string &sname) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_PERMISSION_COPY, + }, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, sgid, sname); +} + +void GroupActionLogger::log_group_rename( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + GroupType type, + uint64_t gid, + const std::string &name, + const std::string &sname) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_RENAME, + }, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, 0, sname); +} + +void GroupActionLogger::log_group_delete( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + GroupType type, + uint64_t gid, + const std::string &name) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_DELETE, + }, kGroupTargetName[(int) target], kGroupTypeName[(int) type], gid, name, 0, ""); +} + +/* -------------------------- group assignment logger -------------------------- */ +bool GroupAssignmentActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +GroupAssignmentActionLogger::GroupAssignmentActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_group_assignments", + kDefaultHeaderFields, + {"target", "VARCHAR(64)"}, + {"group", "BIGINT"}, + {"group_name", "VARCHAR(128)"}, + {"client", "BIGINT"}, + {"client_name", "VARCHAR(128)"} +} { } + +void GroupAssignmentActionLogger::log_group_assignment_add( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + uint64_t gid, + const std::string &gname, + uint64_t tclient, + const std::string &client_name) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_ASSIGNMENT_ADD, + }, kGroupTargetName[(int) target], gid, gname, tclient, client_name); +} + +void GroupAssignmentActionLogger::log_group_assignment_remove( + ts::ServerId sid, + const std::shared_ptr &client, + GroupTarget target, + uint64_t gid, + const std::string &gname, + uint64_t tclient, + const std::string &client_name) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(client), + .action = Action::GROUP_ASSIGNMENT_REMOVE, + }, kGroupTargetName[(int) target], gid, gname, tclient, client_name); +} + +/* -------------------------- group assignment logger -------------------------- */ +bool ClientChannelActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +ClientChannelActionLogger::ClientChannelActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_client_channel", + kDefaultHeaderFields, + {"target_client", "BIGINT"}, + {"target_client_name", "VARCHAR(128)"}, + {"source_channel", "BIGINT"}, + {"source_channel_name", "VARCHAR(128)"}, + {"target_channel", "BIGINT"}, + {"target_channel_name", "VARCHAR(128)"} +} { } + +void ClientChannelActionLogger::log_client_join( + ServerId sid, + const std::shared_ptr& subject, + uint64_t target_channel, + const std::string& target_channel_name) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = kInvokerSystem, + .action = Action::CLIENT_JOIN, + }, subject->getClientDatabaseId(), subject->getDisplayName(), 0, "", target_channel, target_channel_name); +} + +void ClientChannelActionLogger::log_client_leave( + ServerId sid, + const std::shared_ptr& subject, + uint64_t source_channel, + const std::string& source_channel_name) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = kInvokerSystem, + .action = Action::CLIENT_LEAVE, + }, subject->getClientDatabaseId(), subject->getDisplayName(), source_channel, source_channel_name, 0, ""); +} + +void ClientChannelActionLogger::log_client_move( + ServerId sid, + const std::shared_ptr &issuer, + const std::shared_ptr &subject, + uint64_t target_channel, const std::string &target_channel_name, uint64_t source_channel, const std::string &source_channel_name) { + + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::CLIENT_MOVE, + }, subject->getClientId(), subject->getDisplayName(), source_channel, source_channel_name, target_channel, target_channel_name); +} + +void ClientChannelActionLogger::log_client_kick( + ServerId sid, + const std::shared_ptr &issuer, + const std::shared_ptr &subject, + uint64_t target_channel, const std::string &target_channel_name, uint64_t source_channel, const std::string &source_channel_name) { + + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::CLIENT_KICK, + }, subject->getClientId(), subject->getDisplayName(), source_channel, source_channel_name, target_channel, target_channel_name); +} + +/* -------------------------- group assignment logger -------------------------- */ +bool ClientEditActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +ClientEditActionLogger::ClientEditActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_client_edit", + kDefaultHeaderFields, + {"target_client", "BIGINT"}, + {"target_client_name", "VARCHAR(128)"}, + {"property", "VARCHAR(128)"}, + {"old_value", "TEXT"}, + {"new_value", "TEXT"} +} { } + +void ClientEditActionLogger::log_client_edit( + ServerId sid, + const std::shared_ptr& issuer, + const std::shared_ptr& subject, + const property::PropertyDescription& property, + const std::string& old_value, + const std::string& new_value +) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::CLIENT_EDIT, + }, subject->getClientId(), subject->getDisplayName(), property.name, old_value, new_value); +} + +/* -------------------------- file transfer logger -------------------------- */ +bool FilesActionLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +FilesActionLogger::FilesActionLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_files", + kDefaultHeaderFields, + {"source_channel_id", "BIGINT"}, + {"source_path", "VARCHAR(256)"}, + {"target_channel_id", "BIGINT"}, + {"target_path", "VARCHAR(256)"} +} { } + +void FilesActionLogger::log_file_upload( + ServerId sid, + const std::shared_ptr &issuer, + uint64_t channel_id, + const std::string &path) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::FILE_UPLOAD, + }, 0, "", channel_id, path); +} + +void FilesActionLogger::log_file_download( + ServerId sid, + const std::shared_ptr &issuer, + uint64_t channel_id, + const std::string &path) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::FILE_DOWNLOAD, + }, channel_id, path, 0, ""); +} + +void FilesActionLogger::log_file_rename( + ServerId sid, + const std::shared_ptr &issuer, + uint64_t old_channel_id, + const std::string &old_name, + uint64_t mew_channel_id, + const std::string &new_name) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::FILE_RENAME, + }, old_channel_id, old_name, mew_channel_id, new_name); +} + +void FilesActionLogger::log_file_directory_create( + ServerId sid, + const std::shared_ptr &issuer, + uint64_t channel_id, + const std::string &path) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::FILE_DIRECTORY_CREATE, + }, 0, "", channel_id, path); +} + +void FilesActionLogger::log_file_delete( + ServerId sid, + const std::shared_ptr &issuer, + uint64_t channel_id, + const std::string &path) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::FILE_DELETE, + }, channel_id, path, 0, ""); +} + +/* -------------------------- custom logger -------------------------- */ +bool CustomLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +CustomLogger::CustomLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_custom", + kDefaultHeaderFields, + {"message", "TEXT"}, +} { } + +void CustomLogger::add_log_message( + ServerId sid, + const std::shared_ptr &issuer, + const std::string &message) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = sid, + .invoker = client_to_invoker(issuer), + .action = Action::CUSTOM_LOG, + }, message); +} + +#if 0 + + +//Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `result` + class QueryAuthenticateLogger : public TypedActionLogger { + public: + void log_query_authenticate( + ServerId /* server id */, + const std::shared_ptr& /* query */, + const std::string& /* username (if empty he logs out) */, + bool /* success */ + ); + }; + + //Table: `id`, `timestamp`, `server_id`, `invoker_database_id`, `invoker_name`, `action`, `ip`, `username`, `source_server` + class QueryServerLogger : public TypedActionLogger { + public: + void log_query_switch( + ServerId /* server id */, + const std::shared_ptr& /* query */, + const std::string& /* authenticateed username */, + ServerId /* source */ + ); + }; +#endif + +/* -------------------------- query authenticate logger -------------------------- */ +bool QueryAuthenticateLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +QueryAuthenticateLogger::QueryAuthenticateLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_query_authenticate", + kDefaultHeaderFields, + {"ip", "VARCHAR(64)"}, + {"username", "VARCHAR(128)"}, + {"result", "INT(1)"} +} { } + +void QueryAuthenticateLogger::log_query_authenticate( + ServerId server, + const std::shared_ptr &query, + const std::string &username, + QueryAuthenticateResult result) { + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = server, + .invoker = client_to_invoker(query), + .action = Action::QUERY_AUTHENTICATE, + }, query->getLoggingPeerIp(), username, kQueryAuthenticateResultName[(int) result]); +} + +/* -------------------------- query server logger -------------------------- */ +bool QueryServerLogger::setup(int version, std::string &error) { + if(!TypedActionLogger::setup(version, error)) + return false; + + switch (version) { + case 0: + case 1: + /* up to date, nothing to do */ + return true; + + default: + error = "invalid database source version"; + return false; + } +} + +QueryServerLogger::QueryServerLogger(ActionLogger* impl) : TypedActionLogger{ + impl, + "logs_query_server", + kDefaultHeaderFields, + {"ip", "VARCHAR(64)"}, + {"username", "VARCHAR(128)"}, + {"other_server", "INT"} +} { } + +void QueryServerLogger::log_query_switch(const std::shared_ptr &query, const std::string &username, ServerId source, ServerId target) { + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = source, + .invoker = client_to_invoker(query), + .action = Action::QUERY_LEAVE, + }, query->getLoggingPeerIp(), username, target); + + this->do_log({ + .timestamp = std::chrono::system_clock::now(), + .server_id = target, + .invoker = client_to_invoker(query), + .action = Action::QUERY_JOIN, + }, query->getLoggingPeerIp(), username, source); +} \ No newline at end of file diff --git a/shared b/shared index f11bee7..809fa82 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit f11bee75c19b437513154a9dc1c9167c72e94427 +Subproject commit 809fa82b3158c78ad9604ec576fe779688de13fa