Initial commit

This commit is contained in:
Hein Erik
2026-04-02 16:09:55 +02:00
commit bc9a453452
7 changed files with 785 additions and 0 deletions

298
WebRDM/api.php Normal file
View File

@@ -0,0 +1,298 @@
<?php
ini_set('display_errors', 0);
session_start();
header("Content-Type: application/json");
$db_dir = __DIR__ . '/db';
if (!is_dir($db_dir)) { mkdir($db_dir, 0755, true); }
$db_file = $db_dir . '/rdm.db';
// --- KRYPTOGRAPHIE & SCHLÜSSEL ---
$key_file = $db_dir . '/secret.key';
if (!file_exists($key_file)) { file_put_contents($key_file, random_bytes(32)); }
$crypto_key = file_get_contents($key_file);
function encryptData($data, $key) {
if (!$data) return '';
if (strpos($data, 'ENC:') === 0) return $data;
$iv = random_bytes(16);
$cipher = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
return 'ENC:' . base64_encode($iv . $cipher);
}
function decryptData($data, $key) {
if (!$data || strpos($data, 'ENC:') !== 0) return $data;
$raw = base64_decode(substr($data, 4));
$iv = substr($raw, 0, 16);
$cipher = substr($raw, 16);
return openssl_decrypt($cipher, 'aes-256-cbc', $key, 0, $iv);
}
function logAudit($pdo, $user, $action, $details) {
$stmt = $pdo->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()]); }
?>