prepare("INSERT INTO audit_logs (user, action, details) VALUES (?, ?, ?)"); $stmt->execute([$user, $action, $details]); } function generateTOTPSecret() { $b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $s = ""; for ($i = 0; $i < 16; $i++) $s .= $b32[random_int(0, 31)]; return $s; } function verifyTOTP($secret, $code) { if (empty($secret) || strlen($code) != 6) return false; $b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; $secret = strtoupper($secret); $dec = ""; for ($i = 0; $i < strlen($secret); $i++) { $n = strpos($b32, $secret[$i]); if ($n === false) return false; $dec .= str_pad(base_convert($n, 10, 2), 5, '0', STR_PAD_LEFT); } $dec = str_split($dec, 8); $bin = ""; foreach ($dec as $b) $bin .= chr(bindec($b)); $time = floor(time() / 30); $timeBin = str_pad(pack('N', $time), 8, "\x00", STR_PAD_LEFT); $hmac = hash_hmac('sha1', $timeBin, $bin, true); $offset = ord(substr($hmac, -1)) & 0x0F; $value = unpack('N', substr($hmac, $offset, 4))[1] & 0x7FFFFFFF; $calcCode = str_pad($value % 1000000, 6, '0', STR_PAD_LEFT); return hash_equals($calcCode, $code); } try { $pdo = new PDO("sqlite:" . $db_file); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT, totp_secret TEXT, totp_enabled INTEGER DEFAULT 0)"); $pdo->exec("CREATE TABLE IF NOT EXISTS folders (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)"); $pdo->exec("CREATE TABLE IF NOT EXISTS connections (id INTEGER PRIMARY KEY AUTOINCREMENT, folder_name TEXT, parent_id INTEGER DEFAULT NULL, name TEXT, type TEXT, host TEXT, port TEXT, username TEXT, password TEXT, kuma_url TEXT, tags TEXT, asset_type TEXT, os TEXT, hardware TEXT, serial_number TEXT, mac_address TEXT, location TEXT, warranty TEXT, uplink_id INTEGER, uplink_port TEXT, local_port TEXT, map_x REAL, map_y REAL, map_locked INTEGER DEFAULT 0)"); $pdo->exec("CREATE TABLE IF NOT EXISTS protocols (id INTEGER PRIMARY KEY AUTOINCREMENT, proto_key TEXT UNIQUE, name TEXT, exec_path TEXT, args TEXT, icon TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS templates (id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT, name TEXT, type TEXT, host TEXT, port TEXT, username TEXT, password TEXT, kuma_url TEXT, tags TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY AUTOINCREMENT, key_name TEXT UNIQUE, value TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS scripts (id INTEGER PRIMARY KEY AUTOINCREMENT, folder_name TEXT, name TEXT, language TEXT, script_body TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS audit_logs (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, user TEXT, action TEXT, details TEXT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS password_history (id INTEGER PRIMARY KEY AUTOINCREMENT, connection_id INTEGER, password TEXT, changed_at DATETIME DEFAULT CURRENT_TIMESTAMP)"); try { $pdo->exec("ALTER TABLE connections ADD COLUMN local_port TEXT"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_x REAL"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_y REAL"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE connections ADD COLUMN map_locked INTEGER DEFAULT 0"); } catch (Exception $e) {} if ($pdo->query("SELECT COUNT(*) FROM users")->fetchColumn() == 0) { $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)")->execute(['admin', password_hash('admin', PASSWORD_DEFAULT), 'admin']); logAudit($pdo, 'System', 'INSTALL', 'Admin user created.'); } if ($pdo->query("SELECT COUNT(*) FROM protocols")->fetchColumn() == 0) { $pdo->exec("INSERT INTO protocols (proto_key, name, exec_path, args, icon) VALUES ('rdp', 'RDP (Windows)', 'mstsc.exe', '/v:[HOST]', 'bi-windows'), ('ssh', 'SSH', 'putty.exe', '-ssh [USER]@[HOST]', 'bi-terminal'), ('web', 'Website', '', '', 'bi-globe')"); } $pdo->exec("INSERT OR IGNORE INTO settings (key_name, value) VALUES ('dashboard_url', 'https://threatmap.fortiguard.com/')"); $pdo->exec("INSERT OR IGNORE INTO settings (key_name, value) VALUES ('pwd_exclude_chars', 'lI1O0')"); $action = $_GET['action'] ?? ''; $method = $_SERVER['REQUEST_METHOD']; $input = json_decode(file_get_contents('php://input'), true); if ($action === 'login' && $method === 'POST') { $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$input['username']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && password_verify($input['password'], $user['password'])) { if ($user['totp_enabled']) { $_SESSION['pending_2fa'] = $user['id']; echo json_encode(["status" => "2fa_required"]); } else { $_SESSION['user_id'] = $user['id']; $_SESSION['role'] = $user['role']; $_SESSION['username'] = $user['username']; logAudit($pdo, $user['username'], 'LOGIN', 'Erfolgreicher Login.'); echo json_encode(["status" => "success", "role" => $user['role'], "username" => $user['username']]); } } else { logAudit($pdo, $input['username'] ?? 'Unknown', 'LOGIN_FAILED', 'Falsche Zugangsdaten.'); http_response_code(401); echo json_encode(["error" => "Falsche Zugangsdaten"]); } exit; } elseif ($action === 'verify_2fa' && $method === 'POST') { if (!isset($_SESSION['pending_2fa'])) { http_response_code(401); die(json_encode(["error" => "Session invalid"])); } $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['pending_2fa']]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if (verifyTOTP($user['totp_secret'], $input['code'])) { unset($_SESSION['pending_2fa']); $_SESSION['user_id'] = $user['id']; $_SESSION['role'] = $user['role']; $_SESSION['username'] = $user['username']; logAudit($pdo, $user['username'], 'LOGIN_2FA', 'Erfolgreicher 2FA Login.'); echo json_encode(["status" => "success", "role" => $user['role'], "username" => $user['username']]); } else { logAudit($pdo, $user['username'], 'LOGIN_2FA_FAILED', 'Falscher 2FA Code.'); http_response_code(401); echo json_encode(["error" => "Falscher 2FA Code"]); } exit; } elseif ($action === 'logout') { if(isset($_SESSION['username'])) logAudit($pdo, $_SESSION['username'], 'LOGOUT', 'Benutzer abgemeldet.'); session_destroy(); echo json_encode(["status" => "success"]); exit; } elseif ($action === 'session') { if(isset($_SESSION['user_id'])) echo json_encode(["status" => "active", "role" => $_SESSION['role'], "username" => $_SESSION['username']]); else echo json_encode(["status" => "inactive"]); exit; } if (!isset($_SESSION['user_id'])) { http_response_code(401); die(json_encode(["error" => "Nicht eingeloggt"])); } $isAdmin = ($_SESSION['role'] === 'admin'); $currentUser = $_SESSION['username']; if ($action === 'categories' && $method === 'GET') { $img_dir = __DIR__ . '/img/DeviceCategories'; if (!is_dir($img_dir)) { @mkdir($img_dir, 0755, true); } $categories = []; if (is_dir($img_dir)) { $files = scandir($img_dir); foreach ($files as $file) { if ($file !== '.' && $file !== '..' && in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['png', 'jpg', 'jpeg', 'gif', 'svg'])) { $categories[] = ['name' => pathinfo($file, PATHINFO_FILENAME), 'url' => 'img/DeviceCategories/' . $file]; } } } echo json_encode($categories); exit; } if ($action === 'users') { if ($method === 'GET') { if (!$isAdmin) die(json_encode(["error" => "Access denied"])); echo json_encode($pdo->query("SELECT id, username, role, totp_enabled FROM users")->fetchAll(PDO::FETCH_ASSOC)); } elseif ($method === 'POST') { if (!$isAdmin) die(); if (!empty($input['id'])) { if(!empty($input['password'])) $pdo->prepare("UPDATE users SET username=?, role=?, password=? WHERE id=?")->execute([$input['username'], $input['role'], password_hash($input['password'], PASSWORD_DEFAULT), $input['id']]); else $pdo->prepare("UPDATE users SET username=?, role=? WHERE id=?")->execute([$input['username'], $input['role'], $input['id']]); logAudit($pdo, $currentUser, 'USER_UPDATE', "Benutzer {$input['username']} aktualisiert."); } else { $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)")->execute([$input['username'], password_hash($input['password'], PASSWORD_DEFAULT), $input['role']]); logAudit($pdo, $currentUser, 'USER_CREATE', "Benutzer {$input['username']} erstellt."); } echo json_encode(["status" => "success"]); } elseif ($method === 'DELETE') { if (!$isAdmin || $input['id'] == $_SESSION['user_id']) die(); $pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$input['id']]); logAudit($pdo, $currentUser, 'USER_DELETE', "Benutzer ID {$input['id']} gelöscht."); echo json_encode(["status" => "success"]); } } elseif ($action === 'audit' && $method === 'GET' && $isAdmin) { echo json_encode($pdo->query("SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT 200")->fetchAll(PDO::FETCH_ASSOC)); } elseif ($action === 'password_history' && $method === 'GET') { $stmt = $pdo->prepare("SELECT password, changed_at FROM password_history WHERE connection_id = ? ORDER BY changed_at DESC"); $stmt->execute([$_GET['id']]); $hist = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach($hist as &$h) { $h['password'] = decryptData($h['password'], $crypto_key); } echo json_encode($hist); } elseif ($action === 'my_2fa') { if ($method === 'GET') { $stmt = $pdo->prepare("SELECT totp_secret, totp_enabled FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $u = $stmt->fetch(PDO::FETCH_ASSOC); $secret = $u['totp_secret']; if (!$secret) { $secret = generateTOTPSecret(); $pdo->prepare("UPDATE users SET totp_secret = ? WHERE id = ?")->execute([$secret, $_SESSION['user_id']]); } $url = "otpauth://totp/WebRDM:" . urlencode($_SESSION['username']) . "?secret=" . $secret . "&issuer=WebRDM"; echo json_encode(["secret" => $secret, "enabled" => $u['totp_enabled'], "qr" => "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" . urlencode($url)]); } elseif ($method === 'POST') { if (verifyTOTP($input['secret'], $input['code'])) { $pdo->prepare("UPDATE users SET totp_enabled = 1 WHERE id = ?")->execute([$_SESSION['user_id']]); logAudit($pdo, $currentUser, '2FA_ENABLED', '2FA aktiviert.'); echo json_encode(["status" => "success"]); } else { http_response_code(400); echo json_encode(["error" => "Code inkorrekt"]); } } elseif ($method === 'DELETE') { $pdo->prepare("UPDATE users SET totp_enabled = 0, totp_secret = NULL WHERE id = ?")->execute([$_SESSION['user_id']]); logAudit($pdo, $currentUser, '2FA_DISABLED', '2FA deaktiviert.'); echo json_encode(["status" => "success"]); } } elseif ($action === 'folders') { if ($method === 'GET') echo json_encode($pdo->query("SELECT name FROM folders ORDER BY name")->fetchAll(PDO::FETCH_COLUMN)); elseif ($method === 'POST' && $isAdmin) { $pdo->prepare("INSERT OR IGNORE INTO folders (name) VALUES (:name)")->execute(['name' => $input['name']]); logAudit($pdo, $currentUser, 'FOLDER_CREATE', "Ordner '{$input['name']}' erstellt."); echo json_encode(["status" => "success"]); } } elseif ($action === 'connections') { if ($method === 'GET') { $conns = $pdo->query("SELECT * FROM connections ORDER BY folder_name, name")->fetchAll(PDO::FETCH_ASSOC); foreach($conns as &$c) { $c['password'] = decryptData($c['password'], $crypto_key); } echo json_encode($conns); } elseif ($method === 'POST' && $isAdmin) { $raw_pw = $input['password'] ?? ''; $old_pw_enc = ''; if (!empty($input['id'])) { $stmt = $pdo->prepare("SELECT password FROM connections WHERE id = ?"); $stmt->execute([$input['id']]); $old_conn = $stmt->fetch(PDO::FETCH_ASSOC); $old_pw_enc = $old_conn['password'] ?? ''; $old_pw_decrypted = decryptData($old_pw_enc, $crypto_key); if ($old_pw_decrypted !== '' && $old_pw_decrypted !== $raw_pw) { $pdo->prepare("INSERT INTO password_history (connection_id, password) VALUES (?, ?)")->execute([$input['id'], $old_pw_enc]); $pdo->prepare("DELETE FROM password_history WHERE id NOT IN (SELECT id FROM password_history WHERE connection_id = ? ORDER BY changed_at DESC LIMIT 10)")->execute([$input['id']]); } } if(isset($input['password'])) $input['password'] = encryptData($input['password'], $crypto_key); if (!empty($input['id'])) { $pdo->prepare("UPDATE connections SET folder_name=:folder_name, parent_id=:parent_id, name=:name, type=:type, host=:host, port=:port, username=:username, password=:password, kuma_url=:kuma_url, tags=:tags, asset_type=:asset_type, os=:os, hardware=:hardware, serial_number=:serial_number, mac_address=:mac_address, location=:location, warranty=:warranty, uplink_id=:uplink_id, uplink_port=:uplink_port, local_port=:local_port WHERE id=:id")->execute($input); logAudit($pdo, $currentUser, 'ASSET_UPDATE', "Verbindung '{$input['name']}' aktualisiert."); } else { $stmt = $pdo->prepare("INSERT INTO connections (folder_name, parent_id, name, type, host, port, username, password, kuma_url, tags, asset_type, os, hardware, serial_number, mac_address, location, warranty, uplink_id, uplink_port, local_port) VALUES (:folder_name, :parent_id, :name, :type, :host, :port, :username, :password, :kuma_url, :tags, :asset_type, :os, :hardware, :serial_number, :mac_address, :location, :warranty, :uplink_id, :uplink_port, :local_port)"); unset($input['id']); $stmt->execute($input); logAudit($pdo, $currentUser, 'ASSET_CREATE', "Verbindung '{$input['name']}' erstellt."); } echo json_encode(["status" => "success"]); } } elseif ($action === 'delete_connection' && $method === 'POST' && $isAdmin) { $pdo->prepare("DELETE FROM connections WHERE id = :id")->execute(['id' => $input['id']]); $pdo->prepare("DELETE FROM password_history WHERE connection_id = :id")->execute(['id' => $input['id']]); logAudit($pdo, $currentUser, 'ASSET_DELETE', "Verbindung ID {$input['id']} gelöscht."); echo json_encode(["status" => "success"]); } elseif ($action === 'scripts') { if ($method === 'GET') { echo json_encode($pdo->query("SELECT * FROM scripts ORDER BY folder_name, name")->fetchAll(PDO::FETCH_ASSOC)); } elseif ($method === 'POST' && $isAdmin) { $data = ['folder_name' => $input['folder_name'] ?: null, 'name' => $input['name'], 'language' => $input['language'], 'script_body' => $input['script_body']]; if (!empty($input['id'])) { $data['id'] = $input['id']; $pdo->prepare("UPDATE scripts SET folder_name=:folder_name, name=:name, language=:language, script_body=:script_body WHERE id=:id")->execute($data); logAudit($pdo, $currentUser, 'SCRIPT_UPDATE', "Skript '{$input['name']}' aktualisiert."); } else { $pdo->prepare("INSERT INTO scripts (folder_name, name, language, script_body) VALUES (:folder_name, :name, :language, :script_body)")->execute($data); logAudit($pdo, $currentUser, 'SCRIPT_CREATE', "Skript '{$input['name']}' erstellt."); } echo json_encode(["status" => "success"]); } } elseif ($action === 'delete_script' && $method === 'POST' && $isAdmin) { $pdo->prepare("DELETE FROM scripts WHERE id = :id")->execute(['id' => $input['id']]); logAudit($pdo, $currentUser, 'SCRIPT_DELETE', "Skript ID {$input['id']} gelöscht."); echo json_encode(["status" => "success"]); } elseif ($action === 'save_map_pos' && $method === 'POST' && $isAdmin) { $pdo->prepare("UPDATE connections SET map_x = ?, map_y = ? WHERE id = ?")->execute([$input['x'], $input['y'], $input['id']]); echo json_encode(["status" => "success"]); } elseif ($action === 'toggle_map_lock' && $method === 'POST' && $isAdmin) { $pdo->prepare("UPDATE connections SET map_locked = ? WHERE id = ?")->execute([$input['locked'], $input['id']]); echo json_encode(["status" => "success"]); } elseif ($action === 'reset_map' && $method === 'POST' && $isAdmin) { $pdo->exec("UPDATE connections SET map_x = NULL, map_y = NULL, map_locked = 0"); logAudit($pdo, $currentUser, 'MAP_RESET', "Kartenphysik und Layout zurückgesetzt."); echo json_encode(["status" => "success"]); } elseif ($action === 'protocols' && $method === 'GET') echo json_encode($pdo->query("SELECT * FROM protocols")->fetchAll(PDO::FETCH_ASSOC)); elseif ($action === 'settings' && $method === 'GET') { $stmt = $pdo->query("SELECT * FROM settings"); $s = []; foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) $s[$r['key_name']] = $r['value']; echo json_encode($s); } elseif ($isAdmin) { if ($action === 'protocols') { if ($method === 'POST') { $pdo->prepare("INSERT INTO protocols (proto_key, name, exec_path, args, icon) VALUES (:key, :name, :path, :args, :icon)")->execute(['key' => strtolower($input['proto_key']), 'name' => $input['name'], 'path' => $input['exec_path'], 'args' => $input['args'], 'icon' => $input['icon'] ?? 'bi-gear']); echo json_encode(["status" => "success"]); } elseif ($method === 'DELETE') { $pdo->prepare("DELETE FROM protocols WHERE id = :id AND proto_key != 'web'")->execute(['id' => $input['id']]); echo json_encode(["status" => "success"]); } } elseif ($action === 'templates') { if ($method === 'POST') { $pdo->prepare("INSERT INTO templates (category, name, type, host, port, username, password, kuma_url, tags) VALUES (:category, :name, :type, :host, :port, :username, :password, :kuma_url, :tags)")->execute(['category' => $input['category'], 'name' => $input['name'], 'type' => $input['type'] ?? 'rdp', 'host' => $input['host'] ?? '', 'port' => $input['port'] ?? '', 'username' => $input['username'] ?? '', 'password' => $input['password'] ?? '', 'kuma_url' => $input['kuma_url'] ?? '', 'tags' => $input['tags'] ?? '']); echo json_encode(["status" => "success"]); } elseif ($method === 'DELETE') { $pdo->prepare("DELETE FROM templates WHERE id = :id")->execute(['id' => $input['id']]); echo json_encode(["status" => "success"]); } } elseif ($action === 'settings' && $method === 'POST') { // Robustes UPSERT, funktioniert immer in SQLite $stmt_check = $pdo->prepare("SELECT COUNT(*) FROM settings WHERE key_name = ?"); $stmt_insert = $pdo->prepare("INSERT INTO settings (key_name, value) VALUES (?, ?)"); $stmt_update = $pdo->prepare("UPDATE settings SET value = ? WHERE key_name = ?"); foreach($input as $key => $val) { $stmt_check->execute([$key]); if($stmt_check->fetchColumn() > 0) $stmt_update->execute([$val, $key]); else $stmt_insert->execute([$key, $val]); } logAudit($pdo, $currentUser, 'SETTINGS_UPDATE', "Globale Einstellungen geändert."); echo json_encode(["status" => "success"]); } } else { http_response_code(403); echo json_encode(["error" => "Berechtigung fehlt"]); } } catch (Exception $e) { http_response_code(500); echo json_encode(["error" => $e->getMessage()]); } ?>