From d619d70a76db7a32e375a1e19ee8e6d831f01bfa Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 4 Sep 2025 12:05:22 +0200 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20Unterst=C3=BCtzung=20f=C3=BCr=20SSL?= =?UTF-8?q?/TLS=20in=20die=20Konfiguration=20und=20das=20Build-System=20ei?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- CMakeLists.txt | 6 + config/chatconfig.json | 5 +- deploy/deploy.sh | 10 + deploy/install_dependencies.sh | 2 +- deploy/setup-ssl.sh | 419 +++++++++++++++++++++++++++++++++ deploy/update_config.sh | 12 + src/core/ssl_server.cpp | 364 ++++++++++++++++++++++++++++ src/core/ssl_server.h | 100 ++++++++ src/main.cpp | 13 + 9 files changed, 929 insertions(+), 2 deletions(-) create mode 100755 deploy/setup-ssl.sh create mode 100644 src/core/ssl_server.cpp create mode 100644 src/core/ssl_server.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 58ce4eb..791c21c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,12 @@ endif() find_package(OpenSSL REQUIRED) target_link_libraries(yourchat OpenSSL::SSL OpenSSL::Crypto) +# Link libwebsockets for SSL/TLS WebSocket support +find_package(PkgConfig REQUIRED) +pkg_check_modules(LWS REQUIRED libwebsockets) +target_include_directories(yourchat PRIVATE ${LWS_INCLUDE_DIRS}) +target_link_libraries(yourchat ${LWS_LIBRARIES}) + add_executable(ws_probe tools/ws_probe.cpp) target_compile_features(ws_probe PRIVATE cxx_std_17) target_link_libraries(ws_probe pthread) diff --git a/config/chatconfig.json b/config/chatconfig.json index bf7bb5c..3f34c0d 100755 --- a/config/chatconfig.json +++ b/config/chatconfig.json @@ -1,6 +1,9 @@ { "server": { - "port": 1235 + "port": 1235, + "ssl_enabled": false, + "ssl_cert_path": "/etc/yourpart/server.crt", + "ssl_key_path": "/etc/yourpart/server.key" }, "database": { "user": "yourpart", diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 54cf52b..2ba6d70 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -40,6 +40,16 @@ echo "=== Schritt 4: Konfiguration aktualisieren ===" echo "Führe update_config.sh aus..." ./deploy/update_config.sh +echo "" +echo "=== Schritt 5: SSL-Setup (optional) ===" +echo "Möchten Sie SSL/TLS für sichere Verbindungen einrichten? (y/N)" +read -p "SSL einrichten? " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Führe SSL-Setup aus..." + ./deploy/setup-ssl.sh +fi + echo "" echo "=== Deployment abgeschlossen! ===" echo "" diff --git a/deploy/install_dependencies.sh b/deploy/install_dependencies.sh index 05a0d3d..af5ef80 100755 --- a/deploy/install_dependencies.sh +++ b/deploy/install_dependencies.sh @@ -34,7 +34,7 @@ sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-13 130 echo "=== Bibliotheken installieren ===" # Benötigte Bibliotheken -sudo apt install -y libssl-dev libjsoncpp-dev libpqxx-dev +sudo apt install -y libssl-dev libjsoncpp-dev libpqxx-dev libwebsockets-dev echo "=== PostgreSQL installieren (falls nicht vorhanden) ===" # PostgreSQL (falls Datenbank lokal laufen soll) diff --git a/deploy/setup-ssl.sh b/deploy/setup-ssl.sh new file mode 100755 index 0000000..ba2bbde --- /dev/null +++ b/deploy/setup-ssl.sh @@ -0,0 +1,419 @@ +#!/bin/bash + +# SSL/TLS Setup Script für YourChat +# Erstellt oder verwaltet SSL-Zertifikate für sichere Chat-Verbindungen +# Unterstützt Self-Signed Certificates und Let's Encrypt + +set -e + +# Farben für Logging +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +CERT_DIR="/etc/yourpart" +CERT_FILE="$CERT_DIR/server.crt" +KEY_FILE="$CERT_DIR/server.key" +CSR_FILE="$CERT_DIR/server.csr" + +# Let's Encrypt Verzeichnisse +LETSENCRYPT_DIR="/etc/letsencrypt/live" +LETSENCRYPT_CERT="$LETSENCRYPT_DIR/your-part.de/fullchain.pem" +LETSENCRYPT_KEY="$LETSENCRYPT_DIR/your-part.de/privkey.pem" + +# Apache2 Zertifikate (Ubuntu/Debian) +APACHE2_CERT_PATHS=( + "/etc/letsencrypt/live/your-part.de/fullchain.pem" + "/etc/letsencrypt/live/$(hostname)/fullchain.pem" + "/etc/apache2/ssl/apache.crt" + "/etc/httpd/ssl/apache.crt" + "/etc/ssl/certs/apache-selfsigned.crt" + "/etc/ssl/certs/ssl-cert-snakeoil.pem" +) + +APACHE2_KEY_PATHS=( + "/etc/letsencrypt/live/your-part.de/privkey.pem" + "/etc/letsencrypt/live/$(hostname)/privkey.pem" + "/etc/apache2/ssl/apache.key" + "/etc/httpd/ssl/apache.key" + "/etc/ssl/private/apache-selfsigned.key" + "/etc/ssl/private/ssl-cert-snakeoil.key" +) + +# Prüfe ob OpenSSL installiert ist +if ! command -v openssl &> /dev/null; then + log_error "OpenSSL ist nicht installiert!" + exit 1 +fi + +# Prüfe ob wir sudo-Rechte haben +if ! sudo -n true 2>/dev/null; then + log_info "Einige Operationen benötigen sudo-Rechte für SSL-Verzeichnisse..." +fi + +# Funktionen +setup_letsencrypt() { + log_info "Let's Encrypt Setup für your-part.de" + + # Prüfe ob certbot installiert ist + if ! command -v certbot &> /dev/null; then + log_error "Certbot ist nicht installiert!" + log_info "Installiere Certbot..." + if command -v apt &> /dev/null; then + sudo apt update + sudo apt install -y certbot + elif command -v zypper &> /dev/null; then + sudo zypper install -y certbot + else + log_error "Paketmanager nicht unterstützt. Installiere Certbot manuell." + exit 1 + fi + fi + + # Prüfe ob Let's Encrypt Zertifikate bereits existieren + if [ -f "$LETSENCRYPT_CERT" ] && [ -f "$LETSENCRYPT_KEY" ]; then + log_info "Let's Encrypt Zertifikate existieren bereits" + + # Prüfe Gültigkeit + if openssl x509 -in "$LETSENCRYPT_CERT" -text -noout &> /dev/null; then + log_success "Let's Encrypt Zertifikat ist gültig" + + # Zeige Zertifikats-Informationen + log_info "Let's Encrypt Zertifikats-Informationen:" + openssl x509 -in "$LETSENCRYPT_CERT" -text -noout | grep -E "(Subject:|Not Before|Not After|DNS:)" + + read -p "Möchten Sie die Zertifikate erneuern? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Zertifikate bleiben unverändert" + return 0 + fi + else + log_warning "Let's Encrypt Zertifikat ist ungültig, erstelle neue..." + fi + fi + + # Erstelle oder erneuere Let's Encrypt Zertifikat + log_info "Erstelle/erneuere Let's Encrypt Zertifikat für your-part.de..." + + # Prüfe ob Port 80 verfügbar ist (für HTTP-01 Challenge) + if ! sudo netstat -tlnp | grep -q ":80 "; then + log_warning "Port 80 ist nicht verfügbar. Stelle sicher, dass kein anderer Service läuft." + log_info "Oder verwende DNS-01 Challenge mit --dns-cloudflare oder ähnlich" + fi + + # Erstelle Zertifikat mit HTTP-01 Challenge + sudo certbot certonly --standalone -d your-part.de --non-interactive --agree-tos --email admin@your-part.de + + if [ $? -eq 0 ]; then + log_success "Let's Encrypt Zertifikat erfolgreich erstellt!" + + # Erstelle Symlinks zu den Zertifikaten + sudo ln -sf "$LETSENCRYPT_CERT" "$CERT_FILE" + sudo ln -sf "$LETSENCRYPT_KEY" "$KEY_FILE" + + # Setze korrekte Berechtigungen + sudo chown yourchat:yourchat "$CERT_FILE" "$KEY_FILE" + sudo chmod 644 "$CERT_FILE" + sudo chmod 600 "$KEY_FILE" + + # Zeige Zertifikats-Informationen + log_info "Let's Encrypt Zertifikats-Informationen:" + openssl x509 -in "$CERT_FILE" -text -noout | grep -E "(Subject:|Not Before|Not After|DNS:)" + + # Erstelle Auto-Renewal Cron Job + setup_auto_renewal + + else + log_error "Let's Encrypt Zertifikat konnte nicht erstellt werden!" + exit 1 + fi +} + +setup_apache2_certificates() { + log_info "Apache2 Zertifikate Setup" + + # Finde verfügbare Zertifikate + FOUND_CERT="" + FOUND_KEY="" + + for cert_path in "${APACHE2_CERT_PATHS[@]}"; do + if sudo test -f "$cert_path"; then + FOUND_CERT="$cert_path" + log_info "Gefundenes Zertifikat: $cert_path" + break + fi + done + + for key_path in "${APACHE2_KEY_PATHS[@]}"; do + if sudo test -f "$key_path"; then + FOUND_KEY="$key_path" + log_info "Gefundener Private Key: $key_path" + break + fi + done + + if [ -z "$FOUND_CERT" ] || [ -z "$FOUND_KEY" ]; then + log_error "Keine Apache2-Zertifikate gefunden!" + log_info "Verfügbare Pfade:" + for path in "${APACHE2_CERT_PATHS[@]}" "${APACHE2_KEY_PATHS[@]}"; do + if sudo test -f "$path"; then + log_info " ✓ $path" + else + log_info " ✗ $path" + fi + done + exit 1 + fi + + # Warnung für Snakeoil-Zertifikate + if [[ "$FOUND_CERT" == *"snakeoil"* ]]; then + log_warning "ACHTUNG: Snakeoil-Zertifikat erkannt!" + log_warning "Dieses Zertifikat ist nur für localhost gültig, nicht für your-part.de" + log_warning "Für Produktionsumgebungen sollten Sie Let's Encrypt verwenden" + echo "" + read -p "Möchten Sie trotzdem fortfahren? (y/N): " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Setup abgebrochen. Verwenden Sie Option 2 für Let's Encrypt." + exit 0 + fi + fi + + # Erstelle Symlinks zu den Apache2-Zertifikaten + log_info "Erstelle Symlinks zu Apache2-Zertifikaten..." + sudo ln -sf "$FOUND_CERT" "$CERT_FILE" + sudo ln -sf "$FOUND_KEY" "$KEY_FILE" + + # Setze korrekte Berechtigungen + sudo chown yourchat:yourchat "$CERT_FILE" "$KEY_FILE" + sudo chmod 644 "$CERT_FILE" + sudo chmod 600 "$KEY_FILE" + + log_success "Apache2-Zertifikate erfolgreich verlinkt!" + + # Zeige Zertifikats-Informationen + log_info "Apache2-Zertifikats-Informationen:" + openssl x509 -in "$CERT_FILE" -text -noout | grep -E "(Subject:|Not Before|Not After|DNS:)" + + # Prüfe ob es sich um Let's Encrypt-Zertifikate handelt + if [[ "$FOUND_CERT" == *"letsencrypt"* ]]; then + log_info "Let's Encrypt-Zertifikate erkannt, richte Auto-Renewal ein..." + setup_auto_renewal + else + log_warning "Self-Signed oder andere Zertifikate erkannt - kein Auto-Renewal eingerichtet" + fi +} + +setup_auto_renewal() { + log_info "Richte automatische Zertifikats-Erneuerung ein..." + + # Erstelle Renewal Script + sudo tee /etc/yourpart/renew-ssl.sh > /dev/null << 'EOF' +#!/bin/bash +# Automatische SSL-Zertifikats-Erneuerung für YourChat + +CERT_DIR="/etc/yourpart" +LETSENCRYPT_CERT="/etc/letsencrypt/live/your-part.de/fullchain.pem" +LETSENCRYPT_KEY="/etc/letsencrypt/live/your-part.de/privkey.pem" + +# Erneuere Zertifikat +certbot renew --quiet + +if [ $? -eq 0 ]; then + # Aktualisiere Symlinks + ln -sf "$LETSENCRYPT_CERT" "$CERT_DIR/server.crt" + ln -sf "$LETSENCRYPT_KEY" "$CERT_DIR/server.key" + + # Setze Berechtigungen + chown yourchat:yourchat "$CERT_DIR/server.crt" "$CERT_DIR/server.key" + chmod 644 "$CERT_DIR/server.crt" + chmod 600 "$CERT_DIR/server.key" + + # Starte YourChat neu + systemctl reload yourchat + + echo "$(date): SSL-Zertifikat erfolgreich erneuert" >> /var/log/yourchat/ssl-renewal.log +fi +EOF + + sudo chmod +x /etc/yourpart/renew-ssl.sh + + # Füge Cron Job hinzu (täglich um 2:30 Uhr) + (sudo crontab -l 2>/dev/null; echo "30 2 * * * /etc/yourpart/renew-ssl.sh") | sudo crontab - + + log_success "Automatische Erneuerung eingerichtet (täglich um 2:30 Uhr)" +} + +log_info "SSL/TLS Setup für YourChat" + +# Benutzerauswahl +echo "" +log_info "Wählen Sie den Zertifikatstyp:" +echo "1) Self-Signed Certificate (für Entwicklung/Testing)" +echo "2) Let's Encrypt Certificate (für Produktion)" +echo "3) Bestehende Let's Encrypt Zertifikate verwenden" +echo "4) Apache2-Zertifikate verwenden (empfohlen für Ubuntu)" +echo "" +read -p "Ihre Wahl (1-4): " -n 1 -r +echo "" + +case $REPLY in + 1) + log_info "Self-Signed Certificate wird erstellt..." + CERT_TYPE="self-signed" + ;; + 2) + log_info "Let's Encrypt Certificate wird erstellt..." + CERT_TYPE="letsencrypt" + ;; + 3) + log_info "Bestehende Let's Encrypt Zertifikate werden verwendet..." + CERT_TYPE="existing-letsencrypt" + ;; + 4) + log_info "Apache2-Zertifikate werden verwendet..." + CERT_TYPE="apache2" + ;; + *) + log_error "Ungültige Auswahl!" + exit 1 + ;; +esac + +# Erstelle Zertifikats-Verzeichnis falls nicht vorhanden +if [ ! -d "$CERT_DIR" ]; then + log_info "Erstelle Zertifikats-Verzeichnis: $CERT_DIR" + sudo mkdir -p "$CERT_DIR" +fi + +# Führe entsprechenden Setup-Typ aus +case $CERT_TYPE in + "self-signed") + # Prüfe ob bereits Zertifikate existieren + if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then + log_info "Zertifikate existieren bereits" + + # Prüfe Gültigkeit der Zertifikate + if openssl x509 -in "$CERT_FILE" -text -noout &> /dev/null; then + log_success "Zertifikat ist gültig" + + # Zeige Zertifikats-Informationen + log_info "Zertifikats-Informationen:" + openssl x509 -in "$CERT_FILE" -text -noout | grep -E "(Subject:|Not Before|Not After|DNS:)" + + read -p "Möchten Sie neue Zertifikate erstellen? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Zertifikate bleiben unverändert" + exit 0 + fi + else + log_warning "Zertifikat ist ungültig, erstelle neue..." + fi + fi + + log_info "Erstelle neue Self-Signed SSL-Zertifikate..." + + # Erstelle Private Key + log_info "Erstelle Private Key..." + sudo openssl genrsa -out "$KEY_FILE" 2048 + sudo chmod 600 "$KEY_FILE" + sudo chown yourchat:yourchat "$KEY_FILE" + + # Erstelle Certificate Signing Request (CSR) + log_info "Erstelle Certificate Signing Request..." + sudo openssl req -new -key "$KEY_FILE" -out "$CSR_FILE" -subj "/C=DE/ST=Germany/L=Berlin/O=YourChat/OU=IT/CN=your-part.de" + + # Erstelle Self-Signed Certificate + log_info "Erstelle Self-Signed Certificate..." + sudo openssl x509 -req -days 365 -in "$CSR_FILE" -signkey "$KEY_FILE" -out "$CERT_FILE" + + # Setze korrekte Berechtigungen + sudo chmod 644 "$CERT_FILE" + sudo chown yourchat:yourchat "$CERT_FILE" + + # Lösche CSR-Datei (nicht mehr benötigt) + sudo rm -f "$CSR_FILE" + + log_success "Self-Signed SSL-Zertifikate erfolgreich erstellt!" + ;; + + "letsencrypt") + setup_letsencrypt + ;; + + "existing-letsencrypt") + if [ -f "$LETSENCRYPT_CERT" ] && [ -f "$LETSENCRYPT_KEY" ]; then + log_info "Verwende bestehende Let's Encrypt Zertifikate..." + + # Erstelle Symlinks zu den Zertifikaten + sudo ln -sf "$LETSENCRYPT_CERT" "$CERT_FILE" + sudo ln -sf "$LETSENCRYPT_KEY" "$KEY_FILE" + + # Setze korrekte Berechtigungen + sudo chown yourchat:yourchat "$CERT_FILE" "$KEY_FILE" + sudo chmod 644 "$CERT_FILE" + sudo chmod 600 "$KEY_FILE" + + log_success "Let's Encrypt Zertifikate erfolgreich verlinkt!" + + # Richte Auto-Renewal ein + setup_auto_renewal + else + log_error "Let's Encrypt Zertifikate nicht gefunden in $LETSENCRYPT_DIR" + log_info "Führen Sie zuerst 'certbot certonly' aus oder wählen Sie Option 2" + exit 1 + fi + ;; + + "apache2") + setup_apache2_certificates + ;; +esac + +# Zeige Zertifikats-Informationen +log_info "Zertifikats-Informationen:" +openssl x509 -in "$CERT_FILE" -text -noout | grep -E "(Subject:|Not Before|Not After|DNS:)" + +log_info "" +log_info "Nächste Schritte:" +log_info "1. Aktiviere SSL in der Konfiguration:" +log_info " ./deploy/update_config.sh" +log_info "2. Starte YourChat neu:" +log_info " sudo systemctl restart yourchat" +log_info "3. Verbinde dich mit:" +log_info " wss://your-part.de:1235" +log_info "" + +case $CERT_TYPE in + "self-signed") + log_warning "Hinweis: Dies ist ein Self-Signed Certificate!" + log_warning "Für Produktionsumgebungen verwenden Sie Let's Encrypt oder Apache2-Zertifikate." + ;; + "apache2") + log_success "Apache2-Zertifikate erfolgreich konfiguriert!" + log_info "Diese Zertifikate werden automatisch von Apache2 verwaltet." + ;; + *) + log_success "Let's Encrypt Zertifikat ist produktionsbereit!" + ;; +esac diff --git a/deploy/update_config.sh b/deploy/update_config.sh index e11f93e..01ff3fe 100755 --- a/deploy/update_config.sh +++ b/deploy/update_config.sh @@ -137,6 +137,18 @@ def merge_json(source_file, target_file, output_file): if 'database' not in db_config: db_config['database'] = database + # Spezielle Behandlung für server-Konfiguration (SSL) + if 'server' in merged_data and isinstance(merged_data['server'], dict): + server_config = merged_data['server'] + + # SSL-Konfiguration hinzufügen falls nicht vorhanden + if 'ssl_enabled' not in server_config: + server_config['ssl_enabled'] = False + if 'ssl_cert_path' not in server_config: + server_config['ssl_cert_path'] = "/etc/yourpart/server.crt" + if 'ssl_key_path' not in server_config: + server_config['ssl_key_path'] = "/etc/yourpart/server.key" + with open(output_file, 'w') as f: json.dump(merged_data, f, indent=4, ensure_ascii=False) diff --git a/src/core/ssl_server.cpp b/src/core/ssl_server.cpp new file mode 100644 index 0000000..c14b4ab --- /dev/null +++ b/src/core/ssl_server.cpp @@ -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 +#include +#include + +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, std::shared_ptr 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 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(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(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(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(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 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 lock(_connectionsMutex); + _connections.erase(token); + _users.erase(token); + std::cout << "[YourChat] Verbindung für Token " << token << " entfernt" << std::endl; +} + +std::shared_ptr SSLServer::getUserByToken(const std::string& token) { + std::shared_lock 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 lock(_connectionsMutex); + for (auto& pair : _connections) { + if (lws_get_socket_fd(pair.second) == socket) { + auto *ud = reinterpret_cast(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 SSLServer::roomList() { + std::vector 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 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 diff --git a/src/core/ssl_server.h b/src/core/ssl_server.h new file mode 100644 index 0000000..ba966be --- /dev/null +++ b/src/core/ssl_server.h @@ -0,0 +1,100 @@ +#ifndef YC_LIB_SSL_SERVER_H +#define YC_LIB_SSL_SERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, std::shared_ptr database); + ~SSLServer(); + + void run(); + void stop(); + void createRooms(); + + // Room management + std::vector roomList(); + bool roomAllowed(const std::string& roomName, const std::string& userName, const std::string& password); + bool changeRoom(std::shared_ptr 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 getUserByToken(const std::string& token); + + std::shared_ptr _config; + std::shared_ptr _database; + std::vector> _rooms; + + // SSL/TLS settings + bool _useSSL; + std::string _certPath; + std::string _keyPath; + int _port; + + // Server state + std::atomic _running{false}; + struct lws_context* _context = nullptr; + std::thread _serverThread; + std::thread _messageThread; + + // Message queue + std::mutex _queueMutex; + std::condition_variable _queueCV; + std::queue _messageQueue; + + // Connections + std::shared_mutex _connectionsMutex; + std::unordered_map _connections; + std::unordered_map> _users; + + // Static instance for callbacks + static SSLServer* _instance; + static struct lws_protocols _protocols[]; +}; + +} // namespace Lib +} // namespace Yc + +#endif // YC_LIB_SSL_SERVER_H diff --git a/src/main.cpp b/src/main.cpp index ad33cb3..136946b 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,13 +2,26 @@ #include "core/config.h" #include "core/server.h" #include "lib/database.h" +#include // main function int main(int, char **) { auto config = std::make_shared(); auto database = std::make_shared(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(config, database); server->createRooms(config->group("rooms")); server->run(); + return 0; }