true, 'BastionGuard-ransomware-realtime-alert.service' => true, 'BastionGuard-ransomware-scanner.service' => true, 'BastionGuard-useragent.service' => true, 'BastionGuard-phishing-scanner.service' => true, 'BastionGuard-mailproxy.service' => true, 'BastionGuard-ransomware-realtime.service' => true, ]; } function bg_wizard_service_minimal() { // Same minimal preset as FirstRunServicesWindow::select_minimal(). return [ 'BastionGuard-ransomware-realtime-alert.service' => true, ]; } function bg_wizard_service_labels() { // Labels/descriptions mirrored from src/first_run/first-run-services-window.cpp. return [ 'BastionGuard-ransomware-alert.service' => ['label'=>'Ransomware Alert', 'description'=>'Notifiche su possibili attività ransomware'], 'BastionGuard-ransomware-realtime-alert.service' => ['label'=>'Ransomware Realtime Alert', 'description'=>'Avvisi in tempo reale su comportamenti sospetti'], 'BastionGuard-ransomware-scanner.service' => ['label'=>'Ransomware Scanner', 'description'=>'Scansione periodica alla ricerca di comportamenti malevoli'], 'BastionGuard-useragent.service' => ['label'=>'User-Agent Randomizer', 'description'=>'Modifica user-agent per aumentare anonimato'], 'BastionGuard-phishing-scanner.service' => ['label'=>'Phishing Scanner (System)', 'description'=>'Scansione anti-phishing a livello di sistema'], 'BastionGuard-mailproxy.service' => ['label'=>'Mail Proxy', 'description'=>'Proxy SMTP locale per protezione email in uscita'], 'BastionGuard-ransomware-realtime.service' => ['label'=>'Ransomware Realtime (System)', 'description'=>'Monitor realtime ransomware a livello di sistema'], ]; } function bg_wizard_config_array(array $services = null, $installedAt = null) { return [ 'installed_at' => $installedAt ?: gmdate('Y-m-d\TH:i:s\Z'), 'services' => $services ?: bg_wizard_services_default(), 'wizard_completed' => true, ]; } function bg_wizard_config_json(array $services = null) { return json_encode(bg_wizard_config_array($services), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; } function bg_config_catalog() { $cfg = bg_bastionguard_config_dir(); $catalog = [ 'app_config' => [ 'label' => 'General GUI/wizard config', 'path' => $cfg . '/config.json', 'scope' => 'user', 'type' => 'json', 'default' => bg_wizard_config_array(bg_wizard_services_default(), null), 'description' => 'Wizard status and initial services read from the desktop UI. The WebUI does not overwrite it if already completed.', ], 'config_ini' => [ 'label' => 'Runtime/quarantine config', 'path' => $cfg . '/config.ini', 'scope' => 'user', 'type' => 'ini', 'default' => 'quarantinePath=' . bg_bastionguard_data_dir() . '/quarantine' . "\n", 'description' => 'Runtime config used by Backend::getQuarantinePath()/setQuarantinePath.', ], 'cloud' => [ 'label' => 'VirusTotal cloud API', 'path' => $cfg . '/cloud.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "malware_bazaar_api_key=\n", 'description' => 'VirusTotal API key read by ScanPage/cloud scanner.', ], 'cred' => [ 'label' => 'Credentials / local secrets', 'path' => $cfg . '/cred.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "# Local BastionGuard credentials\n", 'description' => 'Local credentials file if used by installed modules.', ], 'firewall' => [ 'label' => 'Firewall', 'path' => $cfg . '/firewall.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "enabled=1\nmode=auto\n", 'description' => 'Firewall preferences read by firewall modules.', ], 'identity_leak' => [ 'label' => 'Identity leaks', 'path' => $cfg . '/identity_leak.json', 'scope' => 'user', 'type' => 'json', 'default' => ['emails'=>[], 'providers'=>[]], 'description' => 'Identity/email breach check configuration.', ], 'lang' => [ 'label' => 'UI language', 'path' => $cfg . '/lang.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "language=en\n", 'description' => 'Language read by multiple desktop components.', ], 'mail' => [ 'label' => 'Mail proxy', 'path' => $cfg . '/mail.json', 'scope' => 'user', 'type' => 'json', 'default' => [ 'version'=>2, 'enabled'=>false, 'scan_outgoing'=>true, 'inject_signature'=>false, 'local_smtp_host'=>'127.0.0.1', 'local_smtp_port'=>2525, 'local_smtp_tls_port'=>2465, 'local_submission_port'=>2587, 'advertise_starttls'=>true, 'enable_implicit_tls_listener'=>true, 'default_profile_id'=>'default', 'profiles'=>[[ 'id'=>'default','label'=>'Default','match_from'=>[], 'match_from_domain'=>[], 'smtp_host'=>'','smtp_port'=>587,'starttls'=>true,'implicit_tls'=>false,'username'=>'','password'=>'', 'signature'=>['display_name'=>'','job_title'=>'','company'=>'','phone'=>'','website'=>'','logo_path'=>'/usr/share/BastionGuard/data/logo.png'] ]] ], 'description' => 'Configuration used by BastionGuard-mailproxy.service.', ], 'payments' => [ 'label' => 'Safe payments', 'path' => $cfg . '/payments.json', 'scope' => 'user', 'type' => 'json', 'default' => [ 'version'=>1, 'enabled'=>false, 'list'=>['paypal.com','stripe.com','adyen.com','klarna.com','checkout.com','worldpay.com','braintreepayments.com','braintreegateway.com','amazonpay.com','pay.google.com','pay.apple.com','amazon.com'], ], 'description' => 'Payment domain list read by desktop SecureBrowser. The WebUI only saves the list and never manages proxy/browser daemons.', ], 'proxy_bypass' => [ 'label' => 'Proxy bypass', 'path' => $cfg . '/proxy_bypass.json', 'scope' => 'user', 'type' => 'json', 'default' => ['domains'=>['localhost','127.0.0.1']], 'description' => 'Domains/exclusions used by desktop proxy/browser components. The WebUI only edits the file.', ], 'samba' => [ 'label' => 'Samba', 'path' => $cfg . '/samba.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "enabled=false\n", 'description' => 'Samba configuration compatible with Backend::saveSambaConfig: first line enabled=true/false, then one path/share per line.', ], 'settings' => [ 'label' => 'Settings generali', 'path' => $cfg . '/settings.json', 'scope' => 'user', 'type' => 'json', 'default' => ['version'=>1], 'description' => 'Preferenze generali della UI BastionGuard.', ], 'theme' => [ 'label' => 'UI theme', 'path' => $cfg . '/theme.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "theme=system\n", 'description' => 'Theme read from the desktop UI.', ], 'whitelist' => [ 'label' => 'Whitelist browser/phishing', 'path' => $cfg . '/whitelist.json', 'scope' => 'user', 'type' => 'json', 'default' => ['domains'=>[]], 'description' => 'Whitelist usata da backend e secure browser.', ], 'allowlist' => [ 'label' => 'Anti-Ransomware SHA256 allowlist', 'path' => $cfg . '/allowlist.txt', 'scope' => 'user', 'type' => 'text', 'default' => '', 'description' => 'SHA256 allowlist compatible with SettingsPage.cpp.', ], 'googlesafe' => [ 'label' => 'Google Safe Browsing', 'path' => $cfg . '/googlesafe.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "# Google Safe Browsing configuration\ngoogle_safe_enabled=false\ngoogle_safe_key=\n", 'description' => 'Google Safe Browsing settings used by Anti-Phishing.', ], 'first_run_services_done' => [ 'label' => 'Flag first-run services', 'path' => $cfg . '/first-run-services-done', 'scope' => 'user', 'type' => 'text', 'default' => "", 'description' => 'Flag creato dal wizard servizi della UI.', ], 'webports' => [ 'label' => 'Porte Web BastionGuard', 'path' => '/etc/BastionGuard/webports.conf', 'scope' => 'system', 'type' => 'text', 'default' => "81 444\n", 'description' => 'Porte HTTP/HTTPS usate dal wizard e dalla configurazione NGINX.', ], 'dnsmasq' => [ 'label' => 'DNSMasq BastionGuard', 'path' => '/etc/dnsmasq.d/BastionGuard.conf', 'scope' => 'system', 'type' => 'text', 'default' => "# Interfacce di ascolto per BastionGuard DNS Protection\nlisten-address=127.0.0.1,127.0.0.2\n\n# Facoltativo: riduce warning di bind\nbind-interfaces\n", 'description' => 'Local DNS config compatible with the GTK wizard.', ], 'scan_config' => [ 'label' => 'Manual scan/realtime', 'path' => bg_home_path('.local/share/BastionGuard/config.json'), 'scope' => 'user', 'type' => 'json', 'default' => ['auto_scan_enabled' => true], 'description' => 'Configuration used by ScanPage.cpp for auto-scan.', ], 'scanner' => [ 'label' => 'Anti-Ransomware scanner', 'path' => $cfg . '/scanner.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "# --- BastionGuard Anti-Ransomware Config ---\nsuspicious_only=0\nignore_paths=/proc;/sys;/dev;/run;/tmp\nignore_ext=.tmp;.log;.cache\nsuspicious_ext=.locked;.encrypted;.crypt;.enc;.encrypted\n\n# --- Ransomware Scanner ---\nenable_yara=1\nenable_sanesecurity=1\nscan_interval=60\nscan_path=/home\n", 'description' => 'Unifica le chiavi lette da SettingsPage.cpp e dallo scanner ransomware.', ], 'sources' => [ 'label' => 'Sorgenti aggiornamenti', 'path' => $cfg . '/sources.conf', 'scope' => 'user', 'type' => 'ini', 'default' => "clamav=1\nsanesecurity=1\n", 'description' => 'Sorgenti UpdatePage per firme e feed.', ], ]; foreach ($catalog as $key => &$def) { $def['key'] = $key; } unset($def); return $catalog; } function bg_config_default_string(array $def) { $v = $def['default']; if (($def['type'] ?? '') === 'directory') return ''; if (($def['type'] ?? '') === 'json') return json_encode($v, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; return (string)$v; } function bg_config_parse_raw(array $def, array $out) { $type = $def['type'] ?? 'text'; if ($type === 'json') { $p = json_decode($out['raw'], true); if (json_last_error() === JSON_ERROR_NONE) $out['parsed'] = $p; else $out['error'] = 'Invalid JSON: ' . json_last_error_msg(); } elseif ($type === 'ini') { $p = @parse_ini_string($out['raw'], false, INI_SCANNER_RAW); $out['parsed'] = is_array($p) ? $p : []; } else { $out['parsed'] = $out['raw']; } return $out; } function bg_config_read_via_helper(array $def) { $key = $def['key'] ?? ''; if ($key === '') return null; $args = ['read-config-b64', $key]; if (($def['scope'] ?? '') === 'user') { $user = bg_detect_desktop_user(); if (!$user) return null; $args[] = $user; } $res = bg_admin_helper($args, 60); $code = (int)($res['code'] ?? 1); $out = ['exists'=>false, 'readable'=>false, 'raw'=>'', 'parsed'=>null, 'error'=>'', 'via'=>'helper']; if ($code === 66) return $out; if ($code === 68) { $out['exists'] = true; $out['readable'] = true; $out['error'] = 'The path is a directory.'; return $out; } if ($code === 65) { $out['exists'] = true; $out['readable'] = true; $out['error'] = 'The path exists but is not a file.'; return $out; } if ($code === 13) { $out['exists'] = true; $out['readable'] = false; $out['error'] = 'File not readable neppure tramite helper.'; return $out; } if ($code !== 0) return null; $raw = base64_decode(trim((string)($res['output'] ?? '')), true); if ($raw === false) { $out['exists'] = true; $out['readable'] = false; $out['error'] = 'Helper ha restituito dati non base64.'; return $out; } $out['exists'] = true; $out['readable'] = true; $out['raw'] = $raw; return bg_config_parse_raw($def, $out); } function bg_config_read(array $def) { $path = $def['path']; $type = $def['type'] ?? 'text'; $out = ['exists'=>file_exists($path), 'readable'=>is_readable($path), 'raw'=>'', 'parsed'=>null, 'error'=>'', 'via'=>'direct']; if ($type === 'directory') { if (is_dir($path)) { $items = []; foreach (scandir($path) ?: [] as $it) { if ($it === '.' || $it === '..') continue; $items[] = $it . (is_dir($path . '/' . $it) ? '/' : ''); } $out['exists'] = true; $out['readable'] = is_readable($path); $out['parsed'] = $items; $out['raw'] = implode("\n", $items); return $out; } // La directory potrebbe esistere ma non essere attraversabile da Apache: prova helper. $scan = bg_config_scan_dir(); if (!empty($scan['exists'])) { foreach ($scan['items'] as $it) { if ($it['name'] === basename($path) && $it['type'] === 'directory') { $out['exists'] = true; $out['readable'] = !empty($it['readable']); $out['via'] = 'helper'; return $out; } } } return $out; } if (!$out['exists'] || !$out['readable'] || !is_file($path)) { $helper = bg_config_read_via_helper($def); if (is_array($helper)) return $helper; if (!$out['exists']) return $out; if (!is_file($path)) { $out['error'] = 'The path exists but is not a file.'; return $out; } if (!$out['readable']) { $out['error'] = 'File not readable dal processo WebUI e helper non disponibile/aggiornato.'; return $out; } } $raw = @file_get_contents($path); if ($raw === false) { $helper = bg_config_read_via_helper($def); if (is_array($helper)) return $helper; $out['error'] = 'File read error.'; return $out; } $out['raw'] = $raw; return bg_config_parse_raw($def, $out); } function bg_config_direct_write(array $def, $content, $createOnly = false) { $path = $def['path']; if ($createOnly && is_file($path)) return ['code'=>0,'output'=>'already present']; $dir = dirname($path); if (!is_dir($dir)) bg_safe_mkdir($dir, 0750); if (($def['type'] ?? '') === 'json') { json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) return ['code'=>2,'output'=>'Invalid JSON: '.json_last_error_msg()]; } $tmp = $path . '.webui.tmp'; if (@file_put_contents($tmp, $content, LOCK_EX) === false) return ['code'=>1,'output'=>'Impossibile scrivere file temporaneo.']; @chmod($tmp, ($def['scope'] ?? '') === 'system' ? 0644 : 0640); if (!@rename($tmp, $path)) { @unlink($tmp); return ['code'=>1,'output'=>'Impossibile spostare il file finale.']; } return ['code'=>0,'output'=>'scritto: '.$path]; } function bg_admin_helper($args, $timeout = 120) { $helper = '/usr/local/sbin/bastionguard-webui-admin'; if (!is_executable($helper)) return ['code'=>127,'output'=>'Admin helper not installed. Run: sudo bash scripts/install-webui-helpers.sh "$USER"']; $argv = ['sudo','-n',$helper]; foreach ($args as $a) $argv[] = (string)$a; return bg_run_argv($argv, $timeout); } function bg_wizard_helper($args, $timeout = 300) { $helper = '/usr/local/sbin/bastionguard-webui-wizard'; if (!is_executable($helper)) { return ['code'=>127,'output'=>'Wizard helper not installed. Run: sudo bash scripts/install-webui-helpers.sh "$USER"']; } $argv = ['sudo','-n',$helper]; foreach ($args as $a) $argv[] = (string)$a; return bg_run_argv($argv, $timeout); } function bg_wizard_status($user = null) { $user = $user ?: bg_detect_desktop_user(); if (!$user) return ['code'=>67,'output'=>'No desktop user configured.']; return bg_wizard_helper(['status', $user], 60); } function bg_wizard_detect_web() { return bg_wizard_helper(['detect-web'], 30); } function bg_wizard_apply_web_config($http, $https, $distro = 'auto') { $http = (int)$http; $https = (int)$https; if ($http < 1 || $http > 65535) $http = 81; if ($https < 1 || $https > 65535) $https = 444; return bg_wizard_helper(['apply-web-config', (string)$http, (string)$https, (string)$distro], 120); } function bg_wizard_install_ca($userOrPath = '') { $args = ['install-ca']; if (trim((string)$userOrPath) !== '') $args[] = trim((string)$userOrPath); return bg_wizard_helper($args, 120); } function bg_wizard_install_thunderbird_extension($user = null) { $user = $user ?: bg_detect_desktop_user(); if (!$user) return ['code'=>67,'output'=>'No desktop user configured.']; return bg_wizard_helper(['install-thunderbird-extension', $user], 120); } function bg_data_file_read_b64($key, $user = null) { $user = $user ?: bg_detect_desktop_user(); if (!$user) return ['code'=>67,'output'=>'No desktop user configured.']; return bg_admin_helper(['read-data-file-b64', $key, $user], 60); } function bg_update_banks_user($user = null, $url = '') { $user = $user ?: bg_detect_desktop_user(); if (!$user) return ['code'=>67,'output'=>'No desktop user configured.']; $args = ['update-banks', $user]; if (trim((string)$url) !== '') $args[] = trim((string)$url); return bg_wizard_helper($args, 180); } function bg_config_ensure_all($targetUser = null) { if (function_exists('bg_current_user_is_admin') && !bg_current_user_is_admin()) return ['code'=>13,'output'=>bg_t('config_write_denied')]; $targetUser = $targetUser ?: bg_detect_desktop_user(); $res = bg_admin_helper(['ensure-configs', $targetUser], 120); if (($res['code'] ?? 1) === 0) return $res; // Fallback: prova a creare solo i file scrivibili direttamente dal processo PHP. $logs = [trim((string)$res['output'])]; foreach (bg_config_catalog() as $key=>$def) { if (in_array($key, ['app_config','first_run_services_done'], true)) continue; if (($def['scope'] ?? '') === 'system') continue; if (file_exists($def['path'])) continue; $r = bg_config_direct_write($def, bg_config_default_string($def), true); $logs[] = $key . ': ' . $r['output']; } return ['code'=>$res['code'] ?? 1, 'output'=>implode("\n", array_filter($logs))]; } function bg_config_save_key($key, $content) { if (function_exists('bg_current_user_is_admin') && !bg_current_user_is_admin()) return ['code'=>13,'output'=>bg_t('config_write_denied')]; $catalog = bg_config_catalog(); if (empty($catalog[$key])) return ['code'=>2,'output'=>'Invalid configuration key.']; $def = $catalog[$key]; if (($def['scope'] ?? '') === 'system') { return bg_admin_helper(['write-config', $key, base64_encode($content)], 60); } // If the file is in another user home, use the helper to keep the correct owner. $target = bg_detect_desktop_user(); if ($target && bg_current_process_user() !== $target) return bg_admin_helper(['write-config', $key, base64_encode($content), $target], 60); return bg_config_direct_write($def, $content, false); } function bg_wizard_steps() { return [ 'resolv' => ['label'=>bg_t('wizard_step_resolver'), 'description'=>bg_t('wizard_step_resolver_desc')], 'dnsmasq' => ['label'=>bg_t('wizard_step_dnsmasq'), 'description'=>bg_t('wizard_step_dnsmasq_desc')], 'firewall' => ['label'=>bg_t('wizard_step_firewall'), 'description'=>bg_t('wizard_step_firewall_desc')], 'nftables' => ['label'=>bg_t('wizard_step_nftables'), 'description'=>bg_t('wizard_step_nftables_desc')], 'certs' => ['label'=>bg_t('wizard_step_certs'), 'description'=>bg_t('wizard_step_certs_desc')], 'webconf' => ['label'=>bg_t('wizard_step_webserver'), 'description'=>bg_t('wizard_step_webserver_desc')], 'native' => ['label'=>bg_t('wizard_step_native'), 'description'=>bg_t('wizard_step_native_desc')], 'useragent' => ['label'=>bg_t('wizard_step_useragent'), 'description'=>bg_t('wizard_step_useragent_desc')], 'banks' => ['label'=>bg_t('wizard_step_banks'), 'description'=>bg_t('wizard_step_banks_desc')], 'services' => ['label'=>bg_t('wizard_step_services'), 'description'=>bg_t('wizard_step_services_desc')], ]; } function bg_wizard_step_defaults_src() { // GTK wizard check buttons start unchecked; these defaults preserve that behavior. return [ 'resolv' => false, 'dnsmasq' => false, 'firewall' => false, 'nftables' => false, 'certs' => false, 'webconf' => false, 'native' => false, 'useragent' => false, 'banks' => false, ]; } function bg_wizard_step_recommended() { // Convenience preset used by the WebUI only; not auto-applied. return [ 'resolv' => true, 'dnsmasq' => true, 'firewall' => true, 'nftables' => false, 'certs' => true, 'webconf' => true, 'native' => true, 'useragent' => true, 'banks' => true, ]; } function bg_webui_disabled_unit($unit) { return in_array(strtolower((string)$unit), ['bastionguard-cef.service','bastionguard-pacd.service','bastionguard-privacyd.service','bastionguard-usbd.service'], true); } function bg_wizard_selected_services_from_post(array $post, array $defaults = null, $prefix = 'svc') { $defaults = $defaults ?: bg_wizard_services_default(); $selected = []; $posted = isset($post[$prefix]) && is_array($post[$prefix]) ? $post[$prefix] : []; foreach ($defaults as $unit => $enabled) { $selected[$unit] = bg_webui_disabled_unit($unit) ? false : !empty($posted[$unit]); } return $selected; } function bg_wizard_selected_steps_from_post(array $post, $prefix = 'step') { $selected = []; $posted = isset($post[$prefix]) && is_array($post[$prefix]) ? $post[$prefix] : []; foreach (array_keys(bg_wizard_steps()) as $key) { if ($key === 'services') continue; $selected[$key] = !empty($posted[$key]); } return $selected; } function bg_desktop_mode_status($user = null) { $user = $user ?: bg_detect_desktop_user(); if (!$user) return ['code'=>67, 'output'=>'No desktop user configured.', 'mode'=>'unknown', 'summary'=>'']; $res = bg_admin_helper(['desktop-mode', $user], 30); $summary = trim((string)($res['output'] ?? '')); $mode = str_starts_with($summary, 'gui') ? 'gui' : (str_starts_with($summary, 'headless') ? 'headless' : 'unknown'); return ['code'=>$res['code'] ?? 1, 'output'=>$summary, 'mode'=>$mode, 'summary'=>$summary]; } function bg_wizard_lock_status($user = null, $scan = null, $configStatus = null, $desktopMode = null) { $user = $user ?: bg_detect_desktop_user(); $scan = is_array($scan) ? $scan : bg_config_scan_dir($user ?: null); $configStatus = is_array($configStatus) ? $configStatus : bg_wizard_config_status($user ?: null); $desktopMode = is_array($desktopMode) ? $desktopMode : bg_desktop_mode_status($user ?: null); $reasons = []; $items = is_array($scan['items'] ?? null) ? $scan['items'] : []; // Hard boundary: the WebUI wizard is configurable only when the desktop // profile has no BastionGuard configuration yet. A running GUI alone is not // a lock because CEF/PAC are excluded from the WebUI and remain GTK-owned. if (!empty($configStatus['exists'])) { $reasons[] = bg_t('wizard_lock_reason_config_json'); } elseif (!empty($scan['exists']) && count($items) > 0) { $sample = array_slice(array_map(function($i){ return (string)($i['name'] ?? ''); }, $items), 0, 6); $sample = array_values(array_filter($sample, fn($v) => $v !== '')); $detail = $sample ? ' (' . implode(', ', $sample) . (count($items) > count($sample) ? ', …' : '') . ')' : ''; $reasons[] = bg_t('wizard_lock_reason_config_dir') . $detail; } return [ 'locked' => count($reasons) > 0, 'reasons' => $reasons, 'user' => $user, 'config_dir' => (string)($scan['dir'] ?? bg_bastionguard_config_dir($user)), 'desktop_mode' => (string)($desktopMode['mode'] ?? 'unknown'), 'desktop_summary' => (string)($desktopMode['summary'] ?? ''), ]; } function bg_wizard_environment_status($user = null) { $user = $user ?: bg_detect_desktop_user(); $desktop = bg_desktop_mode_status($user); $web = bg_web_options(); return [ 'desktop_user' => $user, 'desktop_mode' => $desktop['mode'], 'desktop_summary' => $desktop['summary'], 'http_port' => (int)($web['http_port'] ?? 81), 'https_port' => (int)($web['https_port'] ?? 444), ]; } function bg_wizard_apply(array $opts) { $user = trim((string)($opts['desktop_user'] ?? bg_detect_desktop_user())); $http = (int)($opts['http_port'] ?? 81); $https = (int)($opts['https_port'] ?? 444); if ($http < 1 || $http > 65535) $http = 81; if ($https < 1 || $https > 65535) $https = 444; $flags = []; foreach (array_keys(bg_wizard_steps()) as $k) if (!empty($opts[$k])) $flags[] = $k; return bg_wizard_helper(array_merge(['run', $user, (string)$http, (string)$https], $flags), 300); } function bg_config_scan_dir_via_helper($user = null) { $user = $user ?: bg_detect_desktop_user(); if (!$user) return null; $res = bg_admin_helper(['list-configs', $user], 60); if ((int)($res['code'] ?? 1) !== 0) return null; $dir = bg_bastionguard_config_dir($user); $exists = false; $items = []; foreach (preg_split('/\R+/', trim((string)($res['output'] ?? ''))) as $line) { if ($line === '') continue; $parts = explode("\t", $line); if (($parts[0] ?? '') === 'DIR') { $dir = $parts[1] ?? $dir; $exists = (($parts[2] ?? '0') === '1'); } elseif (($parts[0] ?? '') === 'ITEM' && count($parts) >= 6) { $name = base64_decode($parts[1], true); if ($name === false) continue; $items[] = [ 'name' => $name, 'path' => rtrim($dir, '/') . '/' . $name, 'type' => $parts[2], 'readable' => ($parts[3] === '1'), 'size' => (int)$parts[4], 'mtime' => (int)$parts[5], 'via' => 'helper', ]; } } usort($items, function($a, $b) { return strcmp($a['name'], $b['name']); }); return ['dir'=>$dir, 'exists'=>$exists, 'items'=>$items, 'via'=>'helper']; } function bg_config_scan_dir($user = null) { $dir = bg_bastionguard_config_dir($user); $items = []; if (is_dir($dir) && is_readable($dir)) { foreach (scandir($dir) ?: [] as $name) { if ($name === '.' || $name === '..') continue; $path = $dir . '/' . $name; $items[] = [ 'name' => $name, 'path' => $path, 'type' => is_dir($path) ? 'directory' : 'file', 'readable' => is_readable($path), 'size' => is_file($path) ? (int)@filesize($path) : 0, 'mtime' => @filemtime($path) ?: 0, 'via' => 'direct', ]; } usort($items, function($a, $b) { return strcmp($a['name'], $b['name']); }); return ['dir'=>$dir, 'exists'=>true, 'items'=>$items, 'via'=>'direct']; } $helper = bg_config_scan_dir_via_helper($user); if (is_array($helper)) return $helper; return ['dir'=>$dir, 'exists'=>false, 'items'=>$items, 'via'=>'none']; } function bg_wizard_config_status($user = null) { $catalog = bg_config_catalog(); $def = $catalog['app_config']; if ($user) $def['path'] = bg_bastionguard_config_dir($user) . '/config.json'; $read = bg_config_read($def); $out = [ 'path'=>$def['path'], 'exists'=>!empty($read['exists']), 'readable'=>!empty($read['readable']), 'valid'=>false, 'completed'=>false, 'services_ok'=>false, 'data'=>null, 'raw'=>(string)($read['raw'] ?? ''), 'error'=>(string)($read['error'] ?? ''), 'via'=>$read['via'] ?? 'direct', ]; if (!$out['exists'] || !$out['readable'] || $out['error']) return $out; $data = $read['parsed']; if (!is_array($data)) { $out['error'] = 'Invalid JSON or not readable.'; return $out; } $out['valid'] = true; $out['data'] = $data; $out['completed'] = !empty($data['wizard_completed']); $services = $data['services'] ?? null; $default = bg_wizard_services_default(); $out['services_ok'] = is_array($services) && count(array_intersect_key($default, $services)) === count($default); return $out; } function bg_write_first_run_config($user, array $services) { $status = bg_wizard_config_status($user); if (!empty($status['completed']) && !empty($status['services_ok'])) { return ['code'=>0, 'output'=>'config.json already completed: no changes.']; } $json = bg_wizard_config_json($services); $res = bg_admin_helper(['first-run-config', $user, base64_encode($json)], 60); if (($res['code'] ?? 1) === 0) return $res; $def = ['path'=>bg_bastionguard_config_dir($user) . '/config.json', 'scope'=>'user', 'type'=>'json']; $r = bg_config_direct_write($def, $json, false); if (($r['code'] ?? 1) === 0) return $r; return ['code'=>$res['code'] ?? 1, 'output'=>trim(($res['output'] ?? '') . "\n" . ($r['output'] ?? ''))]; } function bg_apply_services_from_config(array $services) { $catalog = bg_services_catalog(); $byUnit = []; foreach ($catalog as $svc) foreach ($svc['services'] as $unit) $byUnit[$unit] = $svc; $logs = []; foreach ($services as $unit=>$enabled) { if (bg_webui_disabled_unit($unit)) { $logs[] = $unit . ': skipped - CEF/PAC, Webcam/Privacy and USB are GTK/desktop-only or hardware-local modules and are never managed by the server WebUI. [code 0]'; continue; } $svc = $byUnit[$unit] ?? ['services'=>[$unit], 'user'=>!in_array($unit, ['BastionGuard-phishing-scanner.service','BastionGuard-ransomware-realtime.service'], true)]; $action = $enabled ? 'enable' : 'disable'; $r = bg_service_action([$unit], $action, !empty($svc['user'])); $logs[] = $unit . ' ' . $action . ': ' . trim($r['output'] ?? '') . ' [code ' . ($r['code'] ?? '?') . ']'; } return ['code'=>0, 'output'=>implode(" ", $logs)]; } function bg_prediction_items() { $items = []; $catalog = bg_config_catalog(); foreach ($catalog as $key=>$def) { $r = bg_config_read($def); if (!$r['exists']) $items[] = ['level'=>'warning','title'=>'Missing configuration: '.$def['label'], 'detail'=>$def['path'], 'fix'=>'Create missing configurations from the Configurations page.']; elseif (!$r['readable']) $items[] = ['level'=>'danger','title'=>'Configuration not readable: '.$def['label'], 'detail'=>$def['path'], 'fix'=>'Check permissions or install the WebUI helper.']; elseif ($r['error']) $items[] = ['level'=>'danger','title'=>'Invalid configuration: '.$def['label'], 'detail'=>$r['error'], 'fix'=>'Open and save the file from the Configurations page.']; } $diag = bg_user_services_diagnostics(); if (empty($diag['target_user'])) $items[] = ['level'=>'danger','title'=>'Desktop user not detected', 'detail'=>'systemctl --user services will not be readable.', 'fix'=>'Set the user in Settings → Web Options.']; if (empty($diag['helper_installed'])) $items[] = ['level'=>'warning','title'=>'Admin/user helper not installed', 'detail'=>'The WebUI cannot create files in /etc or reliably manage systemd --user.', 'fix'=>'sudo bash scripts/install-webui-helpers.sh "$USER"']; foreach (bg_services_catalog() as $svc) { $st = bg_service_status($svc['services'], $svc['user']); if (empty($st['exists'])) $items[] = ['level'=>'secondary','title'=>'Service not found: '.$svc['label'], 'detail'=>implode(', ', $svc['services']), 'fix'=>'Install the package/service or verify the unit name.']; elseif (!$st['active']) $items[] = ['level'=>'warning','title'=>'Service fermo: '.$svc['label'], 'detail'=>$st['name'].' → '.$st['active_state'].'/'.$st['sub_state'], 'fix'=>'Startlo dalla pagina Services o dal Wizard.']; } if (!is_file('/etc/BastionGuard/certs/BastionGuard-ca.crt.pem')) $items[] = ['level'=>'warning','title'=>'CA BastionGuard non generata', 'detail'=>'/etc/BastionGuard/certs/BastionGuard-ca.crt.pem missing.', 'fix'=>'Esegui step Certificati dal Wizard Web.']; if (empty($items)) $items[] = ['level'=>'success','title'=>'No obvious issue', 'detail'=>'Main configurations and services look consistent.', 'fix'=>'']; return $items; } function bg_samba_load_config() { $catalog = bg_config_catalog(); $def = $catalog['samba']; $r = bg_config_read($def); $enabled = false; $shares = []; $raw = (string)($r['raw'] ?? ''); foreach (preg_split('/\R+/', $raw) as $line) { $line = trim($line); if ($line === '' || strpos($line, '#') === 0) continue; if (preg_match('/^enabled\s*=\s*(true|1|yes|on)$/i', $line)) { $enabled = true; continue; } if (preg_match('/^enabled\s*=\s*(false|0|no|off)$/i', $line)) { $enabled = false; continue; } if (preg_match('/^(scan_enabled|quarantine_enabled)\s*=/i', $line)) continue; $shares[] = $line; } return ['enabled'=>$enabled, 'shares'=>$shares, 'read'=>$r, 'path'=>$def['path']]; } function bg_samba_save_config($enabled, array $shares) { $lines = ['enabled=' . ($enabled ? 'true' : 'false')]; foreach ($shares as $share) { $share = trim((string)$share); if ($share !== '') $lines[] = $share; } return bg_config_save_key('samba', implode("\n", $lines) . "\n"); } function bg_samba_credentials_summary() { $catalog = bg_config_catalog(); $def = $catalog['cred']; $r = bg_config_read($def); $items = []; $current = ''; foreach (preg_split('/\R+/', (string)($r['raw'] ?? '')) as $line) { $line = trim($line); if ($line === '') continue; if (preg_match('/^\[(.+)\]$/', $line, $m)) { $current = $m[1]; if (!isset($items[$current])) $items[$current] = ['user'=>'', 'has_pass'=>false]; } elseif ($current !== '' && strpos($line, 'user=') === 0) { $u = base64_decode(substr($line, 5), true); $items[$current]['user'] = $u === false ? '' : $u; } elseif ($current !== '' && strpos($line, 'pass=') === 0) { $items[$current]['has_pass'] = trim(substr($line, 5)) !== ''; } } return ['items'=>$items, 'read'=>$r, 'path'=>$def['path']]; } ?>