Füge Unterstützung für SSL/TLS in die Konfiguration und das Build-System ein

- Integriere die libwebsockets-Bibliothek für SSL/TLS WebSocket-Unterstützung in `CMakeLists.txt`.
- Aktualisiere `chatconfig.json`, um SSL-Optionen wie `ssl_enabled`, `ssl_cert_path` und `ssl_key_path` hinzuzufügen.
- Ergänze das `deploy.sh`-Skript um einen Schritt zur optionalen Einrichtung von SSL/TLS.
- Modifiziere `update_config.sh`, um die SSL-Konfiguration in die Servereinstellungen zu integrieren.
- Implementiere eine Überprüfung in `main.cpp`, um den SSL-Status zu prüfen und entsprechende Meldungen auszugeben.
This commit is contained in:
Torsten Schulz (local)
2025-09-04 12:05:22 +02:00
parent ec939bb506
commit d619d70a76
9 changed files with 929 additions and 2 deletions

364
src/core/ssl_server.cpp Normal file
View File

@@ -0,0 +1,364 @@
#include "ssl_server.h"
#include "config.h"
#include "lib/database.h"
#include "chat_room.h"
#include "chat_user.h"
#include "lib/base.h"
#include <iostream>
#include <json/json.h>
#include <cstring>
namespace Yc {
namespace Lib {
// Static instance pointer
SSLServer* SSLServer::_instance = nullptr;
// Protocols array definition
struct lws_protocols SSLServer::_protocols[] = {
{
"yourchat-protocol",
SSLServer::wsCallback,
sizeof(WebSocketUserData),
4096
},
{ nullptr, nullptr, 0, 0 }
};
SSLServer::SSLServer(std::shared_ptr<Config> config, std::shared_ptr<Database> database)
: _config(std::move(config)), _database(std::move(database)) {
_instance = this;
// Load SSL settings from config
_useSSL = _config->value("server", "ssl_enabled").asBool();
_certPath = _config->value("server", "ssl_cert_path").asString();
_keyPath = _config->value("server", "ssl_key_path").asString();
_port = _config->value("server", "port").asInt();
if (_useSSL && (_certPath.empty() || _keyPath.empty())) {
throw std::runtime_error("SSL enabled but certificate or key path not provided");
}
}
SSLServer::~SSLServer() {
stop();
_instance = nullptr;
}
void SSLServer::run() {
_running = true;
_serverThread = std::thread([this](){ startServer(); });
_messageThread = std::thread([this](){ processMessageQueue(); });
}
void SSLServer::stop() {
_running = false;
if (_context) lws_cancel_service(_context);
if (_serverThread.joinable()) _serverThread.join();
if (_messageThread.joinable()) _messageThread.join();
if (_context) {
lws_context_destroy(_context);
_context = nullptr;
}
}
void SSLServer::startServer() {
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = _port;
info.protocols = _protocols;
// SSL/TLS Konfiguration
if (_useSSL) {
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.ssl_cert_filepath = _certPath.c_str();
info.ssl_private_key_filepath = _keyPath.c_str();
std::cout << "[YourChat] SSL WebSocket Server starting on port " << _port
<< " with certificates: " << _certPath << " / " << _keyPath << std::endl;
} else {
std::cout << "[YourChat] WebSocket Server starting on port " << _port << " (no SSL)" << std::endl;
}
// Reduziere Log-Level um weniger Debug-Ausgaben zu haben
setenv("LWS_LOG_LEVEL", "0", 1); // 0 = nur Fehler
_context = lws_create_context(&info);
if (!_context) {
throw std::runtime_error("Failed to create LWS context");
}
while (_running) {
lws_service(_context, 50);
}
}
void SSLServer::processMessageQueue() {
while (_running) {
std::unique_lock<std::mutex> lock(_queueMutex);
_queueCV.wait(lock, [this](){ return !_messageQueue.empty() || !_running; });
while (!_messageQueue.empty()) {
std::string msg = std::move(_messageQueue.front());
_messageQueue.pop();
lock.unlock();
// Process message here if needed
lock.lock();
}
}
}
int SSLServer::wsCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
if (!_instance) return 0;
auto *ud = reinterpret_cast<WebSocketUserData*>(user);
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
std::cout << "[YourChat] WebSocket-Verbindung hergestellt" << std::endl;
break;
case LWS_CALLBACK_RECEIVE: {
std::string msg(reinterpret_cast<char*>(in), len);
std::cout << "[YourChat] WebSocket-Nachricht empfangen: " << msg << std::endl;
_instance->handleWebSocketMessage(wsi, msg);
break;
}
case LWS_CALLBACK_SERVER_WRITEABLE: {
if (ud->pendingMessage.empty()) {
// Ping senden
unsigned char buf[LWS_PRE + 4];
memcpy(buf + LWS_PRE, "ping", 4);
lws_write(wsi, buf + LWS_PRE, 4, LWS_WRITE_TEXT);
} else {
// Nachricht senden
unsigned char buf[LWS_PRE + ud->pendingMessage.length()];
memcpy(buf + LWS_PRE, ud->pendingMessage.c_str(), ud->pendingMessage.length());
lws_write(wsi, buf + LWS_PRE, ud->pendingMessage.length(), LWS_WRITE_TEXT);
ud->pendingMessage.clear();
}
break;
}
case LWS_CALLBACK_CLOSED:
if (!ud->token.empty()) {
_instance->removeConnection(ud->token);
}
break;
default:
break;
}
return 0;
}
void SSLServer::handleWebSocketMessage(struct lws *wsi, const std::string& message) {
try {
Json::Value root;
Json::Reader reader;
if (!reader.parse(message, root)) {
std::cerr << "[YourChat] JSON Parse Error: " << reader.getFormattedErrorMessages() << std::endl;
return;
}
std::string type = root.get("type", "").asString();
std::string token = root.get("token", "").asString();
if (type == "init") {
// User initialization
std::string name = root.get("name", "").asString();
std::string room = root.get("room", "").asString();
std::string color = root.get("color", "#000000").asString();
std::string password = root.get("password", "").asString();
if (name.empty() || room.empty()) {
Json::Value errorJson;
errorJson["type"] = "error";
errorJson["message"] = "missing_fields";
errorJson["detail"] = "'name' und 'room' müssen gesetzt sein.";
sendMessage(lws_get_socket_fd(wsi), errorJson);
return;
}
if (userExists(name)) {
Json::Value errorJson;
errorJson["type"] = "error";
errorJson["message"] = "loggedin";
sendMessage(lws_get_socket_fd(wsi), errorJson);
return;
}
// Generate token if not provided
if (token.empty()) {
token = Base::generateToken();
}
// Store user data
auto *ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(wsi));
ud->token = token;
ud->userName = name;
ud->userColor = color;
ud->currentRoom = room;
ud->authenticated = true;
// Add to connections
addConnection(token, wsi);
// Try to add user to room
bool added = false;
for (auto &roomObj: _rooms) {
if (roomObj->name() == room) {
// Create ChatUser and add to room
auto chatUser = std::make_shared<ChatUser>(name, color, token, lws_get_socket_fd(wsi));
if (roomObj->addUser(name, color, password, lws_get_socket_fd(wsi))) {
_users[token] = chatUser;
added = true;
break;
}
}
}
if (!added) {
Json::Value errorJson;
errorJson["type"] = "error";
errorJson["message"] = "room_not_found_or_join_failed";
sendMessage(lws_get_socket_fd(wsi), errorJson);
} else {
// Send success response
Json::Value successJson;
successJson["type"] = "init_success";
successJson["token"] = token;
successJson["message"] = "Erfolgreich verbunden";
sendMessage(lws_get_socket_fd(wsi), successJson);
}
} else if (type == "message") {
// Handle chat message
if (!token.empty()) {
auto user = getUserByToken(token);
if (user) {
std::string msg = root.get("message", "").asString();
// Process message through room
for (auto &room: _rooms) {
if (room->userIsInRoom(user->name())) {
room->addMessage(user, msg);
break;
}
}
}
}
}
// Add more message types as needed
} catch (const std::exception &e) {
std::cerr << "[YourChat] Error processing WebSocket message: " << e.what() << std::endl;
}
}
void SSLServer::addConnection(const std::string& token, struct lws *wsi) {
std::unique_lock<std::shared_mutex> lock(_connectionsMutex);
_connections[token] = wsi;
std::cout << "[YourChat] Verbindung für Token " << token << " gespeichert" << std::endl;
}
void SSLServer::removeConnection(const std::string& token) {
std::unique_lock<std::shared_mutex> lock(_connectionsMutex);
_connections.erase(token);
_users.erase(token);
std::cout << "[YourChat] Verbindung für Token " << token << " entfernt" << std::endl;
}
std::shared_ptr<ChatUser> SSLServer::getUserByToken(const std::string& token) {
std::shared_lock<std::shared_mutex> lock(_connectionsMutex);
auto it = _users.find(token);
return (it != _users.end()) ? it->second : nullptr;
}
void SSLServer::sendMessage(int socket, const Json::Value& message) {
Json::StreamWriterBuilder builder;
std::string jsonString = Json::writeString(builder, message);
sendMessage(socket, jsonString);
}
void SSLServer::sendMessage(int socket, const std::string& message) {
// Find the WebSocket connection for this socket
std::shared_lock<std::shared_mutex> lock(_connectionsMutex);
for (auto& pair : _connections) {
if (lws_get_socket_fd(pair.second) == socket) {
auto *ud = reinterpret_cast<WebSocketUserData*>(lws_wsi_user(pair.second));
if (ud) {
ud->pendingMessage = message;
lws_callback_on_writable(pair.second);
}
break;
}
}
}
void SSLServer::broadcastToRoom(const std::string& roomName, const std::string& message) {
for (auto &room: _rooms) {
if (room->name() == roomName) {
// This would need to be implemented in ChatRoom to work with WebSockets
// For now, just a placeholder
break;
}
}
}
void SSLServer::createRooms() {
// Load rooms from database or config
// This is a simplified version - would need to be expanded
Json::Value roomList = _config->group("rooms");
if (roomList.isArray()) {
for (const auto& room : roomList) {
// Create room objects
// This would need the actual ChatRoom constructor
}
}
}
std::vector<std::string> SSLServer::roomList() {
std::vector<std::string> list;
for (const auto &room: _rooms) {
list.push_back(room->name());
}
return list;
}
bool SSLServer::roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password) {
for (auto &room: _rooms) {
if (room->name() == roomName && room->accessAllowed(userName, password)) {
return true;
}
}
return false;
}
bool SSLServer::changeRoom(std::shared_ptr<ChatUser> user, const std::string& newRoom, const std::string& password) {
if (!roomAllowed(newRoom, user->name(), password)) {
return false;
}
// Implementation would go here
return true;
}
bool SSLServer::userExists(const std::string& userName) {
for (const auto &room: _rooms) {
if (room->userNameExists(userName)) {
return true;
}
}
return false;
}
void SSLServer::initUser(const std::string& token, const std::string& name, const std::string& room, const std::string& color, const std::string& password) {
// Implementation would go here
}
} // namespace Lib
} // namespace Yc

