From 0d3d3cfc53b6fca33b0e86e0008ede564dc26464 Mon Sep 17 00:00:00 2001
From: WolverinDEV <git@mcgalaxy.de>
Date: Fri, 22 Nov 2019 20:51:00 +0100
Subject: [PATCH] A lot of updates

---
 MusicBot/Protocol.md                          | 249 -----------------
 MusicBot/src/MusicPlayer.cpp                  |   8 +-
 license/CMakeLists.txt                        |  47 +++-
 license/LicenseServerMain.cpp                 |  61 +++-
 license/MySQLLibSSLFix.c                      | 107 +++++++
 license/packets/LicenseRequest.proto          |   9 +
 license/server/LicenseServer.cpp              |  10 +-
 license/server/LicenseServer.h                |   8 +
 license/server/LicenseServerHandler.cpp       |  21 +-
 license/server/WebAPI.cpp                     |   3 -
 license/shared/License.h                      |   4 +-
 license/shared/LicenseRequest.cpp             |  17 +-
 license/shared/LicenseRequest.h               |  15 +-
 license/shared/LicenseRequestHandler.cpp      |  13 +-
 music                                         |   2 +-
 server/main.cpp                               |  24 +-
 server/src/Configuration.cpp                  | 158 ++++++++---
 server/src/Configuration.h                    |   1 +
 server/src/InstanceHandler.cpp                | 261 ++++++++++++++----
 server/src/InstanceHandler.h                  |   9 +-
 server/src/ShutdownHelper.cpp                 |  25 +-
 .../client/ConnectedClientCommandHandler.cpp  |   4 +-
 server/src/lincense/LicenseHelper.cpp         |   9 +
 server/src/lincense/LicenseHelper.h           |   1 +
 server/src/manager/SqlDataManager.cpp         |  14 +-
 server/src/manager/SqlDataManager.h           |   4 +-
 server/src/music/MusicBotManager.cpp          |   5 +
 server/src/music/MusicBotManager.h            |   1 +
 server/src/terminal/CommandHandler.cpp        |  30 +-
 server/src/terminal/CommandHandler.h          |   4 +-
 shared                                        |   2 +-
 31 files changed, 700 insertions(+), 426 deletions(-)
 delete mode 100644 MusicBot/Protocol.md
 create mode 100644 license/MySQLLibSSLFix.c

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": <PacketType>,
-    "data": [
-        {
-            <key>: 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`: <msg>
-    * `type`: {info|error}
-
-
-#### Server `reqError`:
-The server sends this if you applay a invalid request
-* **[1]**
-    * `message`: <msg>
-    * `requestId`: <reqestId>
-
-
-#### 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`: <string|Current playing title etc. May need to be inproved> 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<void(MusicEvent)>& 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<music::manager::PlayerProvider> &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 <iostream>
-#include <shared/License.h>
+#include <openssl/bio.h>
 #include <server/LicenseServer.h>
 #include <log/LogUtils.h>
 #include <CXXTerminal/Terminal.h>
 #include <sql/mysql/MySQL.h>
-#include <pipes/misc/http.h>
 #include "server/WebAPI.h"
 #include "server/StatisticManager.h"
 #include <event2/thread.h>
 #include "server/UserManager.h"
 
-#include <misc/base64.h>
 #include <misc/digest.h>
-#include <fstream>
+#include <misc/hex.h>
 
 using namespace std;
 using namespace std::chrono;
@@ -43,6 +41,59 @@ shared_ptr<license::web::WebStatistics> web_server;
 shared_ptr<LicenseServer> license_server;
 shared_ptr<UserManager> user_manager;
 
+inline std::shared_ptr<WebCertificate> 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> bio{nullptr};
+	const uint8_t* mem_ptr{nullptr};
+	size_t length{0};
+
+	{
+		bio = shared_ptr<BIO>(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>(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<WebCertificate>();
+	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<LicenseServer>(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 <openssl/aes.h>
+#include <openssl/ssl.h>
+
+#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<server::LicenseManager>&, const std::shared_ptr<stats::StatisticManager>& /* stats */, const std::shared_ptr<web::WebStatistics>& /* web stats */, const std::shared_ptr<UserManager>& /* user manager */);
@@ -75,6 +81,8 @@ namespace license {
 				std::lock_guard lock(this->lock);
 				return currentClients;
 			}
+
+			std::shared_ptr<WebCertificate> web_certificate{nullptr};
         private:
             void unregisterClient(const std::shared_ptr<ConnectedClient>&);
 		    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 <misc/endianness.h>
 #include <misc/base64.h>
+#include <misc/hex.h>
 #include <log/LogUtils.h>
 #include <LicenseManager.pb.h>
 #include <shared/License.h>
@@ -224,7 +225,7 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &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<ConnectedClient> &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<license::web::WebStatistics::Client> &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> license = nullptr;
-        std::shared_ptr<ServerInfo> info = nullptr;
+		std::shared_ptr<License> license{nullptr};
+        std::shared_ptr<ServerInfo> 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<std::shared_ptr<LicenseRequestResponse>> ResponseFuture;
@@ -88,6 +96,7 @@ namespace license {
 
             void sendPacket(const protocol::packet&);
 
+            std::function<void(const WebCertificate&)> callback_update_certificate{nullptr};
             bool verbose = true;
         private:
             std::shared_ptr<LicenseRequestData> 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<string,deque<string>>& map, const std::deque<std::shared
 	}
 }
 
-void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBinding>>& bindings) {
+void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBinding>>& 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<string, deque<string>>& comme
 std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license);
 
 #define CURRENT_CONFIG_VERSION 14
+static std::string _config_path;
 vector<string> config::parseConfig(const std::string& path) {
-	//FIXME test for premium!
+	_config_path = path;
+
     vector<string> errors;
     saveConfig = false;
 
@@ -426,15 +431,10 @@ vector<string> 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<string> 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<string> config::parseConfig(const std::string& path) {
     return errors;
 }
 
+std::vector<std::string> config::reload() {
+
+	vector<string> 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<EntryBinding>& _entry, std::string& target, const std::string& default_value) {
 	_entry->default_value = [default_value]() -> std::deque<std::string> { return { default_value }; };
 	_entry->value_description = [] { return "The value must be a string"; };
@@ -760,7 +802,7 @@ inline std::string join_path(const deque<string>& stack, const std::string& entr
 #define CREATE_BINDING(name, _flags) \
 	auto binding = make_shared<EntryBinding>(); \
 	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<string>& 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> config::create_bindings() {
             	};
 		        binding->default_value = []() -> deque<string> { 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<std::shared_ptr<EntryBinding>> config::create_bindings() {
 				weak_ptr<EntryBinding> _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<std::shared_ptr<EntryBinding>> config::create_bindings() {
 					        continue;
 				        }
 
-				        config::web::ssl::certificates.push_back({it->first.as<string>(), node_key.as<string>(), node_cert.as<string>()});
+				        config::web::ssl::certificates.emplace_back(it->first.as<string>(), node_key.as<string>(), node_cert.as<string>());
 			        }
 		        };
 	        }
@@ -1280,14 +1332,16 @@ std::deque<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::shared_ptr<EntryBinding>> 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<std::string> parseConfig(const std::string& /* path */);
+	    extern std::vector<std::string> reload();
         extern std::deque<std::shared_ptr<EntryBinding>> 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 <misc/digest.h>
 #include <misc/base64.h>
+#include <misc/hex.h>
 #include <misc/rnd.h>
 #include <misc/strobf.h>
 #include <jemalloc/jemalloc.h>
@@ -28,7 +29,6 @@
 #endif
 #include <unistd.h>
 #undef _POSIX_SOURCE
-#include <stdio.h>
 
 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<threads::Scheduler>(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<string> split_hosts(const std::string& message, char delimiter) {
 	vector<string> result;
 	size_t found, index = 0;
@@ -275,6 +258,19 @@ bool InstanceHandler::startInstance() {
 		return false;
 	}
 
+	{
+		vector<string> 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<string>();
@@ -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>(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>(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 <sys/ioctl.h>
 #include <net/if.h>
-#include <unistd.h>
 #include <netinet/in.h>
-#include <string.h>
 
 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<license::LicenseRequestData> InstanceHandler::generateLicenseData() {
-	auto response = make_shared<license::LicenseRequestData>();
-	response->license = config::license;
-	response->servers_online = this->voiceServerManager->runningServers();
+	auto request = make_shared<license::LicenseRequestData>();
+	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<uint64_t>();
-	response->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
-	response->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
-	response->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
+	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<uint64_t>();
+	request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
+	request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
+	request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
+
+	request->web_certificate_revision = this->web_cert_revision;
 
     {
         auto info = make_shared<license::ServerInfo>();
@@ -672,7 +651,7 @@ std::shared_ptr<license::LicenseRequestData> 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<license::LicenseRequestData> 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<std::string>& 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>(
+							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>(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<ts::server::InternalClient> getInitalServerAdmin(){ return globalServerAdmin; }
+                std::shared_ptr<ts::server::InternalClient> getInitialServerAdmin(){ return globalServerAdmin; }
                 std::shared_ptr<ts::GroupManager> getGroupManager(){ return groupManager; }
 
 		        std::shared_ptr<ts::ServerChannelTree> getChannelTree() { return this->default_tree; }
@@ -51,6 +51,9 @@ namespace ts {
                 void executeTick(TSServer*);
                 void cancelExecute(TSServer*);
 
+                bool reloadConfig(std::vector<std::string>& /* errors */, bool /* reload file */);
+                void setWebCertRoot(const std::string& /* key */, const std::string& /* certificate */, const std::string& /* revision */);
+
 		        const std::shared_ptr<ConnectedClient>& musicRoot() { return this->_musicRoot; }
 
 		        std::chrono::milliseconds calculateSpokenTime();
@@ -112,6 +115,8 @@ namespace ts {
 		        std::shared_ptr<permission::PermissionNameMapper> permission_mapper = nullptr;
                 std::shared_ptr<TeamSpeakLicense> 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<ConnectedClient>(serverInstance->getInitalServerAdmin()), true);
+		        ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast<ConnectedClient>(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<ConnectedClient>(serverInstance->getInitalServerAdmin()), true);
+				    ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast<ConnectedClient>(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 <log/LogUtils.h>
 #include <misc/strobf.h>
+#include <misc/hex.h>
 #include <src/Configuration.h>
 #include <arpa/inet.h>
 #include <src/SignalHandler.h>
@@ -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::LicenceRequest>(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::TSServer>& 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<string> 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