A lot of updates

This commit is contained in:
WolverinDEV 2019-11-22 20:51:00 +01:00
parent 44fe958f31
commit 0d3d3cfc53
31 changed files with 700 additions and 426 deletions

View File

@ -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`

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

107
license/MySQLLibSSLFix.c Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;

2
music

@ -1 +1 @@
Subproject commit af7918a243bcdfb9d32ec5a220cc2a6018bbe325
Subproject commit ef030797a2a997704b9bd126cbbe1d209205e8f0

View File

@ -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
*/
}

View File

@ -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();
}
}
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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();
};
}
}

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -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&);
};
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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() { }

View File

@ -21,6 +21,7 @@ namespace ts {
public:
static threads::ThreadPool tick_music;
static threads::ThreadPool load_music;
static void shutdown();
static void adjustTickPool();

View File

@ -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;
}
}
}

View File

@ -33,6 +33,8 @@ namespace terminal {
extern bool handleCommandPasswd(TerminalCommand&);
extern bool handleStatsReset(TerminalCommand&);
extern bool handleCommandStatsReset(TerminalCommand&);
extern bool handleCommandReload(TerminalCommand&);
}
}

2
shared

@ -1 +1 @@
Subproject commit 2cae73c51ad8f70e37b6dac9ca7781c8eee2fb20
Subproject commit 4d64f60f189ff15ccb1ad20fd439b545ef887c3a