diff --git a/server/main.cpp b/server/main.cpp index 041c08f..faa39fb 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -419,7 +419,7 @@ int main(int argc, char** argv) { } /* - * 2][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4096 name=\/icon_166694597 cid=0 cpw seekpos=0 proto=1 return_code= +[02][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4096 name=\/icon_166694597 cid=0 cpw seekpos=0 proto=1 return_code= [02][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4095 name=\/icon_4113966246 cid=0 cpw seekpos=0 proto=1 return_code= [02][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4094 name=\/icon_3002705295 cid=0 cpw seekpos=0 proto=1 return_code= [02][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4093 name=\/icon_494035633 cid=0 cpw seekpos=0 proto=1 return_code= diff --git a/server/src/Configuration.cpp b/server/src/Configuration.cpp index 99e771f..3641462 100644 --- a/server/src/Configuration.cpp +++ b/server/src/Configuration.cpp @@ -960,8 +960,8 @@ std::deque> config::create_bindings() { BIND_GROUP(web); { CREATE_BINDING("default_host", 0); - BIND_STRING(config::binding::DefaultWebHost, "0.0.0.0"); - ADD_NOTE("Multibinding like the voice server isnt supported yet!"); + BIND_STRING(config::binding::DefaultWebHost, "0.0.0.0,[::]"); + ADD_NOTE("Multibinding supported here! Host delimiter is \",\""); } } { diff --git a/server/src/DatabaseHelper.cpp b/server/src/DatabaseHelper.cpp index 77026eb..f3a8462 100644 --- a/server/src/DatabaseHelper.cpp +++ b/server/src/DatabaseHelper.cpp @@ -394,7 +394,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptrsql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":id", client_dbid}, - variable{":chId", 0}, + variable{":chId", update.channel_id}, variable{":type", permission::SQL_PERM_USER}, variable{":permId", permission_data->name}, @@ -1081,7 +1081,7 @@ std::shared_ptr DatabaseHelper::loadClientProperties(const std::shar } if(!prop.isModified()) return; - if((prop.type().flags & property::FLAG_SAVE) == 0 && (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0) { + if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) { logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")"); return; } diff --git a/server/src/Group.cpp b/server/src/Group.cpp index 0ad8965..8b55c81 100644 --- a/server/src/Group.cpp +++ b/server/src/Group.cpp @@ -502,6 +502,9 @@ std::deque GroupManager::update_server_group_propert } } + if(!changed.empty()) + client->join_state_id++; /* groups have changed :) */ + return changed; } diff --git a/server/src/TSServer.cpp b/server/src/TSServer.cpp index ece14c3..3dec645 100644 --- a/server/src/TSServer.cpp +++ b/server/src/TSServer.cpp @@ -372,26 +372,37 @@ bool TSServer::start(std::string& error) { } if(ts::config::web::activated && serverInstance->sslManager()->web_ssl_options()) { - string webHostname = this->properties()[property::VIRTUALSERVER_WEB_HOST]; - if(webHostname.empty()) webHostname = this->properties()[property::VIRTUALSERVER_HOST].as(); + string web_host_string = this->properties()[property::VIRTUALSERVER_WEB_HOST]; + if(web_host_string.empty()) + web_host_string = this->properties()[property::VIRTUALSERVER_HOST].as(); + + auto web_port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as(); + if(web_port == 0) + web_port = this->properties()[property::VIRTUALSERVER_PORT].as(); - sockaddr_in webAddress{}; - memset(&webAddress, 0, sizeof(webAddress)); - webAddress.sin_family = AF_INET; - if(!evaluateAddress4(webHostname, webAddress.sin_addr)) { - error = "could not resolve host (WebServer)"; - this->stop("Could not start web server"); - return false; - } - auto port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as(); - if(port == 0) port = this->properties()[property::VIRTUALSERVER_PORT].as(); - webAddress.sin_port = htons(port); - logMessage(this->serverId, "Starting web server on " + webHostname + ":" + to_string(port)); startTimestamp = std::chrono::system_clock::now(); - #ifdef COMPILE_WEB_CLIENT webControlServer = new WebControlServer(self.lock()); - if(!webControlServer->start(&webAddress, error)) { + + + auto web_bindings = net::resolve_bindings(web_host_string, web_port); + deque> bindings; + + for(auto& binding : web_bindings) { + if(!get<2>(binding).empty()) { + logError(this->serverId, "[Web] Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding)); + continue; + } + auto entry = make_shared(); + memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage)); + + entry->file_descriptor = -1; + entry->event_accept = nullptr; + bindings.push_back(entry); + } + + logMessage(this->serverId, "[Web] Starting server on {}:{}", web_host_string, web_port); + if(!webControlServer->start(bindings, error)) { error = "could not start web server. Message: " + error; this->stop("failed to start"); return false; @@ -735,7 +746,7 @@ void TSServer::broadcastMessage(std::shared_ptr invoker, std::s return; } this->forEachClient([&](shared_ptr cl){ - cl->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, invoker, 0, message); + cl->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, invoker, 0, 0, message); }); } diff --git a/server/src/channel/ClientChannelView.cpp b/server/src/channel/ClientChannelView.cpp index fbb8ca1..707f3dc 100644 --- a/server/src/channel/ClientChannelView.cpp +++ b/server/src/channel/ClientChannelView.cpp @@ -317,7 +317,6 @@ std::deque>> ClientChannelView::updat l_channel = l_channel->next; continue; /* all subchannels had been checked */ } else if(visible && !has_perm) { - //Test if tree comes invisible for(const auto& entry : this->test_channel(l_channel, l_own, cache)) result.emplace_back(false, entry); } diff --git a/server/src/channel/ClientChannelView.h b/server/src/channel/ClientChannelView.h index ea951bd..80a0d27 100644 --- a/server/src/channel/ClientChannelView.h +++ b/server/src/channel/ClientChannelView.h @@ -23,8 +23,9 @@ namespace ts { bool subscribed = false; bool editable = false; - bool _deleted = false; + bool joinable = false; /* used within notify text message */ ChannelId previous_channel = 0; + uint16_t join_state_id = 0; /* the calculation id for the flag joinable. If this does not match with the join_state_id within the client the flag needs to be recalculated */ std::weak_ptr handle; std::chrono::system_clock::time_point view_timestamp; private: diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index fbe4984..3c176ae 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -966,4 +966,55 @@ permission::v2::PermissionFlaggedValue ConnectedClient::calculate_permission_val auto value = this->permissionValue(permission::PERMTEST_ORDERED, permission, nullptr); return {value, value != permNotGranted}; +} + +#define RESULT(flag) \ +do { \ + ventry->join_state_id = this->join_state_id; \ + ventry->joinable = (flag); \ + return flag; \ +} while(0) + +bool ConnectedClient::calculate_and_get_join_state(const std::shared_ptr& channel) { + shared_ptr ventry; + { + shared_lock view_lock(this->channel_lock); + ventry = this->channel_view()->find_channel(channel); + if(!ventry) + return false; + } + if(ventry->join_state_id == this->join_state_id) + return ventry->joinable; + + auto permission_cache = make_shared(); + switch(channel->channelType()) { + case ChannelType::permanent: + if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_permanent, 1, channel, true, permission_cache)) + RESULT(false); + break; + case ChannelType::semipermanent: + if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_semi_permanent, 1, channel, true, permission_cache)) + RESULT(false); + break; + case ChannelType::temporary: + if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_temporary, 1, channel, true, permission_cache)) + RESULT(false); + break; + } + if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { + auto result = this->server->calculatePermissions2(this->getClientDatabaseId(), {permission::i_channel_join_power}, this->getType(), channel->channelId(), false, permission_cache); + if(result.empty()) + RESULT(false); + + if(!channel->permission_granted(permission::i_channel_needed_join_power, result.back().second, false)) + RESULT(false); + } + + auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache); + if (val != permNotGranted && val > 0) { + auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache); + if (st != 1) + RESULT(false); + } + RESULT(true); } \ No newline at end of file diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 2fc3aac..25e80f9 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -176,9 +176,9 @@ namespace ts { //Group manager chat virtual bool notifyClientChatComposing(const std::shared_ptr &); virtual bool notifyClientChatClosed(const std::shared_ptr &); - virtual bool notifyTextMessage(ChatMessageMode mode, const std::shared_ptr &sender, uint64_t targetId, const std::string &textMessage); + virtual bool notifyTextMessage(ChatMessageMode mode, const std::shared_ptr &sender, uint64_t targetId, ChannelId channel_id, const std::string &textMessage); inline void sendChannelMessage(const std::shared_ptr& sender, const std::string& textMessage){ - this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, this->currentChannel ? this->currentChannel->channelId() : 0, textMessage); + this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, this->currentChannel ? this->currentChannel->channelId() : 0, 0, textMessage); } //Group Client Groups virtual bool notifyServerGroupClientAdd(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &group); @@ -369,6 +369,8 @@ namespace ts { permission::PermissionValue channels_ignore_view = permNotGranted; bool subscribeToAll = false; + uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */ + bool calculate_and_get_join_state(const std::shared_ptr&); std::weak_ptr selectedBot; std::weak_ptr subscribed_bot; @@ -396,7 +398,7 @@ namespace ts { CommandResult handleCommandClientUpdate(Command&); CommandResult handleCommandClientEdit(Command&); CommandResult handleCommandClientEdit(Command&, const std::shared_ptr& /* target */); - CommandResult handleCommandClientMove(Command&); //TODO: Use cached permission values + CommandResult handleCommandClientMove(Command&); CommandResult handleCommandClientGetVariables(Command&); CommandResult handleCommandClientKick(Command&); CommandResult handleCommandClientPoke(Command&); diff --git a/server/src/client/ConnectedClientCommandHandler.cpp b/server/src/client/ConnectedClientCommandHandler.cpp index eb85b04..7d3ad7a 100644 --- a/server/src/client/ConnectedClientCommandHandler.cpp +++ b/server/src/client/ConnectedClientCommandHandler.cpp @@ -1186,7 +1186,8 @@ CommandResult ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { if(cl->update_cached_permissions()) cl->sendNeededPermissions(false); /* update the needed permissions */ - cl->updateChannelClientProperties(true, true); + cl->updateChannelClientProperties(false, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } }); } @@ -1236,7 +1237,8 @@ CommandResult ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - cl->updateChannelClientProperties(true, false); + cl->updateChannelClientProperties(false, false); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } }); } @@ -1311,17 +1313,18 @@ CommandResult ConnectedClient::handleCommandClientMove(Command &cmd) { if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { CHANNEL_PERMISSION_TEST(permission::i_channel_join_power, permission::i_channel_needed_join_power, channel, false); - - if (target_client == this) { - auto permission_cache_current = make_shared(); - auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache_current); - if (val != permNotGranted && val > 0) { - auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache_current); - if (st != 1) - return CommandResultPermissionError{permission::b_client_is_sticky}; - } - } } + + if (target_client == this) { + auto permission_cache_current = make_shared(); + auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache_current); + if (val != permNotGranted && val > 0) { + auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache_current); + if (st != 1) + return CommandResultPermissionError{permission::b_client_is_sticky}; + } + } + if (target_client != this) { PERM_CHECK_CHANNELR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, target_client->getChannel()), target_client->getChannel(), true); PERM_CHECK_CHANNEL_CR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, channel), channel, true, permission_cache); @@ -2394,29 +2397,31 @@ CommandResult ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { for(const auto& client : this->server->getClientsByChannel(channel)) { /* let them lock the server channel tree as well (read lock so does not matter) */ client->updateChannelClientProperties(true, true); + client->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } - if(update_view && this->server) - this->server->forEachClient([&](const shared_ptr& cl) { - /* server tree read lock still active */ - auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId()); - auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); - sassert(l_source); - if(cl->currentChannel) sassert(l_target); + if(update_view && this->server) { + auto l_source = this->server->channelTree->findLinkedChannel(channel->channelId()); + this->server->forEachClient([&](const shared_ptr& cl) { + /* server tree read lock still active */ + auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); + sassert(l_source); + if(cl->currentChannel) sassert(l_target); - { - unique_lock client_channel_lock(cl->channel_lock); + { + unique_lock client_channel_lock(cl->channel_lock); - deque deleted; - for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { - if(update_entry.first) - cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); - else deleted.push_back(update_entry.second->channelId()); - } - if(!deleted.empty()) - cl->notifyChannelHide(deleted, false); - } - }); + deque deleted; + for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { + if(update_entry.first) + cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + cl->notifyChannelHide(deleted, false); + } + }); + } return command_result; } @@ -2468,8 +2473,10 @@ CommandResult ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { if(updateClients && this->server) this->server->forEachClient([&](std::shared_ptr cl) { - if(cl->currentChannel == channel) + if(cl->currentChannel == channel) { cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } }); if(update_view && this->server) { this->server->forEachClient([&](std::shared_ptr cl) { @@ -2866,6 +2873,7 @@ CommandResult ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } }); }).detach(); @@ -2935,6 +2943,7 @@ CommandResult ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } }); }).detach(); @@ -3021,10 +3030,13 @@ CommandResult ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& server->forEachClient([groups, checkTp](shared_ptr cl) { for(const auto& serverGroup : groups) { if (cl->serverGroupAssigned(serverGroup)) { - if(cl->update_cached_permissions()) /* update cached calculated permissions */ + if(cl->update_cached_permissions()) {/* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if (checkTp) - cl->updateChannelClientProperties(true, true); + } + if (checkTp) { + cl->updateChannelClientProperties(true, true); + } + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */ break; } } @@ -3104,6 +3116,7 @@ CommandResult ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ break; } } @@ -3228,25 +3241,57 @@ CommandResult ConnectedClient::handleCommandSendTextMessage(Command &cmd) { } if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target)) return CommandResult::Success; - target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), cmd["msg"].string()); - this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), cmd["msg"].string()); + target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, cmd["msg"].string()); + this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_CHANNEL) { - CMD_REQ_CHANNEL; CACHED_PERM_CHECK(permission::b_client_channel_textmessage_send, 1, false); + if(!cmd[0].has("cid")) + cmd["cid"] = 0; + RESOLVE_CHANNEL_R(cmd["cid"], false); + auto channel = l_channel ? dynamic_pointer_cast(l_channel->entry) : nullptr; + if(!channel) { + CMD_REQ_CHANNEL; + channel = this->currentChannel; + channel_id = this->currentChannel->channelId(); - if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) return CommandResult::Success; - for (auto &cl : this->server->getClientsByChannel(this->currentChannel)) - cl->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this.lock(), this->getClientId(), cmd["msg"].string()); + if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) + return CommandResult::Success; + } + auto message = cmd["msg"].string(); + auto _this = this->_this.lock(); + auto client_id = this->getClientId(); + + auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as(); + for(const auto& client : this->server->getClients()) { + if(client->connectionState() != ConnectionState::CONNECTED) + continue; + + auto type = client->getType(); + if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) + continue; + + auto own_channel = client->currentChannel == this->currentChannel; + if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) { + if(!own_channel && &*client != this) { + if(flag_password) + continue; /* TODO: Send notification about new message. The client then could request messages via message history */ + + if(!client->calculate_and_get_join_state(channel)) + continue; + } + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this, client_id, channel_id, message); + } + } auto conversations = this->server->conversation_manager(); - auto conversation = conversations->get_or_create(this->currentChannel->channelId()); + auto conversation = conversations->get_or_create(channel->channelId()); conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) { CACHED_PERM_CHECK(permission::b_client_server_textmessage_send, 1); if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return CommandResult::Success; this->server->forEachClient([&](shared_ptr client) { - client->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, _this.lock(), this->getClientId(), cmd["msg"].string()); + client->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, _this.lock(), this->getClientId(), 0, cmd["msg"].string()); }); } else return {findError("parameter_invalid"), "invalid target mode"}; @@ -4568,7 +4613,7 @@ CommandResult ConnectedClient::handleCommandClientMute(Command &cmd) { } if (config::voice::notifyMuted) - client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), config::messages::mute_notify_message); + client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, config::messages::mute_notify_message); return CommandResult::Success; } @@ -4589,7 +4634,7 @@ CommandResult ConnectedClient::handleCommandClientUnmute(Command &cmd) { } if (config::voice::notifyMuted) - client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), config::messages::unmute_notify_message); + client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, config::messages::unmute_notify_message); return CommandResult::Success; } @@ -4879,6 +4924,7 @@ CommandResult ConnectedClient::handleCommandClientAddPerm(Command &cmd) { elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channels) elm->updateChannelClientProperties(true, true); + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } return CommandResult::Success; @@ -4917,6 +4963,7 @@ CommandResult ConnectedClient::handleCommandClientDelPerm(Command &cmd) { elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channel) elm->updateChannelClientProperties(true, true); + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } return CommandResult::Success; } @@ -4938,9 +4985,9 @@ CommandResult ConnectedClient::handleCommandChannelClientPermList(Command &cmd) CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_channelclient_permission_list, 1, true); - - std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); - if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return {ErrorType::VSError}; if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid client id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"].as()); @@ -4987,21 +5034,21 @@ CommandResult ConnectedClient::handleCommandChannelClientPermList(Command &cmd) return CommandResult::Success; } -//TODO: Update this specific channel visibility? CommandResult ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) { - CMD_REQ_SERVER; + CMD_REF_SERVER(server_ref); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("parameter_invalid"), "Invalid manager db id"}; - std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); - if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); + PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); - auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); - PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return {ErrorType::VSError}; bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); - bool conOnError = cmd[0].has("continueonerror"); + bool conOnError = cmd[0].has("continueonerror"), update_view = false; auto cll = this->server->findClientsByCldbId(cmd["cldbid"]); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); @@ -5013,27 +5060,42 @@ CommandResult ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) { mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); } else { mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); + update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; } } if (!cll.empty()) { - for (const auto &cl : cll) { - if(cl->update_cached_permissions()) /* update cached calculated permissions */ - cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - } + for (const auto &elm : cll) { + if(elm->update_cached_permissions()) /* update cached calculated permissions */ + elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - for (const auto &elm : cll) - if (elm->currentChannel == channel) { + if(elm->currentChannel == channel) { elm->updateChannelClientProperties(true, true); + } else if(update_view) { + unique_lock client_channel_lock(this->channel_lock); + + auto elm_channel = elm->currentChannel; + if(elm_channel) { + deque deleted; + for(const auto& update_entry : elm->channels->update_channel_path(l_channel, this->server->channelTree->findLinkedChannel(elm->currentChannel->channelId()))) { + if(update_entry.first) + elm->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + elm->notifyChannelHide(deleted, false); /* we've locked the tree before */ + } } + + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } } return CommandResult::Success; } -//TODO: Update this specific channel visibility? CommandResult ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { - CMD_REQ_SERVER; + CMD_REF_SERVER(server_ref); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("parameter_invalid"), "Invalid manager db id"}; @@ -5041,13 +5103,16 @@ CommandResult ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); - std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); - if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return {ErrorType::VSError}; + auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto onlineClientInstances = this->server->findClientsByCldbId(cmd["cldbid"]); + bool update_view = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); @@ -5063,6 +5128,7 @@ CommandResult ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); } else { mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); + update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; } } if (!onlineClientInstances.empty()) @@ -5070,9 +5136,24 @@ CommandResult ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { if (elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ - if (elm->currentChannel == channel) { + if(elm->currentChannel == channel) { elm->updateChannelClientProperties(true, true); + } else if(update_view) { + unique_lock client_channel_lock(this->channel_lock); + + auto elm_channel = elm->currentChannel; + if(elm_channel) { + deque deleted; + for(const auto& update_entry : elm->channels->update_channel_path(l_channel, this->server->channelTree->findLinkedChannel(elm->currentChannel->channelId()))) { + if(update_entry.first) + elm->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + elm->notifyChannelHide(deleted, false); /* we've locked the tree before */ + } } + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ } return CommandResult::Success; @@ -7337,12 +7418,14 @@ CommandResult ConnectedClient::handleCommandConversationHistory(ts::Command &com size_t index = 0; size_t length = 0; bool merge = command.hasParm("merge"); - for(auto& message : messages) { + + for(auto it = messages.rbegin(); it != messages.rend(); it++) { if(index == 0) { notify[index]["cid"] = conversation_id; notify[index]["flag_volatile"] = conversation->volatile_only(); } + auto& message = *it; notify[index]["timestamp"] = duration_cast(message->message_timestamp.time_since_epoch()).count(); notify[index]["sender_database_id"] = message->sender_database_id; notify[index]["sender_unique_id"] = message->sender_unique_id; diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index 6bc7708..22d4cfc 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -178,13 +178,15 @@ bool ConnectedClient::notifyChannelGroupList() { return true; } -bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, uint64_t targetId, const string &textMessage) { +bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &invoker, uint64_t targetId, ChannelId channel_id, const string &textMessage) { //notifytextmessage targetmode=1 msg=asdasd target=2 invokerid=1 invokername=WolverinDEV invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= Command cmd("notifytextmessage"); INVOKER(cmd, invoker); cmd["targetmode"] = mode; cmd["target"] = targetId; cmd["msg"] = textMessage; + if(this->getType() != ClientType::CLIENT_TEAMSPEAK) + cmd["cid"] = channel_id; this->sendCommand(cmd); return true; } diff --git a/server/src/client/ConnectedClientTextCommandHandler.cpp b/server/src/client/ConnectedClientTextCommandHandler.cpp index e60837f..ea35ab6 100644 --- a/server/src/client/ConnectedClientTextCommandHandler.cpp +++ b/server/src/client/ConnectedClientTextCommandHandler.cpp @@ -122,15 +122,15 @@ bool ConnectedClient::handleTextMessage(ChatMessageMode mode, std::string text, handle_text_command_fn_t function; if(mode == ChatMessageMode::TEXTMODE_SERVER) { function = [&](const shared_ptr& sender, const string& message) { - this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, sender, 0, message); + this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, sender, 0, 0, message); }; } else if(mode == ChatMessageMode::TEXTMODE_CHANNEL) { function = [&](const shared_ptr& sender, const string& message) { - this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, 0, message); + this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, 0, 0, message); }; } else if(mode == ChatMessageMode::TEXTMODE_PRIVATE) { function = [&, target](const shared_ptr& sender, const string& message) { - this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, target, this->getClientId(), message); + this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, target, this->getClientId(), 0, message); }; } diff --git a/server/src/client/query/QueryClient.h b/server/src/client/query/QueryClient.h index d430c7c..9c64986 100644 --- a/server/src/client/query/QueryClient.h +++ b/server/src/client/query/QueryClient.h @@ -102,7 +102,7 @@ namespace ts { bool notifyPluginCmd(std::string name, std::string msg,std::shared_ptr) override; bool notifyClientChatComposing(const std::shared_ptr &ptr) override; bool notifyClientChatClosed(const std::shared_ptr &ptr) override; - bool notifyTextMessage(ChatMessageMode mode, const std::shared_ptr &sender, uint64_t targetId, const std::string &textMessage) override; + bool notifyTextMessage(ChatMessageMode mode, const std::shared_ptr &sender, uint64_t targetId, ChannelId channel_id, const std::string &textMessage) override; bool notifyServerGroupClientAdd(const std::shared_ptr &invoker, const std::shared_ptr &client, const std::shared_ptr &group) override; bool notifyServerGroupClientRemove(std::shared_ptr invoker, std::shared_ptr client, std::shared_ptr group) override; diff --git a/server/src/client/query/QueryClientNotify.cpp b/server/src/client/query/QueryClientNotify.cpp index a0e64eb..b4ba516 100644 --- a/server/src/client/query/QueryClientNotify.cpp +++ b/server/src/client/query/QueryClientNotify.cpp @@ -67,11 +67,11 @@ bool QueryClient::notifyClientChatClosed(const shared_ptr &ptr) return ConnectedClient::notifyClientChatClosed(ptr); } -bool QueryClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &sender, uint64_t targetId, const string &textMessage) { +bool QueryClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr &sender, uint64_t targetId, ChannelId channel_id, const string &textMessage) { if(mode == ChatMessageMode::TEXTMODE_PRIVATE) CHK_EVENT(QEVENTGROUP_CHAT, QEVENTSPECIFIER_CHAT_MESSAGE_PRIVATE); else if(mode == ChatMessageMode::TEXTMODE_CHANNEL) CHK_EVENT(QEVENTGROUP_CHAT, QEVENTSPECIFIER_CHAT_MESSAGE_CHANNEL); else if(mode == ChatMessageMode::TEXTMODE_SERVER) CHK_EVENT(QEVENTGROUP_CHAT, QEVENTSPECIFIER_CHAT_MESSAGE_SERVER); - return ConnectedClient::notifyTextMessage(mode, sender, targetId, textMessage); + return ConnectedClient::notifyTextMessage(mode, sender, targetId, channel_id, textMessage); } bool QueryClient::notifyServerGroupClientAdd(const shared_ptr &invoker, const shared_ptr &client, const shared_ptr &group) { diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index c7018ad..848f065 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -22,11 +22,10 @@ using namespace ts; using namespace ts::server; using namespace ts::protocol; -WebClient::WebClient(WebControlServer* server, int fd, const sockaddr_in& addr) : SpeakingClient(server->getTS()->getSql(), server->getTS()), handle(server) { +WebClient::WebClient(WebControlServer* server, int fd) : SpeakingClient(server->getTS()->getSql(), server->getTS()), handle(server) { memtrack::allocated(this); assert(server->getTS()); - memcpy(&this->remote_address, &addr, sizeof(sockaddr_in)); this->state = ConnectionState::INIT_LOW; this->file_descriptor = fd; } diff --git a/server/src/client/web/WebClient.h b/server/src/client/web/WebClient.h index 1c20ccf..3264011 100644 --- a/server/src/client/web/WebClient.h +++ b/server/src/client/web/WebClient.h @@ -21,7 +21,7 @@ namespace ts { class WebClient : public SpeakingClient { friend class WebControlServer; public: - WebClient(WebControlServer*, int socketFd, const sockaddr_in&); + WebClient(WebControlServer*, int socketFd); ~WebClient() override; void sendJson(const Json::Value&); diff --git a/server/src/manager/ConversationManager.cpp b/server/src/manager/ConversationManager.cpp index 9bfcc97..ce87453 100644 --- a/server/src/manager/ConversationManager.cpp +++ b/server/src/manager/ConversationManager.cpp @@ -1025,6 +1025,9 @@ std::deque> Conversation::message_history(con for(auto it = this->_last_messages.rbegin(); it != this->_last_messages.rend(); it++) { if((*it)->message_timestamp > end_timestamp) /* message has been send after the search timestamp */ continue; + if(begin_timestamp.time_since_epoch().count() != 0 && (*it)->message_timestamp < begin_timestamp) + return result; + result.push_back(*it); if(--message_count == 0) return result; @@ -1102,6 +1105,7 @@ std::deque> Conversation::message_history(con if(begin_timestamp.time_since_epoch().count() != 0 && rmid->timestamp < begin_timestamp) return result; + if(rmid->timestamp >= timestamp) continue; /* for some reason we got a message from the index of before where we are. This could happen for "orphaned" blocks which point to a valid block within the future block */ /* diff --git a/server/src/server/WebServer.cpp b/server/src/server/WebServer.cpp index 417a39b..524ee52 100644 --- a/server/src/server/WebServer.cpp +++ b/server/src/server/WebServer.cpp @@ -18,70 +18,168 @@ using namespace ts::server; WebControlServer::WebControlServer(const std::shared_ptr& handle) : handle(handle) {} WebControlServer::~WebControlServer() = default; -bool WebControlServer::start(sockaddr_in* addr, std::string& errorMessage) { - if(this->running()) return false; - this->_running = true; - boundAddress = new sockaddr_in; - memcpy(boundAddress, addr, sizeof(*addr)); +bool WebControlServer::start(const std::deque>& bindings, std::string& error) { + if(this->running()) { + error = "server already running"; + return false; + } + this->_running = true; - this->serverSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); - if (serverSocket < 0) { - logCritical(this->handle->getServerId(), "Cant create server socket for server"); - return false; - } + /* reserve backup file descriptor in case that the max file descriptors have been reached */ + { + this->server_reserve_fd = dup(1); + if(this->server_reserve_fd < 0) + logWarning(this->handle->getServerId(), "Failed to reserve a backup accept file descriptor. ({} | {})", errno, strerror(errno)); + } - int enable = 1; - int disabled = 0; - if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) - logError("setsockopt(SO_REUSEADDR) failed"); - if(setsockopt(serverSocket, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) - logError("Cant disable nopush! Error: "+to_string(errno)+" / "+strerror(errno)); - setsockopt(serverSocket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + { + for(auto& binding : bindings) { + binding->file_descriptor = socket(binding->address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0); + if(binding->file_descriptor < 0) { + logError(this->handle->getServerId(), "[Web] Failed to bind server to {}. (Failed to create socket: {} | {})", binding->as_string(), errno, strerror(errno)); + continue; + } - if(fcntl(serverSocket, F_SETFD, FD_CLOEXEC) < 0) - logError(this->handle->getServerId(), "Failed to enable FD_CLOEXEC for {} (WebControlServer)", serverSocket); + int enable = 1, disabled = 0; - if (bind(serverSocket, (struct sockaddr *) boundAddress, sizeof(*boundAddress)) < 0) { - errorMessage = string() + "Cant bind server socket (" + strerror(errno) + ")"; - return false; - } - if(listen(serverSocket, 255) < 0){ - errorMessage = string() + "Cant listen on server socket (" + strerror(errno) + ")"; - return false; - } + if (setsockopt(binding->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + logWarning(this->handle->getServerId(), "[Web] Failed to activate SO_REUSEADDR for binding {} ({} | {})", binding->as_string(), errno, strerror(errno)); + if(setsockopt(binding->file_descriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) + logWarning(this->handle->getServerId(), "[Web] Failed to deactivate TCP_NOPUSH for binding {} ({} | {})", binding->as_string(), errno, strerror(errno)); + if(binding->address.ss_family == AF_INET6) { + if(setsockopt(binding->file_descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(int)) < 0) + logWarning(this->handle->getServerId(), "[Web] Failed to activate IPV6_V6ONLY for IPv6 binding {} ({} | {})", binding->as_string(), errno, strerror(errno)); + } + if(fcntl(binding->file_descriptor, F_SETFD, FD_CLOEXEC) < 0) + logWarning(this->handle->getServerId(), "[Web] Failed to set flag FD_CLOEXEC for binding {} ({} | {})", binding->as_string(), errno, strerror(errno)); - auto io_base = serverInstance->getWebIoLoop()->next_loop(); - assert(io_base); - this->acceptEvent = event_new(io_base->loop, this->serverSocket, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((WebControlServer*) c)->onClientAccept(a, b, c); }, this); - event_add(this->acceptEvent, nullptr); - return true; + if (bind(binding->file_descriptor, (struct sockaddr *) &binding->address, sizeof(binding->address)) < 0) { + logError(this->handle->getServerId(), "[Web] Failed to bind server to {}. (Failed to bind socket: {} | {})", binding->as_string(), errno, strerror(errno)); + close(binding->file_descriptor); + continue; + } + + if (listen(binding->file_descriptor, SOMAXCONN) < 0) { + logError(this->handle->getServerId(), "[Web] Failed to bind server to {}. (Failed to listen: {} | {})", binding->as_string(), errno, strerror(errno)); + close(binding->file_descriptor); + continue; + } + + + auto io_base = serverInstance->getWebIoLoop()->next_loop(); + assert(io_base); + binding->event_accept = event_new(io_base->loop, binding->file_descriptor, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((WebControlServer *) c)->on_client_receive(a, b, c); }, this); + event_add(binding->event_accept, nullptr); + this->bindings.push_back(binding); + } + + if(this->bindings.empty()) { + this->stop(); + error = "failed to bind to any address"; + return false; + } + } + return true; } -void WebControlServer::onClientAccept(int fd, short ev, void *arg) { - sockaddr_in remoteAddr{}; - memset(&remoteAddr, 0, sizeof(sockaddr_in)); - socklen_t addrLength = sizeof(remoteAddr); +#define CLOSE_CONNECTION \ +if(shutdown(file_descriptor, SHUT_RDWR) < 0) { \ + debugMessage(LOG_FT, "[{}] Failed to shutdown socket ({} | {}).", logging_address(remote_address), errno, strerror(errno)); \ +} \ +if(close(file_descriptor) < 0) { \ + debugMessage(LOG_FT, "[{}] Failed to close socket ({} | {}).", logging_address(remote_address), errno, strerror(errno)); \ +} - int acceptedSocketFd = accept(this->serverSocket, (struct sockaddr *) &remoteAddr, &addrLength); - if (acceptedSocketFd < 0) { - if(errno == EAGAIN) //No manager - return; +inline std::string logging_address(const sockaddr_storage& address) { + if(config::server::disable_ip_saving) + return "X.X.X.X" + to_string(net::port(address)); + return net::to_string(address, true); +} - logError(this->handle->getServerId(), lstream << "Having an error while accepting a new manager. ("+to_string(errno)+"/"+string(strerror(errno))+")"); - return; - } +void WebControlServer::on_client_receive(int _server_file_descriptor, short ev, void *arg) { + sockaddr_storage remote_address{}; + memset(&remote_address, 0, sizeof(remote_address)); + socklen_t address_length = sizeof(remote_address); - shared_ptr client = std::make_shared(this, acceptedSocketFd, remoteAddr); - client->applySelfLock(client); - client->initialize(); - memcpy(&client->remote_address, &remoteAddr, sizeof(sockaddr_in)); + int file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length); + if (file_descriptor < 0) { + if(errno == EAGAIN) + return; - this->clientLock.lock(); - this->clients.push_back(client); - this->clientLock.unlock(); - event_add(client->readEvent, nullptr); - logMessage(this->handle->getServerId(), lstream << "[Web] Got new manager from " << client->getLoggingPeerIp() << ":" << client->getPeerPort() << endl); + if(errno == EMFILE || errno == ENFILE) { + if(errno == EMFILE) + logError(this->handle->getServerId(), "[Web] Server ran out file descriptors. Please increase the process file descriptor limit."); + else + logError(this->handle->getServerId(), "[Web] Server ran out file descriptors. Please increase the process and system-wide file descriptor limit."); + + bool tmp_close_success = false; + { + lock_guard reserve_fd_lock(server_reserve_fd_lock); + if(this->server_reserve_fd > 0) { + debugMessage(this->handle->getServerId(), "[Web] Trying to accept client with the reserved file descriptor to close the incomming connection."); + auto _ = [&]{ + if(close(this->server_reserve_fd) < 0) { + debugMessage(this->handle->getServerId(), "[Web] Failed to close reserved file descriptor"); + tmp_close_success = false; + return; + } + this->server_reserve_fd = 0; + + errno = 0; + file_descriptor = accept(_server_file_descriptor, (struct sockaddr *) &remote_address, &address_length); + if(file_descriptor < 0) { + if(errno == EMFILE || errno == ENFILE) + debugMessage(this->handle->getServerId(), "[Web] [{}] Even with freeing the reserved descriptor accept failed. Attempting to reclaim reserved file descriptor", logging_address(remote_address)); + else if(errno == EAGAIN); + else { + debugMessage(this->handle->getServerId(), "[Web] [{}] Failed to accept client with reserved file descriptor. ({} | {})", logging_address(remote_address), errno, strerror(errno)); + } + this->server_reserve_fd = dup(1); + if(this->server_reserve_fd < 0) + debugMessage(this->handle->getServerId(), "[Web] [{}] Failed to reclaim reserved file descriptor. Future clients cant be accepted!", logging_address(remote_address)); + else + tmp_close_success = true; + return; + } + debugMessage(this->handle->getServerId(), "[Web] [{}] Successfully accepted client via reserved descriptor (fd: {}). Disconnecting client.", logging_address(remote_address), file_descriptor); + + CLOSE_CONNECTION + this->server_reserve_fd = dup(1); + if(this->server_reserve_fd < 0) + debugMessage(this->handle->getServerId(), "[Web] Failed to reclaim reserved file descriptor. Future clients cant be accepted!"); + else + tmp_close_success = true; + logMessage(this->handle->getServerId(), "[Web] [{}] Dropping file transfer connection attempt because of too many open file descriptors.", logging_address(remote_address)); + }; + _(); + } + } + + if(!tmp_close_success) { + debugMessage(this->handle->getServerId(), "[Web] Sleeping two seconds because we're currently having no resources for this user. (Removing the accept event)"); + for(auto& binding : this->bindings) + event_del_noblock(binding->event_accept); + accept_event_deleted = system_clock::now(); + return; + } + return; + } + logMessage(this->handle->getServerId(), "[Web] Got an error while accepting a new client. (errno: {}, message: {})", errno, strerror(errno)); + return; + } + + + shared_ptr client = std::make_shared(this, file_descriptor); + memcpy(&client->remote_address, &remote_address, sizeof(remote_address)); + client->applySelfLock(client); + client->initialize(); + + this->clientLock.lock(); + this->clients.push_back(client); + this->clientLock.unlock(); + event_add(client->readEvent, nullptr); + logMessage(this->handle->getServerId(), "[Web] Got new client from {}:{}", client->getLoggingPeerIp(), client->getPeerPort()); } void WebControlServer::stop() { @@ -94,19 +192,28 @@ void WebControlServer::stop() { e->closeConnection(system_clock::now()); } - if(this->acceptEvent) { - event_del(this->acceptEvent); - event_free(this->acceptEvent); + + for(auto& binding : this->bindings) { + if(binding->event_accept) { + event_del_block(binding->event_accept); + event_free(binding->event_accept); + binding->event_accept = nullptr; + } + if(binding->file_descriptor > 0) { + if(shutdown(binding->file_descriptor, SHUT_RDWR) < 0) + logWarning(this->handle->getServerId(), "[Web] Failed to shutdown socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno)); + if(close(binding->file_descriptor) < 0) + logError(this->handle->getServerId(), "[Web] Failed to close socket for binding {} ({} | {}).", binding->as_string(), errno, strerror(errno)); + binding->file_descriptor = -1; + } } + this->bindings.clear(); - if(this->serverSocket > 0){ - if(shutdown(this->serverSocket, SHUT_RDWR) < 0) logError(this->handle->getServerId(), "Could not shutdown WebSocketServer socket!"); - if(close(this->serverSocket) < 0) logError(this->handle->getServerId(), "Could not close WebSocketServer socket!"); - } - this->serverSocket = -1; - - delete this->boundAddress; - this->boundAddress = nullptr; + if(this->server_reserve_fd > 0) { + if(close(this->server_reserve_fd) < 0) + logError(this->handle->getServerId(), "[Web] Failed to close backup file descriptor ({} | {})", errno, strerror(errno)); + } + this->server_reserve_fd = -1; } void WebControlServer::unregisterConnection(const std::shared_ptr& connection) { diff --git a/server/src/server/WebServer.h b/server/src/server/WebServer.h index f742ea9..92f5fae 100644 --- a/server/src/server/WebServer.h +++ b/server/src/server/WebServer.h @@ -3,21 +3,29 @@ #include #include #include +#include namespace ts { namespace server { class TSServer; class WebClient; - class WebRTCServer; class WebControlServer { friend class WebClient; public: + struct Binding { + sockaddr_storage address{}; + int file_descriptor = 0; + ::event* event_accept = nullptr; + + inline std::string as_string() { return net::to_string(address, true); } + }; + explicit WebControlServer(const std::shared_ptr&); ~WebControlServer(); - bool start(sockaddr_in*, std::string&); - inline bool running(){ return _running; } + bool start(const std::deque>& /* bindings */, std::string& /* error */); + inline bool running(){ return _running; } void stop(); std::shared_ptr getTS(){ return this->handle; } @@ -34,11 +42,15 @@ namespace ts { std::deque> clients; bool _running = false; - sockaddr_in* boundAddress = nullptr; - int serverSocket = 0; - ::event* acceptEvent = nullptr; + std::deque> bindings; + + std::mutex server_reserve_fd_lock; + int server_reserve_fd = -1; /* -1 = unset | 0 = in use | > 0 ready to use */ + + //IO stuff + std::chrono::system_clock::time_point accept_event_deleted; private: - void onClientAccept(int fd, short ev, void *arg); + void on_client_receive(int fd, short ev, void *arg); void unregisterConnection(const std::shared_ptr&); }; } diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index 9ac9154..d4f1e48 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -223,7 +223,7 @@ namespace terminal { return false; } for(const auto &cl : server->getClientsByChannel(channel)) - cl->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), cl->getClientId(), message); + cl->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), cl->getClientId(), 0, message); } break; case ChatMessageMode::TEXTMODE_PRIVATE: @@ -233,7 +233,7 @@ namespace terminal { logError("Cloud not find manager from clid"); return false; } - client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), client->getClientId(), message); + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), client->getClientId(), 0, message); } break; default: diff --git a/shared b/shared index a0cca36..9f2181c 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit a0cca36eca11da410626a340dbe4377067d59c1b +Subproject commit 9f2181c18b4579de2957347c3b788f81a4de987a