diff --git a/MusicBot/Protocol.md b/MusicBot/Protocol.md deleted file mode 100644 index 8aa1bad..0000000 --- a/MusicBot/Protocol.md +++ /dev/null @@ -1,249 +0,0 @@ -# Musik bot websocket protocol -## General structure -Transmitted data is in json format -The json format has the structure as following: -``` -{ - "type": , - "data": [ - { - : value - }, - ... - ] -} -``` -Example: -``` -{ - "type": "showMessage", - "data": [ - { - "message": "A simple info modal", - "type": "info" - }, - { - "message": "A simple error modal", - "type": "error" - } - ] -} -``` -## TODO list -* Music bot queue -* Music bot ts3 access rights (allow other clients to use this music bot etc.) - -## Packet types -### General types -#### Server `showMessage`: -This packet should show up a message modal. -* **[~]** - * `message`: - * `type`: {info|error} - - -#### Server `reqError`: -The server sends this if you applay a invalid request -* **[1]** - * `message`: - * `requestId`: - - -#### Server `disconnect` -I send this packet before i close the comunication -* **[1]** - * `message` - -___ -### Login packets -#### Client `login` -Try login -* **[1]** - * `username` - * `password` - * `requestId` - - -#### Server `notifylogin` -* **[1]** - * `requestId` - * `succeeded`: {0|1} - * `uid` own uid. only set if login failed - * `message` only set if login failed - -#### Client `logout` -* **[1]** - * `requestId` - -#### Server `notifylogout` -Could be send at any time (force logout) -* **[1]** - * `requestId` (empty of not requested) - * `succeeded`: {0|1} - -___ -### Server Management -#### Client `serverlist` -* **[1]** - * `requestId` - -#### Server `notifyserverlist` -Sends when requested or list updated -(Lists online avariable server for the client view) -* **[~]** - * [1] `requestId` (empty of not requested) - * `name` - * `uid` - * `serverId` - * `status`: {online|offline} - * `clientOnline` - * `maxClients` - -#### Server `notifyserverupdate` -Sends when a server changes display properties -* **[1]** - * `serverId` - * `key`: {name|onlineClients|maxClients} - * `value` - -___ -### Channel Management -#### Client `channellist` -Request a channel list -* **[1]** - * `requestId` - * `serverId` - -#### Server `notifychannellist` -The channel response is ordered: -This packet would also be send if the channel tree gets updated -``` -root -- sub 1 - - sub sub 1 - - sub sub 2 -- sub 2 -root 2 -... -``` -* **[~]** - * [1] `requestId` (empty of not requested) - * [1] `serverId` - * `name` - * `channelId` - * `channelParent` - * `channelOrder` - -___ -### Music bot management -#### Client `musicbotlist` -* **[1]** - * `requestId` - * `serverId` - -#### Server `notifymusikmusicbotlist` -* **[~]** - * [1] `requestId` (empty of not requested) - * [1] `serverId` - * `id` - * `connected`: {1|0} - * `name` - * `channelId` - * `ownerUid` (its your own if its matching with our own id) - * `ownerCldbid` - -#### Client `musicbotcreate` -* **[1]** - * `requestId` - * `serverId` - * `name` - * `channelId` - -#### Server `notifymusikbotcreated` -* **[1]** - * `requestId` (empty of not requested) - * `serverId` - * `id` - * `connected`: {1|0} - * `name` - * `channelId` - * `ownerUid` (its your own if its matching with our own id) - * `ownerCldbid` - -#### Client `musicbotdelete` -* **[1]** - * `requestId` - * `serverId` - * `name` - * `channelId` - -#### Server `notifymusikbotdelete` -* **[1]** - * `requestId` (empty of not requested) - * `serverId` - * `id` - -#### Client `musicbotinfo` -Request music bot info -* **[1]** - * `requestId` - * `serverId` - * `id` - -#### Server `notifymusicbotinfo` -* **[1]** - * `requestId` - * `serverId` - * `id` - * `name` - * `connected` - * `phoeticName` - * `channelId` - * `playing` - * `playingInfo`: Empty if noting selected - * `description` - * `textCurrentSong` - -#### Client `musicbotedit` -* **[1]** - * `requestId` - * `serverId` - * `id` - * `key` - * `value` - -#### Server `notifymusicbotedit` -* **[~]** - * [1] `requestId` (empty of not requested) - * [1] `serverId` - * `id` - * `key`: {connected|name|channelId|description|playing|playingInfo} - -#### Client `musicbotplay` -* **[1]** - * `requestId` - * `serverId` - * `id` - * `type`: {yt|file} - * `value` - -#### Server `notifymusicbotplay` -Send only as answer for `musicbotplay` -You would recive the play state update via `notifymusicbotedit` -* **[1]** - * `requestId` - * `succeeded` - * -#### Client `musicbotstop` -* **[1]** - * `requestId` - * `serverId` - * `id` - * `paused`: {1|0} - -#### Server `notifymusicbotstop` -Send only as answer for `musicbotstop` -You would recive the play state update via `notifymusicbotedit` -* **[1]** - * `requestId` - * `succeeded` \ No newline at end of file diff --git a/MusicBot/src/MusicPlayer.cpp b/MusicBot/src/MusicPlayer.cpp index 0945f64..9f3b88b 100644 --- a/MusicBot/src/MusicPlayer.cpp +++ b/MusicBot/src/MusicPlayer.cpp @@ -16,7 +16,7 @@ void log::log(const Level& lvl, const std::string& msg) { void AbstractMusicPlayer::registerEventHandler(const std::string& key, const std::function& function) { threads::MutexLock lock(this->eventLock); - this->eventHandlers.push_back({key, function}); + this->eventHandlers.emplace_back(key, function); } void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) { @@ -111,5 +111,11 @@ void manager::loadProviders(const std::string& path) { } void manager::register_provider(const std::shared_ptr &provider) { + threads::MutexLock l(staticLock); types.push_back(provider); +} + +void manager::finalizeProviders() { + threads::MutexLock l(staticLock); + types.clear(); } \ No newline at end of file diff --git a/license/CMakeLists.txt b/license/CMakeLists.txt index 88bdba2..61c6210 100644 --- a/license/CMakeLists.txt +++ b/license/CMakeLists.txt @@ -40,31 +40,50 @@ add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HD server/WebAPI.cpp server/StatisticManager.cpp server/UserManager.cpp + MySQLLibSSLFix.c ) target_link_libraries(TeaLicenseServer - TeaSpeak #Static - ${LIBRARY_PATH_DATA_PIPES} - ${LIBRARY_PATH_TERMINAL} #Static ${LIBRARY_PATH_THREAD_POOL} #Static - ${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a - ${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a - pthread - ${LIBRARY_PATH_BORINGSSL_SSL} - ${LIBRARY_PATH_BORINGSSL_CRYPTO} + TeaSpeak #Static + TeaLicenseHelper #Static + ${LIBRARY_PATH_TERMINAL} #Static ${LIBRARY_PATH_VARIBALES} + ${LIBRARY_PATH_YAML} + pthread + stdc++fs + ${LIBEVENT_PATH}/libevent.a + ${LIBEVENT_PATH}/libevent_pthreads.a + ${LIBRARY_PATH_OPUS} + ${LIBRARY_PATH_JSON} + ${LIBRARY_PATH_PROTOBUF} + + #We're forsed to use boringssl caused by the fact that boringssl is already within webrtc! + + #Require a so + sqlite3 + ${LIBRARY_PATH_BREAKPAD} ${LIBRARY_PATH_PROTOBUF} - ${LIBRARY_TOM_MATH} + #${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version) ${LIBRARY_TOM_CRYPT} + ${LIBRARY_TOM_MATH} - ${LIBRARY_PATH_BREAKPAD} - ${TOM_LIBRARIES} - ${LIBRARY_PATH_JDBC} - jsoncpp.a - stdc++fs.a + mysqlclient.a + + ${LIBRARY_PATH_ED255} + + ${LIBRARY_PATH_DATA_PIPES} + ${LIBRARY_PATH_NICE} + ${LIBRARY_PATH_GLIBC} + + ${LIBRARY_PATH_BORINGSSL_SSL} + ${LIBRARY_PATH_BORINGSSL_CRYPTO} + dl + z ) +include_directories(${LIBRARY_PATH}/boringssl/include/) #The test license client add_executable(TeaLicenseClient diff --git a/license/LicenseServerMain.cpp b/license/LicenseServerMain.cpp index f65a753..1d10458 100644 --- a/license/LicenseServerMain.cpp +++ b/license/LicenseServerMain.cpp @@ -1,18 +1,16 @@ #include -#include +#include #include #include #include #include -#include #include "server/WebAPI.h" #include "server/StatisticManager.h" #include #include "server/UserManager.h" -#include #include -#include +#include using namespace std; using namespace std::chrono; @@ -43,6 +41,59 @@ shared_ptr web_server; shared_ptr license_server; shared_ptr user_manager; +inline std::shared_ptr load_web_certificate() { + std::string certificate_file{"web_certificate.txt"}, certificate{}; + std::string key_file{"web_key.txt"}, key{}; + std::string error{}; + + auto context = ssl_manager->initializeContext("web_shared_cert", key_file, certificate_file, error); + if(!context) { + logError(0, "Failed to load web certificated: {}", error); + return nullptr; + } + + + std::shared_ptr bio{nullptr}; + const uint8_t* mem_ptr{nullptr}; + size_t length{0}; + + { + bio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); + if(PEM_write_bio_PrivateKey(&*bio, &*context->privateKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) { + logError(0, "Failed to export certificate"); + return nullptr; + } + + if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) { + logError(0, "Failed to receive memptr to private key"); + return nullptr; + } + key.resize(length); + memcpy(key.data(), mem_ptr, length); + } + + { + bio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); + if(PEM_write_bio_X509(&*bio, &*context->certificate) != 1) { + logError(0, "Failed to export certificate"); + return nullptr; + } + if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) { + logError(0, "Failed to receive memptr to certificate"); + return nullptr; + } + certificate.resize(length); + memcpy(certificate.data(), mem_ptr, length); + } + + auto response = std::make_shared(); + response->key = key; + response->certificate = certificate; + response->revision = digest::sha512(response->key + response->certificate); + logMessage(0, "Web certificate revision: {}", hex::hex(response->revision)); + return response; +} + int main(int argc, char** argv) { if(argc < 2) { cerr << "Invalid arguments! Need MySQL connection" << endl; @@ -50,7 +101,6 @@ int main(int argc, char** argv) { } evthread_use_pthreads(); - http::decode_url("xxx"); srand(system_clock::now().time_since_epoch().count()); terminal::install(); if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } @@ -213,6 +263,7 @@ int main(int argc, char** argv) { listen_addr.sin_port = htons(27786); license_server = make_shared(listen_addr, license_manager, statistic_manager, web_server, user_manager); + license_server->web_certificate = load_web_certificate(); license_server->startServer(); } diff --git a/license/MySQLLibSSLFix.c b/license/MySQLLibSSLFix.c new file mode 100644 index 0000000..47bb2c5 --- /dev/null +++ b/license/MySQLLibSSLFix.c @@ -0,0 +1,107 @@ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const EVP_CIPHER *EVP_aes_128_cfb1(void){ return 0; } +const EVP_CIPHER *EVP_aes_192_cfb1(void){ return 0; } +const EVP_CIPHER *EVP_aes_256_cfb1(void){ return 0; } + +const EVP_CIPHER *EVP_aes_128_cfb8(void){ return 0; } +const EVP_CIPHER *EVP_aes_192_cfb8(void){ return 0; } +const EVP_CIPHER *EVP_aes_256_cfb8(void){ return 0; } + +const EVP_CIPHER *EVP_aes_128_cfb128(void){ return 0; } +const EVP_CIPHER *EVP_aes_192_cfb128(void){ return 0; } +const EVP_CIPHER *EVP_aes_256_cfb128(void){ return 0; } + +int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) { + return EVP_EncryptFinal_ex(ctx, out, out_len); +} + +int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *out_len) { + return EVP_DecryptFinal_ex(ctx, out, out_len); +} + +int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str) { + return 0; +} + +#define DTLSv1_get_timeout DTLSv1_get_timeout +#define DTLSv1_handle_timeout DTLSv1_handle_timeout +#define SSL_CTX_add0_chain_cert SSL_CTX_add0_chain_cert +#define SSL_CTX_add1_chain_cert SSL_CTX_add1_chain_cert +#define SSL_CTX_add_extra_chain_cert SSL_CTX_add_extra_chain_cert +#define SSL_CTX_clear_extra_chain_certs SSL_CTX_clear_extra_chain_certs +#define SSL_CTX_clear_chain_certs SSL_CTX_clear_chain_certs +#define SSL_CTX_clear_mode SSL_CTX_clear_mode +#define SSL_CTX_clear_options SSL_CTX_clear_options +#define SSL_CTX_get0_chain_certs SSL_CTX_get0_chain_certs +#define SSL_CTX_get_extra_chain_certs SSL_CTX_get_extra_chain_certs +#define SSL_CTX_get_max_cert_list SSL_CTX_get_max_cert_list +#define SSL_CTX_get_mode SSL_CTX_get_mode +#define SSL_CTX_get_options SSL_CTX_get_options +#define SSL_CTX_get_read_ahead SSL_CTX_get_read_ahead +#define SSL_CTX_get_session_cache_mode SSL_CTX_get_session_cache_mode +#define SSL_CTX_get_tlsext_ticket_keys SSL_CTX_get_tlsext_ticket_keys +#define SSL_CTX_need_tmp_RSA SSL_CTX_need_tmp_RSA +#define SSL_CTX_sess_get_cache_size SSL_CTX_sess_get_cache_size +#define SSL_CTX_sess_number SSL_CTX_sess_number +#define SSL_CTX_sess_set_cache_size SSL_CTX_sess_set_cache_size +#define SSL_CTX_set0_chain SSL_CTX_set0_chain +#define SSL_CTX_set1_chain SSL_CTX_set1_chain +#define SSL_CTX_set1_curves SSL_CTX_set1_curves +#define SSL_CTX_set_max_cert_list SSL_CTX_set_max_cert_list +#define SSL_CTX_set_max_send_fragment SSL_CTX_set_max_send_fragment +#define SSL_CTX_set_mode SSL_CTX_set_mode +#define SSL_CTX_set_msg_callback_arg SSL_CTX_set_msg_callback_arg +#define SSL_CTX_set_options SSL_CTX_set_options +#define SSL_CTX_set_read_ahead SSL_CTX_set_read_ahead +#define SSL_CTX_set_session_cache_mode SSL_CTX_set_session_cache_mode +#define SSL_CTX_set_tlsext_servername_arg SSL_CTX_set_tlsext_servername_arg +#define SSL_CTX_set_tlsext_servername_callback \ + SSL_CTX_set_tlsext_servername_callback +#define SSL_CTX_set_tlsext_ticket_key_cb SSL_CTX_set_tlsext_ticket_key_cb +#define SSL_CTX_set_tlsext_ticket_keys SSL_CTX_set_tlsext_ticket_keys +#define SSL_CTX_set_tmp_dh SSL_CTX_set_tmp_dh +#define SSL_CTX_set_tmp_ecdh SSL_CTX_set_tmp_ecdh +#define SSL_CTX_set_tmp_rsa SSL_CTX_set_tmp_rsa +#define SSL_add0_chain_cert SSL_add0_chain_cert +#define SSL_add1_chain_cert SSL_add1_chain_cert +#define SSL_clear_chain_certs SSL_clear_chain_certs +#define SSL_clear_mode SSL_clear_mode +#define SSL_clear_options SSL_clear_options +#define SSL_get0_certificate_types SSL_get0_certificate_types +#define SSL_get0_chain_certs SSL_get0_chain_certs +#define SSL_get_max_cert_list SSL_get_max_cert_list +#define SSL_get_mode SSL_get_mode +#define SSL_get_options SSL_get_options +#define SSL_get_secure_renegotiation_support \ + SSL_get_secure_renegotiation_support +#define SSL_need_tmp_RSA SSL_need_tmp_RSA +#define SSL_num_renegotiations SSL_num_renegotiations +#define SSL_session_reused SSL_session_reused +#define SSL_set0_chain SSL_set0_chain +#define SSL_set1_chain SSL_set1_chain +#define SSL_set1_curves SSL_set1_curves +#define SSL_set_max_cert_list SSL_set_max_cert_list +#define SSL_set_max_send_fragment SSL_set_max_send_fragment +#define SSL_set_mode SSL_set_mode +#define SSL_set_msg_callback_arg SSL_set_msg_callback_arg +#define SSL_set_mtu SSL_set_mtu +#define SSL_set_options SSL_set_options +#define SSL_set_tlsext_host_name SSL_set_tlsext_host_name +#define SSL_set_tmp_dh SSL_set_tmp_dh +#define SSL_set_tmp_ecdh SSL_set_tmp_ecdh +#define SSL_set_tmp_rsa SSL_set_tmp_rsa +#define SSL_total_renegotiations SSL_total_renegotiations + +long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg) { + return 0; +} + +#ifdef __cplusplus +}; +#endif \ No newline at end of file diff --git a/license/packets/LicenseRequest.proto b/license/packets/LicenseRequest.proto index 16ec0cd..7060b5e 100644 --- a/license/packets/LicenseRequest.proto +++ b/license/packets/LicenseRequest.proto @@ -60,6 +60,14 @@ message PropertyUpdateRequest { required int64 bots_online = 9; required int64 queries_online = 10; required int64 servers_online = 11; + + optional bytes web_cert_revision = 12; +} + +message WebCertificate { + required bytes revision = 1; + required string key = 2; + required string certificate = 3; } message PropertyUpdateResponse { @@ -68,4 +76,5 @@ message PropertyUpdateResponse { required int64 speach_varianz_corrector = 3; optional bool reset_speach = 4; + optional WebCertificate web_certificate = 5; } \ No newline at end of file diff --git a/license/server/LicenseServer.cpp b/license/server/LicenseServer.cpp index dd99cd2..2dabdb2 100644 --- a/license/server/LicenseServer.cpp +++ b/license/server/LicenseServer.cpp @@ -136,7 +136,13 @@ void LicenseServer::handleEventWrite(int fd, short, void* ptrServer) { if(!buffer) return; auto writtenBytes = send(fd, &buffer->buffer[buffer->index], buffer->length - buffer->index, 0); - buffer->index += writtenBytes; + if(writtenBytes <= 0) { + if(writtenBytes == -1 && errno == EAGAIN) + return; + logError(LOG_LICENSE_CONTROLL, "Invalid write. Disconnecting remote client. Message: {}/{}", errno, strerror(errno)); + } else { + buffer->index += writtenBytes; + } if(buffer->index >= buffer->length) { TAILQ_REMOVE(&client->network.writeQueue, buffer, tail); @@ -202,7 +208,7 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) { if(read < 0){ if(errno == EWOULDBLOCK) return; - logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote manager. Message: {}/{}", errno, strerror(errno)); + logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client. Message: {}/{}", errno, strerror(errno)); event_del_noblock(client->network.readEvent); server->closeConnection(client); return; diff --git a/license/server/LicenseServer.h b/license/server/LicenseServer.h index bf9ce11..f0fb2c2 100644 --- a/license/server/LicenseServer.h +++ b/license/server/LicenseServer.h @@ -58,6 +58,12 @@ namespace license { inline std::string address() { return inet_ntoa(network.remoteAddr.sin_addr); } }; + struct WebCertificate { + std::string revision; + std::string key; + std::string certificate; + }; + class LicenseServer { public: explicit LicenseServer(const sockaddr_in&, const std::shared_ptr&, const std::shared_ptr& /* stats */, const std::shared_ptr& /* web stats */, const std::shared_ptr& /* user manager */); @@ -75,6 +81,8 @@ namespace license { std::lock_guard lock(this->lock); return currentClients; } + + std::shared_ptr web_certificate{nullptr}; private: void unregisterClient(const std::shared_ptr&); void cleanup_clients(); diff --git a/license/server/LicenseServerHandler.cpp b/license/server/LicenseServerHandler.cpp index ae8fb1c..73a802f 100644 --- a/license/server/LicenseServerHandler.cpp +++ b/license/server/LicenseServerHandler.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -224,7 +225,7 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr &clie if(client->invalid_license) { ts::proto::license::PropertyUpdateResponse response; response.set_accepted(true); - response.set_reset_speach(0); + response.set_reset_speach(false); response.set_speach_total_remote(0); response.set_speach_varianz_corrector(0); client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response}); @@ -247,18 +248,34 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr &clie logMessage("[CLIENT][" + client->address() + "] Bots online : " + to_string(pkt.bots_online())); logMessage("[CLIENT][" + client->address() + "] Servers : " + to_string(pkt.servers_online())); this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt); - //TODO test stuff! + //TODO test stuff if its possible! + + ts::proto::license::WebCertificate* web_certificate{nullptr}; + if(pkt.has_web_cert_revision()) { + logMessage("[CLIENT][" + client->address() + "] -------------------------------"); + logMessage("[CLIENT][" + client->address() + "] Web cert revision : " + hex::hex(pkt.web_cert_revision())); + + auto cert = this->web_certificate; + if(cert && cert->revision != pkt.web_cert_revision()) { + web_certificate = new ts::proto::license::WebCertificate{}; + web_certificate->set_key(cert->key); + web_certificate->set_certificate(cert->certificate); + web_certificate->set_revision(cert->revision); + } + } ts::proto::license::PropertyUpdateResponse response; response.set_accepted(true); response.set_reset_speach(pkt.speach_total() < 0); response.set_speach_total_remote(pkt.speach_total()); response.set_speach_varianz_corrector(0); + response.set_allocated_web_certificate(web_certificate); client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response}); this->disconnectClient(client, "finished"); if(this->statistics) this->statistics->reset_cache_general(); + if(this->web_statistics) this->web_statistics->async_broadcast_notify_general_update(); return true; diff --git a/license/server/WebAPI.cpp b/license/server/WebAPI.cpp index 82763ed..2977343 100644 --- a/license/server/WebAPI.cpp +++ b/license/server/WebAPI.cpp @@ -382,9 +382,6 @@ inline pipes::buffer json_dump(const Json::Value& value) { return pipes::buffer((void*) json.c_str(), json.length()); } -Json::Value::Value(long value) : Value(to_string(value)) {} -Json::Value::Value(unsigned long value) : Value(to_string(value)) {} - bool WebStatistics::handle_message(const std::shared_ptr &client, const pipes::WSMessage &raw_message) { if(this->update_flood(client, 10)) { static pipes::buffer _response; diff --git a/license/shared/License.h b/license/shared/License.h index 444d46d..4340ee5 100644 --- a/license/shared/License.h +++ b/license/shared/License.h @@ -393,8 +393,8 @@ namespace license { struct packet { struct { - PacketType packetId; - mutable uint16_t length; + PacketType packetId{0}; + mutable uint16_t length{0}; } header; std::string data; diff --git a/license/shared/LicenseRequest.cpp b/license/shared/LicenseRequest.cpp index 7ea1e5a..198a862 100644 --- a/license/shared/LicenseRequest.cpp +++ b/license/shared/LicenseRequest.cpp @@ -190,10 +190,18 @@ void LicenceRequest::handleConnected() { } void LicenceRequest::handleMessage(const std::string& message) { - if(message.length() < sizeof(protocol::packet::header)) LICENSE_FERR(this, ConnectionException, "Invalid packet size"); + this->buffer += message; + if(this->buffer.length() < sizeof(protocol::packet::header)) + return; + protocol::packet packet{protocol::PACKET_DISCONNECT, ""}; - memcpy(&packet.header, message.data(), sizeof(protocol::packet::header)); - packet.data = message.substr(sizeof(protocol::packet::header)); + memcpy(&packet.header, this->buffer.data(), sizeof(protocol::packet::header)); + if(packet.header.length <= this->buffer.length() - sizeof(protocol::packet::header)) { + packet.data = this->buffer.substr(sizeof(protocol::packet::header), packet.header.length); + this->buffer = this->buffer.substr(sizeof(protocol::packet::header) + packet.header.length); + } else { + return; + } if(!this->cryptKey.empty()) { xorBuffer((char*) packet.data.data(), packet.data.length(), this->cryptKey.data(), this->cryptKey.length()); @@ -209,6 +217,9 @@ void LicenceRequest::handleMessage(const std::string& message) { this->handlePacketInfoAdjustment(packet.data); } else LICENSE_FERR(this, ConnectionException, "Invalid packet id (" + to_string(packet.header.packetId) + ")"); + + if(!this->buffer.empty() && this->state != protocol::DISCONNECTING && this->state != protocol::UNCONNECTED) + this->handleMessage(""); } void LicenceRequest::disconnect(const std::string& message) { diff --git a/license/shared/LicenseRequest.h b/license/shared/LicenseRequest.h index d582df5..6f4b7bd 100644 --- a/license/shared/LicenseRequest.h +++ b/license/shared/LicenseRequest.h @@ -60,8 +60,10 @@ namespace license { }; struct LicenseRequestData { - std::shared_ptr license = nullptr; - std::shared_ptr info = nullptr; + std::shared_ptr license{nullptr}; + std::shared_ptr info{nullptr}; + + std::string web_certificate_revision{}; int64_t speach_total = 0; int64_t speach_dead = 0; @@ -75,6 +77,12 @@ namespace license { int64_t servers_online = 0; }; + struct WebCertificate { + std::string revision; + std::string key; + std::string certificate; + }; + class LicenceRequest { public: typedef threads::Future> ResponseFuture; @@ -88,6 +96,7 @@ namespace license { void sendPacket(const protocol::packet&); + std::function callback_update_certificate{nullptr}; bool verbose = true; private: std::shared_ptr data; @@ -100,6 +109,8 @@ namespace license { sockaddr_in remote_address; + std::string buffer{}; + int file_descriptor = 0; std::thread event_dispatch; threads::Thread* closeThread = nullptr; diff --git a/license/shared/LicenseRequestHandler.cpp b/license/shared/LicenseRequestHandler.cpp index 6f09da5..198c49d 100644 --- a/license/shared/LicenseRequestHandler.cpp +++ b/license/shared/LicenseRequestHandler.cpp @@ -97,17 +97,28 @@ void LicenceRequest::handlePacketLicenseInfo(const std::string& message) { infos.set_queries_online(this->data->queries_online); infos.set_servers_online(this->data->servers_online); infos.set_web_clients_online(this->data->web_clients_online); + + infos.set_web_cert_revision(this->data->web_certificate_revision); this->sendPacket({protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT, infos}); this->state = protocol::PROPERTY_ADJUSTMENT; } void LicenceRequest::handlePacketInfoAdjustment(const std::string& message) { - ts::proto::license::PropertyUpdateResponse response; + ts::proto::license::PropertyUpdateResponse response{}; if(!response.ParseFromString(message)) LICENSE_FERR(this, InvalidResponseException, "Could not parse response"); this->response->properties_valid = response.accepted(); this->response->speach_varianz_adjustment = response.speach_varianz_corrector(); this->response->speach_reset = response.reset_speach(); + + if(response.has_web_certificate() && this->callback_update_certificate) { + WebCertificate cert{}; + cert.revision = response.web_certificate().revision(); + cert.key = response.web_certificate().key(); + cert.certificate = response.web_certificate().certificate(); + this->callback_update_certificate(cert); + } + this->currentFuture->executionSucceed(this->response); this->response = nullptr; diff --git a/music b/music index af7918a..ef03079 160000 --- a/music +++ b/music @@ -1 +1 @@ -Subproject commit af7918a243bcdfb9d32ec5a220cc2a6018bbe325 +Subproject commit ef030797a2a997704b9bd126cbbe1d209205e8f0 diff --git a/server/main.cpp b/server/main.cpp index bda1114..2259080 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -14,6 +14,7 @@ #include "src/server/file/FileServer.h" #include "src/terminal/CommandHandler.h" #include "src/client/InternalClient.h" +#include "src/music/MusicBotManager.h" #include "src/SignalHandler.h" #include "src/build.h" @@ -366,7 +367,7 @@ int main(int argc, char** argv) { auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password"); if(!password.empty()) { logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password); - auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitalServerAdmin()->getUid()); + auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid()); bool found = false; for(const auto& account : accounts) { if(account->bound_server != 0) continue; @@ -395,12 +396,14 @@ int main(int argc, char** argv) { stopApp: logMessageFmt(true, LOG_GENERAL, "Stopping application"); + ::music::manager::finalizeProviders(); + if(serverInstance) serverInstance->stopInstance(); delete serverInstance; serverInstance = nullptr; - + ts::music::MusicBotManager::shutdown(); if(sql) sql->finalize(); delete sql; @@ -410,19 +413,4 @@ int main(int argc, char** argv) { terminal::uninstall(); mainThreadDone = true; return 0; -} - -/* -[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= -[02][OUT] (188.225.34.225:9988) ftinitdownload clientftfid=4092 name=\/icon_847789427 cid=0 cpw seekpos=0 proto=1 return_code= -[02][ IN] (188.225.34.225:9988) notifyclientupdated clid=5 client_version=3.2.0\s[Build:\s1533739581] client_platform=Linux client_login_name=WolverinDEV client_created=1536521950 client_lastconnected=1536522252 client_totalconnections=2 client_month_bytes_uploaded=0 client_month_bytes_downloaded=0 client_total_bytes_uploaded=0 client_total_bytes_downloaded=0 client_icon_id=0 client_country=DE -[02][ IN] (188.225.34.225:9988) notifystartdownload clientftfid=4096 proto=1 serverftfid=1 ftkey=R0Vcnx4fNdrXuMFg port=30303 size=1086 -[02][ IN] (188.225.34.225:9988) notifystartdownload clientftfid=4095 proto=1 serverftfid=1 ftkey=3eYwsuviQvTWme42 port=30303 size=822 -[02][ IN] (188.225.34.225:9988) notifystartdownload clientftfid=4094 proto=1 serverftfid=1 ftkey=dM5oaVuLYLwia2me port=30303 size=852 -[02][ IN] (188.225.34.225:9988) notifystartdownload clientftfid=4093 proto=1 serverftfid=1 ftkey=60BltUu8fbUqgLhj port=30303 size=3441 -[02][ IN] (188.225.34.225:9988) notifystartdownload clientftfid=4092 proto=1 serverftfid=1 ftkey=a0wmURVHqhNE71H2 port=30303 size=1452 - - */ \ No newline at end of file +} \ No newline at end of file diff --git a/server/src/Configuration.cpp b/server/src/Configuration.cpp index 42f77ca..1d15d1e 100644 --- a/server/src/Configuration.cpp +++ b/server/src/Configuration.cpp @@ -133,6 +133,7 @@ std::string config::music::command_prefix; #define CREATE_IF_NOT_EXISTS 0b00000001 #define PREMIUM_ONLY 0b00000010 #define FLAG_REQUIRE 0b00000100 +#define FLAG_RELOADABLE 0b00001000 #define COMMENT(path, comment) commentMapping[path].emplace_back(comment) #define WARN_SENSITIVE(path) COMMENT(path, "Do NOT TOUCH unless you're 100% sure!") @@ -288,9 +289,11 @@ void build_comments(map>& map, const std::deque>& bindings) { +void read_bindings(YAML::Node& root, const std::deque>& bindings, uint8_t required_flags = 0) { for(const auto& entry : bindings) { - if(entry->bounded_by != 0) continue; + if(entry->bounded_by == 2) continue; + if(required_flags > 0 && (entry->flags & required_flags) == 0) continue; + auto nodes = resolveNode(root, entry->key); assert(!nodes.empty()); assert(entry->read_config); @@ -313,8 +316,10 @@ inline string apply_comments(stringstream &in, map>& comme std::deque> create_local_bindings(int& version, std::string& license); #define CURRENT_CONFIG_VERSION 14 +static std::string _config_path; vector config::parseConfig(const std::string& path) { - //FIXME test for premium! + _config_path = path; + vector errors; saveConfig = false; @@ -426,15 +431,10 @@ vector config::parseConfig(const std::string& path) { } if(!config::license){ -#if true - logErrorFmt(true, LOG_GENERAL, strobf("The given license isnt valid!").string()); + logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string()); logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string()); teaspeak_license = "none"; goto license_parsing; -#else - errors.push_back("Invalid license code! (" + err + ")"); - return errors; -#endif } if(!config::license){ errors.emplace_back(strobf("Invalid license code!").string()); @@ -446,7 +446,7 @@ vector config::parseConfig(const std::string& path) { return errors; } - logErrorFmt(true, LOG_GENERAL, strobf("The given license isnt valid!").string()); + logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string()); logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string()); teaspeak_license = "none"; goto license_parsing; @@ -516,6 +516,48 @@ vector config::parseConfig(const std::string& path) { return errors; } +std::vector config::reload() { + + vector errors; + saveConfig = false; + + ifstream cfgStream(_config_path); + YAML::Node config; + try { + config = YAML::Load(cfgStream); + } catch (const YAML::ParserException& ex){ + errors.emplace_back("Could not load config file: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column)); + return errors; + } + + try { + int config_version; + string teaspeak_license; + { + auto bindings = create_local_bindings(config_version, teaspeak_license); + read_bindings(config, bindings, 0); + } + if(config_version != CURRENT_CONFIG_VERSION) { + errors.emplace_back("Given config version is no equal to the initial one!"); + return errors; + } + + auto bindings = create_bindings(); + read_bindings(config, bindings, FLAG_RELOADABLE); + } catch(const YAML::Exception& ex) { + errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column)); + return errors; + } catch(const ConfigParseError& ex) { + errors.emplace_back("Failed to parse config entry \"" + ex.entry()->key + "\": " + ex.what()); + return errors; + } catch(const PathNodeError& ex) { + errors.emplace_back("Expected sequence for path " + ex.path() + ": " + ex.message()); + return errors; + } + + return errors; +} + void bind_string_description(const shared_ptr& _entry, std::string& target, const std::string& default_value) { _entry->default_value = [default_value]() -> std::deque { return { default_value }; }; _entry->value_description = [] { return "The value must be a string"; }; @@ -760,7 +802,7 @@ inline std::string join_path(const deque& stack, const std::string& entr #define CREATE_BINDING(name, _flags) \ auto binding = make_shared(); \ binding->key = join_path(group_stack, name); \ - binding->flags = _flags; \ + binding->flags = (_flags); \ result.push_back(binding) #define BIND_STRING(target, default) \ @@ -797,6 +839,9 @@ inline std::string join_path(const deque& stack, const std::string& entr for(const auto& entry : {desc, ##__VA_ARGS__}) \ binding->description["Notes"].emplace_back(entry) +#define ADD_NOTE_RELOADABLE() \ + binding->description["Notes"].emplace_back("This option could be reloaded while the instance is running.") + #define ADD_WARN(desc, ...) \ for(const auto& entry : {desc, ##__VA_ARGS__}) \ binding->description["Warning"].emplace_back(entry) @@ -1011,12 +1056,12 @@ std::deque> config::create_bindings() { { BIND_GROUP(ssl); { - CREATE_BINDING("certificate", 0); + CREATE_BINDING("certificate", FLAG_RELOADABLE); BIND_STRING(config::query::ssl::certFile, "certs/query_certificate.pem"); ADD_DESCRIPTION("The SSL certificate for the query client"); } { - CREATE_BINDING("privatekey", 0); + CREATE_BINDING("privatekey", FLAG_RELOADABLE); BIND_STRING(config::query::ssl::keyFile, "certs/query_privatekey.pem"); ADD_DESCRIPTION("The SSL private key for the query client (You have to export the key without a password!)"); } @@ -1099,19 +1144,21 @@ std::deque> config::create_bindings() { { BIND_GROUP(server) { - CREATE_BINDING("platform", PREMIUM_ONLY); + CREATE_BINDING("platform", PREMIUM_ONLY | FLAG_RELOADABLE); BIND_STRING(config::server::DefaultServerPlatform, strobf("Linux").string()); ADD_DESCRIPTION("The displayed platform to the client"); ADD_NOTE("This option is only for the premium version."); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("version", PREMIUM_ONLY); + CREATE_BINDING("version", PREMIUM_ONLY | FLAG_RELOADABLE); BIND_STRING(config::server::DefaultServerVersion, strobf("TeaSpeak ").string() + build::version()->string(true)); ADD_DESCRIPTION("The displayed version to the client"); ADD_NOTE("This option is only for the premium version."); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("licence", PREMIUM_ONLY); + CREATE_BINDING("licence", PREMIUM_ONLY | FLAG_RELOADABLE); BIND_INTEGRAL(config::server::DefaultServerLicense, LicenseType::LICENSE_AUTOMATIC_SERVER, LicenseType::_LicenseType_MIN, LicenseType::_LicenseType_MAX); ADD_DESCRIPTION("The displayed licence type to every TeaSpeak 3 Client"); ADD_DESCRIPTION("Available types:"); @@ -1125,6 +1172,7 @@ std::deque> config::create_bindings() { ADD_DESCRIPTION(" 7: Auto-License (Instance based)"); ADD_NOTE("This option just work for non 3.2 clients!"); ADD_NOTE("This option is only for the premium version."); + ADD_NOTE_RELOADABLE(); } { CREATE_BINDING("delete_old_bans", 0); @@ -1158,9 +1206,10 @@ std::deque> config::create_bindings() { ADD_DESCRIPTION("Disable the saving of IP addresses within the database"); } { - CREATE_BINDING("max_virtual_servers", 0); + CREATE_BINDING("max_virtual_servers", FLAG_RELOADABLE); BIND_INTEGRAL(config::server::max_virtual_server, 16, -1, 999999); ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited."); + ADD_NOTE_RELOADABLE(); } { /* @@ -1180,9 +1229,10 @@ std::deque> config::create_bindings() { { BIND_GROUP(authentication); { - CREATE_BINDING("name", 0); + CREATE_BINDING("name", FLAG_RELOADABLE); BIND_BOOL(config::server::authentication::name, false); ADD_DESCRIPTION("Allow or disallow client authentication just by their name"); + ADD_NOTE_RELOADABLE(); } } } @@ -1203,7 +1253,8 @@ std::deque> config::create_bindings() { { BIND_GROUP(ssl) { - CREATE_BINDING("certificate", 0); + CREATE_BINDING("certificate", FLAG_RELOADABLE); + ADD_NOTE_RELOADABLE(); binding->type = 4; /* no terminal handling */ @@ -1212,6 +1263,7 @@ std::deque> config::create_bindings() { }; binding->default_value = []() -> deque { return {}; }; + //Unused :) binding->set_default = [](YAML::Node& node) { auto default_node = node["default"]; default_node["certificate"] = "default_certificate.pem"; @@ -1221,11 +1273,11 @@ std::deque> config::create_bindings() { weak_ptr _binding = binding; binding->read_config = [_binding](YAML::Node& node) { auto b = _binding.lock(); - if(!b) - return; + if(!b) return; + config::web::ssl::certificates.clear(); if(!node.IsDefined() || node.IsNull()) - b->set_default(node); + return; for(auto it = node.begin(); it != node.end(); it++) { auto node_cert = it->second["certificate"]; @@ -1239,7 +1291,7 @@ std::deque> config::create_bindings() { continue; } - config::web::ssl::certificates.push_back({it->first.as(), node_key.as(), node_cert.as()}); + config::web::ssl::certificates.emplace_back(it->first.as(), node_key.as(), node_cert.as()); } }; } @@ -1280,14 +1332,16 @@ std::deque> config::create_bindings() { { BIND_GROUP(geolocation); { - CREATE_BINDING("fallback_country", 0); + CREATE_BINDING("fallback_country", FLAG_RELOADABLE); BIND_STRING(config::geo::countryFlag, "DE"); ADD_DESCRIPTION("The fallback country if lookup fails"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("force_fallback_country", 0); + CREATE_BINDING("force_fallback_country", FLAG_RELOADABLE); BIND_BOOL(config::geo::staticFlag, false); ADD_DESCRIPTION("Enforce the default country and disable resolve"); + ADD_NOTE_RELOADABLE(); } { @@ -1329,70 +1383,84 @@ std::deque> config::create_bindings() { { BIND_GROUP(messages); { - CREATE_BINDING("voice.server_stop", 0); + CREATE_BINDING("voice.server_stop", FLAG_RELOADABLE); BIND_STRING(config::messages::serverStopped, "Server stopped"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("application.stop", 0); + CREATE_BINDING("application.stop", FLAG_RELOADABLE); BIND_STRING(config::messages::applicationStopped, "Application stopped"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("application.crash", 0); + CREATE_BINDING("application.crash", FLAG_RELOADABLE); BIND_STRING(config::messages::applicationCrashed, "Application crashed"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("idle_time", 0); + CREATE_BINDING("idle_time", FLAG_RELOADABLE); BIND_STRING(config::messages::idle_time_exceeded, "Idle time exceeded"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("teamspeak_permission_editor", 0); + CREATE_BINDING("teamspeak_permission_editor", FLAG_RELOADABLE); BIND_STRING(config::messages::teamspeak_permission_editor, "\n[b][COLOR=#aa0000]ATTENTION[/COLOR][/b]:\nIt seems like you're trying to edit the TeaSpeak permissions with the TeamSpeak 3 client!\nThis is [b]really[/b] buggy due a bug within the client you're using.\n\nWe recommand to [b]use the [url=https://web.teaspeak.de/]TeaSpeak-Web[/url][/b] client or the [b][url=https://teaspeak.de/]TeaSpeak client[/url][/b].\nYatQA is a good option as well.\n\nTo disable/edit this message please edit the config.yml\nPlease note: Permission bugs, which will be reported wound be accepted."); + ADD_NOTE_RELOADABLE(); } { BIND_GROUP(mute); { - CREATE_BINDING("mute_message", 0); + CREATE_BINDING("mute_message", FLAG_RELOADABLE); BIND_STRING(config::messages::mute_notify_message, "Hey!\nI muted you!"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("unmute_message", 0); + CREATE_BINDING("unmute_message", FLAG_RELOADABLE); BIND_STRING(config::messages::unmute_notify_message, "Hey!\nI unmuted you!"); + ADD_NOTE_RELOADABLE(); } } { BIND_GROUP(kick_invalid); { - CREATE_BINDING("hardware_id", 0); + CREATE_BINDING("hardware_id", FLAG_RELOADABLE); BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid hardware id. Protocol hacked?"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("command", 0); + CREATE_BINDING("command", FLAG_RELOADABLE); BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid command. Protocol hacked?"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("badges", 0); + CREATE_BINDING("badges", FLAG_RELOADABLE); BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid badges. Protocol hacked?"); + ADD_NOTE_RELOADABLE(); } } { - CREATE_BINDING("vpn.kick", 0); + CREATE_BINDING("vpn.kick", FLAG_RELOADABLE); BIND_STRING(config::messages::kick_vpn, "Please disable your VPN! (Provider: ${provider.name})"); ADD_DESCRIPTION("This is the kick/ban message when a client tries to connect with a vpn"); ADD_DESCRIPTION("Variables are enabled. Available:"); ADD_DESCRIPTION(" - provider.name => Contains the provider of the ip which has been flaged as vps"); ADD_DESCRIPTION(" - provider.website => Contains the website provider of the ip which has been flaged as vps"); + + ADD_NOTE_RELOADABLE(); } { BIND_GROUP(shutdown); { - CREATE_BINDING("scheduled", 0); + CREATE_BINDING("scheduled", FLAG_RELOADABLE); BIND_STRING(config::messages::shutdown::scheduled, "[b][color=#DA9100]Scheduled shutdown at ${time}(%Y-%m-%d %H:%M:%S)[/color][/b]"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("interval", 0); + CREATE_BINDING("interval", FLAG_RELOADABLE); BIND_STRING(config::messages::shutdown::interval, "[b][color=red]Server instance shutting down in ${interval}[/color][/b]"); ADD_DESCRIPTION("${interval} is defined via map in 'intervals'"); + ADD_NOTE_RELOADABLE(); } { CREATE_BINDING("intervals", 0); @@ -1448,18 +1516,20 @@ std::deque> config::create_bindings() { ADD_DESCRIPTION("Add or delete intervals as you want"); } { - CREATE_BINDING("now", 0); + CREATE_BINDING("now", FLAG_RELOADABLE); BIND_STRING(config::messages::shutdown::now, "[b][color=red]Server instance shutting down in now[/color][/b]"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("canceled", 0); + CREATE_BINDING("canceled", FLAG_RELOADABLE); BIND_STRING(config::messages::shutdown::canceled, "[b][color=green]Scheduled instance shutdown canceled![/color][/b]"); + ADD_NOTE_RELOADABLE(); } } { BIND_GROUP(music); { - CREATE_BINDING("song_announcement", 0); + CREATE_BINDING("song_announcement", FLAG_RELOADABLE); BIND_STRING(config::messages::music::song_announcement, "Now replaying ${title} (${url}) added by ${invoker}"); ADD_DESCRIPTION("${title} title of the song"); ADD_DESCRIPTION("${description} description of the song"); @@ -1470,12 +1540,14 @@ std::deque> config::create_bindings() { { BIND_GROUP(timeout); { - CREATE_BINDING("connection_reinitialized", 0); + CREATE_BINDING("connection_reinitialized", FLAG_RELOADABLE); BIND_STRING(config::messages::timeout::connection_reinitialized, "Connection lost"); + ADD_NOTE_RELOADABLE(); } { - CREATE_BINDING("packet_resend_failed", 0); + CREATE_BINDING("packet_resend_failed", FLAG_RELOADABLE); BIND_STRING(config::messages::timeout::packet_resend_failed, "Packet resend failed"); + ADD_NOTE_RELOADABLE(); } } } diff --git a/server/src/Configuration.h b/server/src/Configuration.h index 092515f..f941d8c 100644 --- a/server/src/Configuration.h +++ b/server/src/Configuration.h @@ -31,6 +31,7 @@ namespace ts { }; extern std::vector parseConfig(const std::string& /* path */); + extern std::vector reload(); extern std::deque> create_bindings(); namespace database { diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index d7419b6..8283a28 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -19,6 +19,7 @@ #include "build.h" #include #include +#include #include #include #include @@ -28,7 +29,6 @@ #endif #include #undef _POSIX_SOURCE -#include using namespace std; using namespace std::chrono; @@ -37,11 +37,7 @@ using namespace ts::server; #define INSTANCE_TICK_NAME "instance" -#define _STRINGIFY(x) #x -#define STRINGIFY(x) _STRINGIFY(x) - extern bool mainThreadActive; -extern InstanceHandler* serverInstance; InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) { serverInstance = this; this->tick_manager = make_shared(config::threads::ticking, "tick task "); @@ -226,19 +222,6 @@ inline string strip(std::string message) { return message; } -inline sockaddr_in* resolveAddress(const string& host, uint16_t port) { - hostent* record = gethostbyname(host.c_str()); - if (!record) { - cerr << "Cant resolve bind host! (" << host << ")" << endl; - return nullptr; - } - auto addr = new sockaddr_in{}; - addr->sin_addr.s_addr = ((in_addr *) record->h_addr)->s_addr; - addr->sin_family = AF_INET; - addr->sin_port = htons(port); - return addr; -} - inline vector split_hosts(const std::string& message, char delimiter) { vector result; size_t found, index = 0; @@ -275,6 +258,19 @@ bool InstanceHandler::startInstance() { return false; } + { + vector errors; + if(!this->reloadConfig(errors, false)) { + logCritical(LOG_GENERAL, "Failed to initialize config:"); + for(auto& error : errors) + logCritical(LOG_GENERAL, "{}", error); + return false; + } + for(auto& error : errors) + logError(LOG_GENERAL, "{}", error); + } + + this->loadWebCertificate(); fileServer = new ts::server::FileServer(); { auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as(); @@ -303,13 +299,8 @@ bool InstanceHandler::startInstance() { } if(config::query::sslMode > 0) { - string error; - auto result = this->sslMgr->initializeContext("query", config::query::ssl::keyFile, config::query::ssl::certFile, error, false, make_shared(ssl::SSLGenerator{ - .subjects = {}, - .issues = {{"O", "TeaSpeak"}, {"OU", "Query server"}, {"creator", "WolverinDEV"}} - })); - if(!result) { - logCritical(LOG_QUERY, "Failed to initialize query certificate! (" + error + ")"); + if(!this->sslMgr->getContext("query")) { + logCritical(LOG_QUERY, "Missing query SSL certificate."); return false; } } @@ -360,17 +351,6 @@ bool InstanceHandler::startInstance() { #ifdef COMPILE_WEB_CLIENT if(config::web::activated) { string error; - for(auto& certificate : config::web::ssl::certificates) { - auto result = this->sslMgr->initializeContext("web_" + get<0>(certificate), get<1>(certificate), get<2>(certificate), error, false, make_shared(ts::ssl::SSLGenerator{ - .subjects = {}, - .issues = {{"O", "TeaSpeak"}, {"OU", "Web server"}, {"creator", "WolverinDEV"}} - })); - if(!result) { - logError(LOG_INSTANCE, "Failed to initialize web certificate for servername {}! (Private key: {}, Certificate: {})", get<0>(certificate), get<1>(certificate), get<2>(certificate)); - continue; - } - } - auto rsa = this->sslMgr->initializeSSLKey("teaforo_sign", R"( -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsfsTByPTE0aIqi6pJl4f @@ -438,9 +418,6 @@ void InstanceHandler::stopInstance() { threads::MutexLock lock_tick(this->lock_tick); this->scheduler()->cancelTask(INSTANCE_TICK_NAME); - this->save_channel_permissions(); - this->save_group_permissions(); - debugMessage(LOG_INSTANCE, "Stopping all virtual servers"); if (this->voiceServerManager) this->voiceServerManager->shutdownAll(ts::config::messages::applicationStopped); @@ -460,6 +437,9 @@ void InstanceHandler::stopInstance() { this->fileServer = nullptr; debugMessage(LOG_FT, "File server stopped"); + this->save_channel_permissions(); + this->save_group_permissions(); + delete this->sslMgr; this->sslMgr = nullptr; @@ -614,13 +594,11 @@ void InstanceHandler::resetSpeechTime() { #include #include -#include #include -#include string get_mac_address() { - struct ifreq ifr; - struct ifconf ifc; + struct ifreq ifr{}; + struct ifconf ifc{}; char buf[1024]; int success = 0; @@ -637,14 +615,13 @@ string get_mac_address() { for (; it != end; ++it) { strcpy(ifr.ifr_name, it->ifr_name); if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) { - if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback + if (!(ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { success = 1; break; } } - } - else { return "undefined"; } + } else { return "undefined"; } } return success ? base64::encode(ifr.ifr_hwaddr.sa_data, 6) : "undefined"; @@ -652,18 +629,20 @@ string get_mac_address() { #define SN_BUFFER 1024 std::shared_ptr InstanceHandler::generateLicenseData() { - auto response = make_shared(); - response->license = config::license; - response->servers_online = this->voiceServerManager->runningServers(); + auto request = make_shared(); + request->license = config::license; + request->servers_online = this->voiceServerManager->runningServers(); auto report = this->voiceServerManager->clientReport(); - response->client_online = report.clients_ts; - response->web_clients_online = report.clients_web; - response->bots_online = report.bots; - response->queries_online = report.queries; - response->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as(); - response->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as(); - response->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as(); - response->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as(); + request->client_online = report.clients_ts; + request->web_clients_online = report.clients_web; + request->bots_online = report.bots; + request->queries_online = report.queries; + request->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as(); + request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as(); + request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as(); + request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as(); + + request->web_certificate_revision = this->web_cert_revision; { auto info = make_shared(); @@ -672,7 +651,7 @@ std::shared_ptr InstanceHandler::generateLicenseDat { /* uname */ utsname retval{}; - if(uname(&retval) < 0) { // <---- + if(uname(&retval) < 0) { info->uname = "unknown (" + string(strerror(errno)) + ")"; } else { char buffer[SN_BUFFER]; @@ -692,9 +671,9 @@ std::shared_ptr InstanceHandler::generateLicenseDat info->unique_identifier = base64::encode(hash); } - response->info = info; + request->info = info; } - return response; + return request; } bool InstanceHandler::resetMonthlyStats() { @@ -722,4 +701,166 @@ bool InstanceHandler::resetMonthlyStats() { } } return true; +} + +bool InstanceHandler::reloadConfig(std::vector& errors, bool reload_file) { + if(reload_file) { + auto cfg_errors = config::reload(); + if(!cfg_errors.empty()) { + errors.emplace_back("Failed to load config:"); + errors.insert(errors.begin(), cfg_errors.begin(), cfg_errors.end()); + return false; + } + } + + string error; +#ifdef COMPILE_WEB_CLIENT + if(config::web::activated) { + this->sslMgr->unregister_web_contexts(); + //TODO: Generate default certificate (con-gate.work) + + string error; + for (auto &certificate : config::web::ssl::certificates) { + if(get<0>(certificate) == "default") { + logWarning(LOG_GENERAL, "Default Web certificate will be ignored. Using internal one!"); + continue; + } + + auto result = this->sslMgr->initializeContext( + "web_" + get<0>(certificate), get<1>(certificate), get<2>(certificate), error, false, make_shared( + ts::ssl::SSLGenerator{ + .subjects = {}, + .issues = {{"O", "TeaSpeak"}, + {"OU", "Web server"}, + {"creator", "WolverinDEV"}} + } + )); + if (!result) { + errors.push_back("Failed to initialize web certificate for servername " + get<0>(certificate) + "! (Key: " + get<1>(certificate) + ", Certificate: " + get<2>(certificate) + ")"); + continue; + } + } + } +#endif + + auto result = this->sslMgr->initializeContext("query_new", config::query::ssl::keyFile, config::query::ssl::certFile, error, false, make_shared(ssl::SSLGenerator{ + .subjects = {}, + .issues = {{"O", "TeaSpeak"}, {"OU", "Query server"}, {"creator", "WolverinDEV"}} + })); + if(!result) + errors.push_back("Failed to initialize query certificate! (" + error + ")"); + this->sslMgr->rename_context("query_new", "query"); //Will not succeed if the query_new context failed + + return true; +} + +void InstanceHandler::setWebCertRoot(const std::string &key, const std::string &certificate, const std::string &revision) { + std::string error{}; + + logMessage(LOG_INSTANCE, strobf("Received new web default certificate. Revision {}").string(), hex::hex(revision)); + + std::string _key{key}, _cert{certificate}, _revision{revision}; + auto result = this->sslMgr->initializeContext(strobf("web_default_new").string(), _key, _cert, error, true); + if(!result) { + logError(LOG_INSTANCE, strobf("Failed to use web default certificate: {}").string(), error); + return; + } + + this->sslMgr->rename_context(strobf("web_default_new").string(), strobf("web_default").string()); + + //https://127-0-0-1.con-gate.work:9987 + { /* "Crypt" */ + auto& xor_short = _key.length() < _cert.length() ? _key : _cert; + auto& xor_long = _key.length() < _cert.length() ? _cert : _key; + for(size_t index = 0; index < xor_short.length(); index++) + xor_short[index] ^= xor_long[index]; + for(size_t index = 0; index < xor_long.length(); index++) + xor_long[index] ^= ((index << 4) & 0xFF) | ((index >> 4) & 0xFF); + } + + for(auto& e : _cert) + e ^= 0x8A; + for(auto& e : _key) + e ^= 0x8A; + + _key = base64::encode(_key); + _cert = base64::encode(_cert); + _revision = base64::encode(_revision); + + auto response = sql::command(this->sql->sql(), + strobf("DELETE FROM `general` WHERE `key` = 'webcert-revision' or `key` = 'webcert-cert' or `key` = 'webcert-key'").string()).execute(); + if(!response) { + logError(LOG_INSTANCE, strobf("Failed to delete old default web certificate in database: {}").string(), response.fmtStr()); + return; + } + + response = sql::command(this->sql->sql(), strobf("INSERT INTO `general` (`key`, `value`) VALUES ('webcert-revision', :rev), ('webcert-cert', :cert), ('webcert-key', :key)").string(), + variable{":rev", _revision}, + variable{":cert", _cert}, + variable{":key", _key} + ).execute(); + if(!response) { + logError(LOG_INSTANCE, strobf("Failed to insert new default web certificate in database: {}").string(), response.fmtStr()); + return; + } +} + +void InstanceHandler::loadWebCertificate() { + std::string revision{}, cert{}, _key{}, error{}; + + sql::command(this->sql->sql(), strobf("SELECT * FROM `general` WHERE `key` = 'webcert-revision' or `key` = 'webcert-cert' or `key` = 'webcert-key'").string()) + .query([&](int count, std::string* values, std::string* names) { + std::string key{}, value{}; + for(int index = 0; index < count; index++) { + if(names[index] == "key") + key = values[index]; + else if(names[index] == "value") + value = values[index]; + } + + if(!value.empty() && !key.empty()) { + if(key == strobf("webcert-revision").string()) + revision = value; + else if(key == strobf("webcert-cert").string()) + cert = value; + else if(key == strobf("webcert-key").string()) + _key = value; + } + }); + + _key = base64::decode(_key); + cert = base64::decode(cert); + revision = base64::decode(revision); + + if(revision.empty() || cert.empty() || _key.empty()) { + if(!revision.empty() || !cert.empty() || !_key.empty()) + logWarning(LOG_INSTANCE, strobf("Failed to load default web certificate from database.").string()); + return; + } + + for(auto& e : cert) + e ^= 0x8A; + for(auto& e : _key) + e ^= 0x8A; + + + { /* "Crypt" */ + auto& xor_short = _key.length() < cert.length() ? _key : cert; + auto& xor_long = _key.length() < cert.length() ? cert : _key; + + for(size_t index = 0; index < xor_long.length(); index++) + xor_long[index] ^= ((index << 4) & 0xFF) | ((index >> 4) & 0xFF); + + for(size_t index = 0; index < xor_short.length(); index++) + xor_short[index] ^= xor_long[index]; + } + + + auto result = this->sslMgr->initializeContext(strobf("web_default_new").string(), _key, cert, error, true); + if(!result) { + logError(LOG_INSTANCE, strobf("Failed to use web default certificate from db: {}").string(), error); + return; + } + + this->web_cert_revision = revision; } \ No newline at end of file diff --git a/server/src/InstanceHandler.h b/server/src/InstanceHandler.h index 36b0593..8b0842d 100644 --- a/server/src/InstanceHandler.h +++ b/server/src/InstanceHandler.h @@ -32,7 +32,7 @@ namespace ts { return *_properties; } - std::shared_ptr getInitalServerAdmin(){ return globalServerAdmin; } + std::shared_ptr getInitialServerAdmin(){ return globalServerAdmin; } std::shared_ptr getGroupManager(){ return groupManager; } std::shared_ptr getChannelTree() { return this->default_tree; } @@ -51,6 +51,9 @@ namespace ts { void executeTick(TSServer*); void cancelExecute(TSServer*); + bool reloadConfig(std::vector& /* errors */, bool /* reload file */); + void setWebCertRoot(const std::string& /* key */, const std::string& /* certificate */, const std::string& /* revision */); + const std::shared_ptr& musicRoot() { return this->_musicRoot; } std::chrono::milliseconds calculateSpokenTime(); @@ -112,6 +115,8 @@ namespace ts { std::shared_ptr permission_mapper = nullptr; std::shared_ptr teamspeak_license = nullptr; + std::string web_cert_revision{}; + threads::Mutex lock_tick; private: bool setupDefaultGroups(); @@ -119,6 +124,8 @@ namespace ts { void save_group_permissions(); void save_channel_permissions(); + + void loadWebCertificate(); }; } } diff --git a/server/src/ShutdownHelper.cpp b/server/src/ShutdownHelper.cpp index 02bc3e1..504aeca 100644 --- a/server/src/ShutdownHelper.cpp +++ b/server/src/ShutdownHelper.cpp @@ -15,22 +15,27 @@ bool shuttingDown = false; void ts::server::shutdownInstance(const std::string& message) { if(shuttingDown) return; shuttingDown = true; - threads::Thread(THREAD_EXECUTE_LATER, [](){ + + auto hangup_controller = std::thread([]{ threads::self::sleep_for(chrono::seconds(30)); - logCritical("Could not shutdown server within 30 seconds! (Hangup!)"); - logCritical("Killing server!"); + logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)"); + logCriticalFmt(true, 0, "Killing server!"); - threads::Thread(THREAD_EXECUTE_LATER, [](){ + auto force_kill = std::thread([]{ threads::self::sleep_for(chrono::seconds(5)); - logCritical("Failed to exit normally!"); - logCritical("executing raise(SIGKILL);"); + logCriticalFmt(true, 0, "Failed to exit normally!"); + logCriticalFmt(true, 0, "executing raise(SIGKILL);"); raise(SIGKILL); - }).name("Stop exit controller").execute().detach(); + }); + threads::name(force_kill, "force stopper"); + force_kill.detach(); + exit(2); - }).name("Stop controller").execute().detach(); + }); + threads::name(hangup_controller, "stop controller"); + hangup_controller.detach(); - - logMessage("Stopping all server instances!"); + logMessage(LOG_GENERAL, "Stopping all server instances!"); if(serverInstance && serverInstance->getVoiceServerManager()) serverInstance->getVoiceServerManager()->shutdownAll(message); diff --git a/server/src/client/ConnectedClientCommandHandler.cpp b/server/src/client/ConnectedClientCommandHandler.cpp index 4b7c552..01406ab 100644 --- a/server/src/client/ConnectedClientCommandHandler.cpp +++ b/server/src/client/ConnectedClientCommandHandler.cpp @@ -372,7 +372,7 @@ CommandResult ConnectedClient::handleCommand(Command &cmd) { if (this->getType() == ClientType::CLIENT_TEAMSPEAK) if (command.empty() || command.find_first_not_of(' ') == -1) { if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_packet, 1, this->currentChannel)) - ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast(serverInstance->getInitalServerAdmin()), true); + ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast(serverInstance->getInitialServerAdmin()), true); } logError(this->getServerId(), "Missing command '{}'", command); @@ -4865,7 +4865,7 @@ CommandResult ConnectedClient::handleCommandClientEdit(Command &cmd, const std:: } while (index < str.length() && index != 0); if (badgesTags >= 2) { if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_badges, 1, this->currentChannel)) - ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitalServerAdmin()), true); + ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitialServerAdmin()), true); return {findError("parameter_invalid"), "Invalid badges"}; } //FIXME stuff here diff --git a/server/src/lincense/LicenseHelper.cpp b/server/src/lincense/LicenseHelper.cpp index 5754c5e..b454b0d 100644 --- a/server/src/lincense/LicenseHelper.cpp +++ b/server/src/lincense/LicenseHelper.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -13,6 +14,8 @@ using namespace std::chrono; using namespace ts; using namespace ts::server; +#define DO_LOCAL_REQUEST + LicenseHelper::LicenseHelper() { this->scheduled_request = system_clock::now() + seconds(rand() % 30); //Check in one minute } @@ -28,6 +31,7 @@ LicenseHelper::~LicenseHelper() { lock.unlock(); request->abortRequest(); + request->callback_update_certificate = nullptr; } } } @@ -165,6 +169,7 @@ void LicenseHelper::do_request(bool verbose) { auto license_data = serverInstance->generateLicenseData(); auto request = make_shared(license_data, server_addr); request->verbose = false; + request->callback_update_certificate = [&](const auto& update) { this->callback_certificate_update(update); }; { lock_guard lock(this->request.current_lock); this->request.current = request; @@ -195,4 +200,8 @@ void LicenseHelper::handle_request_failed(bool verbose, const std::string& error this->scheduled_request = this->last_request + next_request; if(verbose) logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->scheduled_request)); +} + +void LicenseHelper::callback_certificate_update(const license::WebCertificate &certificate) { + serverInstance->setWebCertRoot(certificate.key, certificate.certificate, certificate.revision); } \ No newline at end of file diff --git a/server/src/lincense/LicenseHelper.h b/server/src/lincense/LicenseHelper.h index 8a6648a..acc84dc 100644 --- a/server/src/lincense/LicenseHelper.h +++ b/server/src/lincense/LicenseHelper.h @@ -35,5 +35,6 @@ namespace license { void do_request(bool /* verbose */); void handle_request_failed(bool /* verbose */, const std::string& /* error */); + void callback_certificate_update(const license::WebCertificate&); }; } \ No newline at end of file diff --git a/server/src/manager/SqlDataManager.cpp b/server/src/manager/SqlDataManager.cpp index 6efffa9..09851ca 100644 --- a/server/src/manager/SqlDataManager.cpp +++ b/server/src/manager/SqlDataManager.cpp @@ -45,7 +45,7 @@ if(!result && result.msg().find(ignore) == string::npos){ #define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")"); #define CURRENT_DATABASE_VERSION 11 -#define CURRENT_PERMISSION_VERSION 0 +#define CURRENT_PERMISSION_VERSION 1 #define CLIENT_UID_LENGTH "64" #define CLIENT_NAME_LENGTH "128" @@ -517,6 +517,18 @@ bool SqlDataManager::update_permissions(std::string &error) { perm_version(0); + case 0: + result = sql::command(this->sql(), "DELETE FROM `permissions` WHERE `permId` = :permid", variable{":permid", "b_client_music_create"}).execute(); + if(!result) { + LOG_SQL_CMD(result); + return false; + } + result = sql::command(this->sql(), "DELETE FROM `permissions` WHERE `permId` = :permid", variable{":permid", "b_client_music_delete_own"}).execute(); + if(!result) { + LOG_SQL_CMD(result); + return false; + } + perm_version(1); default: break; } diff --git a/server/src/manager/SqlDataManager.h b/server/src/manager/SqlDataManager.h index aa625ef..bb217cf 100644 --- a/server/src/manager/SqlDataManager.h +++ b/server/src/manager/SqlDataManager.h @@ -9,8 +9,8 @@ namespace ts { SqlDataManager(); virtual ~SqlDataManager(); - inline int get_database_version() const { return this->_database_version; } - inline int get_permissions_version() const { return this->_database_version; } + [[nodiscard]] inline int get_database_version() const { return this->_database_version; } + [[nodiscard]] inline int get_permissions_version() const { return this->_database_version; } bool initialize(std::string&); void finalize(); diff --git a/server/src/music/MusicBotManager.cpp b/server/src/music/MusicBotManager.cpp index dc5fd32..c23e814 100644 --- a/server/src/music/MusicBotManager.cpp +++ b/server/src/music/MusicBotManager.cpp @@ -26,6 +26,11 @@ void MusicBotManager::adjustTickPool() { tick_music.setThreads(min(config::threads::music::execute_limit, bots * config::threads::music::execute_per_bot)); } +void MusicBotManager::shutdown() { + tick_music.shutdown(); + load_music.shutdown(); +} + MusicBotManager::MusicBotManager(const shared_ptr& server) : handle(server) { } MusicBotManager::~MusicBotManager() { } diff --git a/server/src/music/MusicBotManager.h b/server/src/music/MusicBotManager.h index ecbae09..965fc1d 100644 --- a/server/src/music/MusicBotManager.h +++ b/server/src/music/MusicBotManager.h @@ -21,6 +21,7 @@ namespace ts { public: static threads::ThreadPool tick_music; static threads::ThreadPool load_music; + static void shutdown(); static void adjustTickPool(); diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index 708ae09..ac2aa6a 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -77,7 +77,9 @@ namespace terminal { else if(cmd.lcommand == "memflush") handleCommandMemFlush(cmd); else if(cmd.lcommand == "statsreset") - handleStatsReset(cmd); + handleCommandStatsReset(cmd); + else if(cmd.lcommand == "reload") + handleCommandReload(cmd); else logError("Missing command " + cmd.command + "/" + cmd.line); } @@ -100,6 +102,7 @@ namespace terminal { bool handleCommandHelp(TerminalCommand& args){ logMessage("§aAvariable commands:"); logMessage(" §7- §eend §7| §eshutdown"); + logMessage(" §7- §ereload config"); logMessage(" §7- §echat"); logMessage(" §7- §einfo"); logMessage(" §7- §epermgrant"); @@ -455,7 +458,7 @@ namespace terminal { } - extern bool handleStatsReset(TerminalCommand& cmd) { + extern bool handleCommandStatsReset(TerminalCommand& cmd) { serverInstance->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = 0; logMessage("Monthly statistics will be reset"); return true; @@ -493,5 +496,28 @@ namespace terminal { } return true; } + + + bool handleCommandReload(TerminalCommand& cmd) { + if(cmd.larguments.size() < 1 || cmd.larguments[0] != "config") { + logMessage("Invalid target. Available: config"); + return true; + } + + vector error; + if(!serverInstance->reloadConfig(error, true)) { + logError("Failed to reload instance ({}):", error.size()); + for(auto& msg : error) + logError(" - {}", msg); + } else if(!error.empty()) { + logMessage("Reloaded successfully. Messages:"); + for(auto& msg : error) + logMessage(" - {}", msg); + } else { + logMessage("Reloaded successfully."); + } + + return true; + } } } \ No newline at end of file diff --git a/server/src/terminal/CommandHandler.h b/server/src/terminal/CommandHandler.h index 9aa8b5a..c004273 100644 --- a/server/src/terminal/CommandHandler.h +++ b/server/src/terminal/CommandHandler.h @@ -33,6 +33,8 @@ namespace terminal { extern bool handleCommandPasswd(TerminalCommand&); - extern bool handleStatsReset(TerminalCommand&); + extern bool handleCommandStatsReset(TerminalCommand&); + + extern bool handleCommandReload(TerminalCommand&); } } \ No newline at end of file diff --git a/shared b/shared index 2cae73c..4d64f60 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 2cae73c51ad8f70e37b6dac9ca7781c8eee2fb20 +Subproject commit 4d64f60f189ff15ccb1ad20fd439b545ef887c3a