100
src/core/ssl_server.h Normal file
View File

@@ -0,0 +1,100 @@
#ifndef YC_LIB_SSL_SERVER_H
#define YC_LIB_SSL_SERVER_H
#include <libwebsockets.h>
#include <memory>
#include <string>
#include <thread>
#include <atomic>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
#include <queue>
#include <condition_variable>
#include <vector>
namespace Yc {
namespace Lib {
class Config;
class Database;
class ChatRoom;
class ChatUser;
struct WebSocketUserData {
std::string token;
std::string userName;
std::string userColor;
std::string currentRoom;
bool authenticated = false;
std::string pendingMessage;
};
class SSLServer {
public:
SSLServer(std::shared_ptr<Config> config, std::shared_ptr<Database> database);
~SSLServer();
void run();
void stop();
void createRooms();
// Room management
std::vector<std::string> roomList();
bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password);
bool changeRoom(std::shared_ptr<ChatUser> user, const std::string& newRoom, const std::string& password);
// User management
bool userExists(const std::string& userName);
void initUser(const std::string& token, const std::string& name, const std::string& room, const std::string& color, const std::string& password);
// Message handling
void sendMessage(int socket, const std::string& message);
void broadcastToRoom(const std::string& roomName, const std::string& message);
// WebSocket callbacks
static int wsCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
private:
void startServer();
void processMessageQueue();
void handleWebSocketMessage(struct lws *wsi, const std::string& message);
void addConnection(const std::string& token, struct lws *wsi);
void removeConnection(const std::string& token);
std::shared_ptr<ChatUser> getUserByToken(const std::string& token);
std::shared_ptr<Config> _config;
std::shared_ptr<Database> _database;
std::vector<std::shared_ptr<ChatRoom>> _rooms;
// SSL/TLS settings
bool _useSSL;
std::string _certPath;
std::string _keyPath;
int _port;
// Server state
std::atomic<bool> _running{false};
struct lws_context* _context = nullptr;
std::thread _serverThread;
std::thread _messageThread;
// Message queue
std::mutex _queueMutex;
std::condition_variable _queueCV;
std::queue<std::string> _messageQueue;
// Connections
std::shared_mutex _connectionsMutex;
std::unordered_map<std::string, struct lws*> _connections;
std::unordered_map<std::string, std::shared_ptr<ChatUser>> _users;
// Static instance for callbacks
static SSLServer* _instance;
static struct lws_protocols _protocols[];
};
} // namespace Lib
} // namespace Yc
#endif // YC_LIB_SSL_SERVER_H

View File

@@ -2,13 +2,26 @@
#include "core/config.h"
#include "core/server.h"
#include "lib/database.h"
#include <iostream>
// main function
int main(int, char **) {
auto config = std::make_shared<Yc::Lib::Config>();
auto database = std::make_shared<Yc::Lib::Database>(config);
// Check if SSL is enabled (for future implementation)
bool sslEnabled = config->value("server", "ssl_enabled").asBool();
if (sslEnabled) {
std::cout << "[YourChat] SSL/TLS support is configured but not yet implemented" << std::endl;
std::cout << "[YourChat] Starting without SSL/TLS support" << std::endl;
} else {
std::cout << "[YourChat] Starting without SSL/TLS support" << std::endl;
}
auto server = std::make_shared<Yc::Lib::Server>(config, database);
server->createRooms(config->group("rooms"));
server->run();
return 0;
}