#!/usr/local/bin/php -nc/usr/local/directadmin/plugins/borg-restore/php.ini null, 'REPO' => null, 'BORG_SSH_KEY' => null, ]; if (!is_readable($path)) { return $vars; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $vars; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(?:export\s+)?(BORG_PASSPHRASE|REPO|BORG_SSH_KEY)=(.*)$/', $line, $m)) { $key = $m[1]; $vars[$key] = parse_sh_value($m[2]); } } return $vars; } function load_settings(string $path): array { $settings = [ 'BB_PATH' => '/opt/bb', 'BORG_PATH' => '/usr/bin/borg', 'BORG_VARIABLES_FILE' => '/opt/bb/bbvars.sh', 'ADMIN_MODE_ENABLE' => 'TRUE', 'RESULTS_PER_PAGE' => '10', 'WEB_RESTORE_LOCATION' => '/home/DA_USER/domains', 'MAIL_RESTORE_LOCATION' => '/home/DA_USER/imap', 'ENABLE_WEB_RESTORE' => 'TRUE', 'ENABLE_MAILBOX_RESTORE' => 'TRUE', 'DEFAULT_RESTORE_DESTINATION' => '/home/DA_USER/bb', 'BLOCK_RESTORE_OUTSIDE_LOCATION' => 'TRUE', 'PREVENT_RESTORE_MAIN_DOMAINS_DIR' => 'TRUE', 'PREVENT_RESTORE_MAIN_IMAP_DIR' => 'TRUE', 'PREVENT_CREATE_DIRS_IN_MAIN_DOMAIN' => 'TRUE', 'PREVENT_CREATE_DIRS_IN_MAIN_IMAP' => 'TRUE', 'HASH_VISIBILITY' => 'FALSE', 'ENABLE_LOGS_OVERLAY' => 'TRUE', 'SELECTABLE_DOMAIN_RESTORE_DESTINATION' => 'TRUE', 'SELECTABLE_MAIL_RESTORE_DESTINATION' => 'TRUE', 'CLEAR_DOVECOT_INDEXES_AFTER_MERGE' => 'TRUE', 'RESTORE_PATH_MODE' => 'dir', 'ADD_DATE_TO_RESTORED_DIR' => 'FALSE', 'DEBUG_MODE' => 'FALSE', ]; if (!is_readable($path)) { return $settings; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $settings; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(BB_PATH|BORG_PATH|BORG_VARIABLES_FILE|ADMIN_MODE_ENABLE|RESULTS_PER_PAGE|WEB_RESTORE_LOCATION|MAIL_RESTORE_LOCATION|ENABLE_WEB_RESTORE|ENABLE_MAILBOX_RESTORE|DEFAULT_RESTORE_DESTINATION|BLOCK_RESTORE_OUTSIDE_LOCATION|PREVENT_RESTORE_MAIN_DOMAINS_DIR|PREVENT_RESTORE_MAIN_IMAP_DIR|PREVENT_CREATE_DIRS_IN_MAIN_DOMAIN|PREVENT_CREATE_DIRS_IN_MAIN_IMAP|HASH_VISIBILITY|ENABLE_LOGS_OVERLAY|SELECTABLE_DOMAIN_RESTORE_DESTINATION|SELECTABLE_MAIL_RESTORE_DESTINATION|CLEAR_DOVECOT_INDEXES_AFTER_MERGE|RESTORE_PATH_MODE|ADD_DATE_TO_RESTORED_DIR|DEBUG_MODE)=(.*)$/', $line, $m)) { $settings[$m[1]] = parse_sh_value($m[2]); } } return $settings; } function file_owner_name(int $uid): string { if (function_exists('posix_getpwuid')) { $info = posix_getpwuid($uid); if (is_array($info) && !empty($info['name'])) { return (string)$info['name']; } } return (string)$uid; } function validate_secure_file(string $path, string $label, bool $check_writable = true): string { if ($path === '') { return $label . ' ma pustą ścieżkę.'; } if (!file_exists($path)) { return $label . ' nie istnieje: ' . $path; } if (is_link($path) || !is_file($path)) { return $label . ' nie jest zwykłym plikiem: ' . $path; } $perms = fileperms($path); if ($perms === false) { return $label . ' ma nieprawidłowe uprawnienia: ' . $path; } if ($check_writable && (($perms & 0x0010) !== 0 || ($perms & 0x0002) !== 0)) { return $label . ' nie może być zapisywalny dla grupy/innych: ' . $path; } $uid = fileowner($path); if ($uid === false) { return $label . ' ma nieprawidłowego właściciela: ' . $path; } $owner = file_owner_name((int)$uid); if ($uid !== 0 && $uid !== 500 && $owner !== 'root' && $owner !== 'diradmin') { return $label . ' ma nieprawidłowego właściciela (' . $owner . '): ' . $path; } return ''; } function validate_job_files(string $job_base): array { $errors = []; foreach (['pending' => '*.env', 'processing' => '*.env', 'done' => '*.{ok,fail,cancel,env.ok,env.fail,env.cancel}'] as $dir => $pattern) { foreach (glob($job_base . '/' . $dir . '/' . $pattern, GLOB_BRACE) ?: [] as $path) { $err = validate_secure_file($path, 'Plik zadania', false); if ($err !== '') { $errors[] = $err; } } } return $errors; } function safe_user_key(string $value): string { $key = preg_replace('/[^A-Za-z0-9_.-]/', '_', $value); return $key === '' ? 'unknown' : $key; } function get_csrf_token(string $data_dir, string $da_user): string { $dir = rtrim($data_dir, '/') . '/csrf'; if (!is_dir($dir)) { @mkdir($dir, 0700, true); } $path = $dir . '/' . safe_user_key($da_user) . '.token'; if (is_readable($path)) { $token = trim((string)file_get_contents($path)); if ($token !== '') { return $token; } } $token = bin2hex(random_bytes(32)); @file_put_contents($path, $token, LOCK_EX); @chmod($path, 0600); return $token; } function csrf_is_valid(array $params, string $token): bool { $sent = $params['csrf_token'] ?? ''; if (!is_string($sent) || $sent === '' || $token === '') { return false; } return hash_equals($token, $sent); } function read_params(): array { $params = []; if (!empty($_GET)) { $params = array_merge($params, $_GET); } if (!empty($_POST)) { $params = array_merge($params, $_POST); } $get_raw = getenv('GET') ?: getenv('get') ?: ''; if ($get_raw !== '') { parse_str($get_raw, $parsed); $params = array_merge($parsed, $params); } $qs = $_SERVER['QUERY_STRING'] ?? ''; if ($qs !== '') { parse_str($qs, $parsed); $params = array_merge($parsed, $params); } $post_raw = getenv('POST') ?: getenv('post') ?: ''; if ($post_raw !== '') { parse_str($post_raw, $parsed); $params = array_merge($parsed, $params); } return $params; } function load_job_vars(string $path): array { $vars = [ 'DA_USER' => '', 'REPONAME' => '', 'USERRESTOREPATH' => '', 'TARGETPATH' => '', 'RESTOREMODE' => '', 'RESTORETYPE' => '', 'START_TS' => '', ]; if (!is_readable($path)) { return $vars; } $lines = file($path, FILE_IGNORE_NEW_LINES); if ($lines === false) { return $vars; } foreach ($lines as $line) { $line = trim($line); if ($line === '' || $line[0] === '#') { continue; } if (preg_match('/^(DA_USER|REPONAME|USERRESTOREPATH|TARGETPATH|RESTOREMODE|RESTORETYPE|START_TS|USERLANG)=(.*)$/', $line, $m)) { $vars[$m[1]] = parse_sh_value($m[2]); } } return $vars; } function job_id_from_file(string $file): string { $base = basename($file); if (substr($base, -3) === '.ok') { $base = substr($base, 0, -3); } elseif (substr($base, -5) === '.fail') { $base = substr($base, 0, -5); } if (substr($base, -4) === '.env') { $base = substr($base, 0, -4); } return $base; } function find_job_file(string $job_base, string $file, string $job_id): string { $candidates = [ $job_base . '/processing/' . $file, $job_base . '/pending/' . $file, $job_base . '/done/' . $file, ]; if ($job_id !== '') { $candidates[] = $job_base . '/processing/' . $job_id . '.env'; $candidates[] = $job_base . '/pending/' . $job_id . '.env'; $candidates[] = $job_base . '/done/' . $job_id . '.ok'; $candidates[] = $job_base . '/done/' . $job_id . '.fail'; $candidates[] = $job_base . '/done/' . $job_id . '.cancel'; } foreach ($candidates as $path) { if (is_file($path)) { return $path; } } return ''; } function job_belongs_to_user(string $job_file_path, string $da_user): bool { if ($job_file_path === '' || !is_readable($job_file_path)) { return false; } $vars = load_job_vars($job_file_path); $owner = (string)($vars['DA_USER'] ?? ''); return $owner !== '' && $owner === $da_user; } function find_job_file_for_user(string $job_base, string $file, string $job_id, string $da_user): string { $path = find_job_file($job_base, $file, $job_id); if ($path === '') { return ''; } return job_belongs_to_user($path, $da_user) ? $path : ''; } function format_log_body_for_download(string $text): string { $text = normalize_log($text); $text = preg_replace('/[ \t]+(?=\d{1,3}(?:\.\d+)?%\s+(?:Extracting|Deleting|Pruning|Compacting|Saving|Uploading|Downloading|Processing|Restoring|Creating)\b)/i', "\n", $text); $text = preg_replace('/\n{2,}(?=\d{1,3}(?:\.\d+)?%)/', "\n", $text); $text = preg_replace('/\n{3,}/', "\n\n", $text); return rtrim($text) . "\n"; } function log_missing_include_explanation(string $log_text, string $restore_path_label = ''): string { if (stripos($log_text, 'W przywracanej kopii zapasowej nie odnaleziono wskazanej ścieżki') !== false) { return ''; } if (stripos($log_text, 'The specified restore path was not found in the backup') !== false) { return ''; } if (preg_match("/Include pattern '.*' never matched\\./i", $log_text)) { $suffix = ''; if ($restore_path_label !== '' && $restore_path_label !== '-') { $suffix = ' (' . $restore_path_label . ')'; } return 'W przywracanej kopii zapasowej nie odnaleziono wskazanej ścieżki' . $suffix . '. Prosimy spróbować przywrócić dane z innego archiwum, w którym te pliki mogły się znajdować.'; } return ''; } function format_datetime(string $value, int $fallback = 0): string { $ts = is_numeric($value) ? (int)$value : 0; if ($ts <= 0) { $ts = $fallback > 0 ? $fallback : time(); } return date('d-m-Y-H-i-s', $ts); } function build_log_download_content(string $job_id, string $job_file_path, string $log_path, string $job_base): string { $vars = load_job_vars($job_file_path); $log_meta_start = format_datetime($vars['START_TS'] ?? '', time()); $log_meta_path = display_restore_path($vars['USERRESTOREPATH'] ?? ''); $log_meta_mode = restore_mode_label($vars['RESTOREMODE'] ?? ''); $log_meta_type = restore_type_label($vars['RESTORETYPE'] ?? ''); $log_meta_target = display_restore_path($vars['TARGETPATH'] ?? ''); if ($log_meta_path === '') { $log_meta_path = '-'; } if ($log_meta_mode === '') { $log_meta_mode = '-'; } if ($log_meta_type === '') { $log_meta_type = '-'; } if ($log_meta_target === '') { $log_meta_target = '-'; } $log_text = is_file($log_path) && is_readable($log_path) ? (string)file_get_contents($log_path) : ''; $log_text = normalize_log($log_text); $last_line = last_non_empty_line($log_text); $progress = extract_progress($last_line); $base_status = get_job_status($job_id, $job_base); $display = display_status($base_status, $last_line); $status_label = status_label($display); if (in_array($display, ['done', 'failed', 'canceled'], true)) { $progress_text = $display === 'done' ? '100%' : '0%'; } else { $progress_text = $progress !== null ? ($progress . '%') : ($last_line !== '' ? $last_line : '-'); } $header = "Data rozpoczęcia: {$log_meta_start}\n"; $header .= "Rodzaj przywracanych danych: {$log_meta_type}\n"; $header .= "Ścieżka do przywrócenia: {$log_meta_path}\n"; $header .= "Tryb przywracania: {$log_meta_mode}\n"; $header .= "Lokalizacja docelowa: {$log_meta_target}\n"; $header .= "Status: {$status_label}\n"; $header .= "Postęp: {$progress_text}\n"; $header .= "----- logi Borg -----\n"; $body = $log_text !== '' ? format_log_body_for_download($log_text) : "Log nie istnieje.\n"; $explanation = log_missing_include_explanation($log_text, $log_meta_path); if ($explanation !== '') { $body = rtrim($body) . "\n\n" . $explanation . "\n"; } return $header . $body; } function ajax_output(array $payload): void { global $lang_map; header('Content-Type: text/plain; charset=utf-8'); $json = json_encode($payload, JSON_UNESCAPED_UNICODE); echo '__BORG_AJAX_JSON__'; if ($json === false) { $json = '{}'; } echo translate_text($json, $lang_map ?? []); echo '__BORG_AJAX_JSON__'; exit; } function log_text_output(string $text): void { header('Content-Type: text/plain; charset=utf-8'); echo '__BORG_LOG_TEXT__'; echo $text; echo '__BORG_LOG_TEXT__'; exit; } function status_html_output(string $html): void { header('Content-Type: text/plain; charset=utf-8'); echo '__BORG_STATUS_HTML__'; echo $html; echo '__BORG_STATUS_HTML__'; exit; } function delete_job_assets(string $file, string $job_base, string $log_dir, string $pid_dir, string $cancel_dir): bool { $file = basename($file); if ($file === '' || !preg_match('/^[-A-Za-z0-9_.]+$/', $file)) { return false; } $job_id = job_id_from_file($file); if ($job_id === '') { return false; } $deleted = false; foreach (['pending', 'processing', 'done'] as $dir) { $dir_path = $job_base . '/' . $dir; foreach (glob($dir_path . '/' . $job_id . '*') ?: [] as $path) { if (is_file($path)) { @unlink($path); $deleted = true; } } $exact = $dir_path . '/' . $file; if (is_file($exact)) { @unlink($exact); $deleted = true; } } $log_path = $log_dir . '/' . $job_id . '.log'; if (is_file($log_path)) { @unlink($log_path); } $pid_path = $pid_dir . '/' . $job_id . '.pid'; if (is_file($pid_path)) { @unlink($pid_path); } $cancel_path = $cancel_dir . '/' . $job_id . '.flag'; if (is_file($cancel_path)) { @unlink($cancel_path); } return $deleted; } function list_jobs(string $dir, string $status, string $pattern, string $log_dir, string $pid_dir, string $cancel_dir, string $da_user): array { $items = []; foreach (glob($dir . '/' . $pattern) ?: [] as $path) { $vars = load_job_vars($path); $owner = (string)($vars['DA_USER'] ?? ''); if ($owner === '' || $owner !== $da_user) { continue; } $job_id = job_id_from_file($path); $log_path = $log_dir . '/' . $job_id . '.log'; $cancel_requested = is_file($cancel_dir . '/' . $job_id . '.flag'); $pid = ''; if ($status === 'processing') { $pid_path = $pid_dir . '/' . $job_id . '.pid'; if (is_readable($pid_path)) { $pid = trim((string)file_get_contents($pid_path)); } elseif (!empty($vars['PID'])) { $pid = (string)$vars['PID']; } if ($pid !== '' && !preg_match('/^[0-9]+$/', $pid)) { $pid = ''; } if ($pid !== '' && function_exists('posix_kill')) { if (!@posix_kill((int)$pid, 0)) { $pid = ''; } } } $progress_text = '-'; $display_status = $status; if ($status === 'processing' && is_readable($log_path)) { $info = get_progress_info($log_path); $display_status = display_status($status, $info['last']); if ($cancel_requested) { $display_status = 'canceled'; $progress_text = 'Anulowane'; } elseif ($display_status === 'calculating') { $progress_text = 'Przygotowanie'; } elseif ($info['progress'] !== null) { $progress_text = $info['progress'] . '%'; } elseif ($info['last'] !== '') { $progress_text = $info['last']; } } elseif ($status === 'done') { $progress_text = '100%'; } elseif ($status === 'failed') { $progress_text = 'Błąd'; } elseif ($status === 'canceled') { $progress_text = 'Anulowane'; } $items[] = [ 'status' => $status, 'display_status' => $display_status, 'file' => basename($path), 'time' => @filemtime($path) ?: 0, 'user' => $vars['DA_USER'] ?? '', 'repo' => $vars['REPONAME'] ?? '', 'path' => $vars['USERRESTOREPATH'] ?? '', 'target' => $vars['TARGETPATH'] ?? '', 'restore_mode' => $vars['RESTOREMODE'] ?? '', 'restore_mode_label' => restore_mode_label($vars['RESTOREMODE'] ?? ''), 'restore_type' => $vars['RESTORETYPE'] ?? '', 'restore_type_label' => restore_type_label($vars['RESTORETYPE'] ?? ''), 'job_id' => $job_id, 'log_exists' => is_readable($log_path), 'progress_text' => $progress_text, 'pid' => $pid, 'cancel_requested' => $cancel_requested, ]; } return $items; } function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } function status_label(string $status): string { $map = [ 'pending' => 'Oczekuje', 'processing' => 'W trakcie', 'calculating' => 'Przygotowanie', 'done' => 'Zakończone', 'failed' => 'Błąd', 'canceled' => 'Anulowane', ]; $label = $map[$status] ?? 'Nieznany'; return t($label); } function read_log_tail(string $path, int $max_bytes = 200000): string { if (!is_readable($path)) { return ''; } $size = filesize($path); if ($size === false || $size <= $max_bytes) { return (string)file_get_contents($path); } $fp = fopen($path, 'rb'); if ($fp === false) { return ''; } fseek($fp, -$max_bytes, SEEK_END); $data = fread($fp, $max_bytes); fclose($fp); return (string)$data; } function normalize_log(string $text): string { $text = str_replace("\r", "\n", $text); $lines = preg_split('/\n/', $text); if (!is_array($lines)) { return $text; } $filtered = []; foreach ($lines as $line) { $trimmed = trim($line); if ($trimmed !== '' && stripos($trimmed, 'Permanently added') !== false && stripos($trimmed, 'known host') !== false) { continue; } $filtered[] = $line; } return implode("\n", $filtered); } function last_non_empty_line(string $text): string { $lines = preg_split('/\r\n|\n|\r/', $text); if (!is_array($lines)) { return ''; } for ($i = count($lines) - 1; $i >= 0; $i--) { $line = trim((string)$lines[$i]); if ($line !== '') { return $line; } } return ''; } function is_calculating_line(string $line): bool { return (bool)preg_match('/calculat/i', $line); } function extract_progress(string $line): ?int { if (preg_match('/\b(\d{1,3})%\b/', $line, $m)) { $p = (int)$m[1]; if ($p < 0) { $p = 0; } elseif ($p > 100) { $p = 100; } return $p; } return null; } function get_job_status(string $job_id, string $job_base): string { if ($job_id === '') { return 'unknown'; } if (is_file($job_base . '/processing/' . $job_id . '.env')) { return 'processing'; } if (is_file($job_base . '/pending/' . $job_id . '.env')) { return 'pending'; } if (is_file($job_base . '/done/' . $job_id . '.ok') || is_file($job_base . '/done/' . $job_id . '.env.ok')) { return 'done'; } if (is_file($job_base . '/done/' . $job_id . '.fail') || is_file($job_base . '/done/' . $job_id . '.env.fail')) { return 'failed'; } if (is_file($job_base . '/done/' . $job_id . '.cancel') || is_file($job_base . '/done/' . $job_id . '.env.cancel')) { return 'canceled'; } return 'unknown'; } function get_progress_info(string $log_path): array { $text = normalize_log(read_log_tail($log_path, 20000)); $last = last_non_empty_line($text); $progress = extract_progress($last); $calculating = $last !== '' && is_calculating_line($last); return [ 'last' => $last, 'progress' => $progress, 'calculating' => $calculating, ]; } function display_status(string $base_status, string $last_line): string { if ($base_status === 'processing' && $last_line !== '' && is_calculating_line($last_line)) { return 'calculating'; } return $base_status; } function apply_da_user(string $value, string $da_user): string { return str_replace('DA_USER', $da_user, $value); } function restore_mode_label(string $mode): string { if ($mode === 'overwrite') { return t('Nadpisz aktualne dane'); } if ($mode === 'update') { return t('Scalenie'); } if ($mode === 'location') { return t('Przywracanie do lokalizacji'); } return t('-'); } function restore_mode_label_form(string $mode): string { if ($mode === 'overwrite') { return t('Nadpisz aktualne dane'); } if ($mode === 'update') { return t('Połączenie danych kopii zapasowej z obecnymi danymi'); } if ($mode === 'location') { return t('Przywracanie do lokalizacji'); } return t('-'); } function restore_type_label(string $type): string { if ($type === 'mail') { return t('Zawartość skrzynki pocztowej'); } if ($type === 'web') { return t('Pliki www'); } return t('-'); } function format_start_time($value, int $fallback): string { $ts = is_numeric($value) ? (int)$value : 0; if ($ts <= 0) { $ts = $fallback > 0 ? $fallback : time(); } return date('d-m-Y-H-i-s', $ts); } function parse_bool_setting(?string $value, bool $default): bool { if ($value === null) { return $default; } $value = strtolower(trim($value)); if ($value === '') { return $default; } if (in_array($value, ['1', 'true', 'yes', 'on'], true)) { return true; } if (in_array($value, ['0', 'false', 'no', 'off'], true)) { return false; } return $default; } function display_restore_path(string $value): string { $trimmed = trim($value); if ($trimmed === '') { return ''; } if ($trimmed[0] !== '/') { $trimmed = '/' . $trimmed; } return $trimmed; } function append_date_to_last_dir(string $path, string $date): string { $path = rtrim($path, '/'); if ($path === '' || $path === '/') { return $path; } $parent = dirname($path); $base = basename($path); if ($base === '' || $base === '.' || $base === '..') { return $path; } if ($parent === '/' || $parent === '.') { return '/' . $base . '_' . $date; } return $parent . '/' . $base . '_' . $date; } function describe_overwrite_backup(string $restore_path_raw): string { $restore_path = trim($restore_path_raw, "/"); if ($restore_path === '') { return 'Backup zostanie utworzony jako *_przed_przywroceniem_' . date('d-m-Y_His') . ' w katalogu nadrzędnym ścieżki do przywrócenia.'; } $full = '/' . $restore_path; $base = basename($full); $parent = dirname($full); if ($base === '' || $parent === '' || $parent === '.') { return 'Backup zostanie utworzony jako *_przed_przywroceniem_' . date('d-m-Y_His') . ' w katalogu nadrzędnym ścieżki do przywrócenia.'; } $backup = $parent . '/' . $base . '_przed_przywroceniem_' . date('d-m-Y_His'); return 'Backup zostanie utworzony jako: ' . $backup . '.'; } function render_confirm_overwrite_html(string $restore_path_raw, string $selected_archive, string $target_path_raw, string $restore_mode, string $restore_type, bool $ajax, string $csrf_token): string { $mode_label = restore_mode_label_form($restore_mode); $title = $restore_mode === 'update' ? 'Potwierdź scalenie danych' : 'Potwierdź nadpisanie danych'; $warning = $restore_mode === 'update' ? 'Wybrano tryb scalenia danych. Istniejący katalog zostanie połączony z danymi z backupu.' : 'Wybrano tryb nadpisywania danych. Istniejący katalog zostanie zastąpiony.'; $loss_warning = ''; if ($restore_mode === 'overwrite' && $restore_type === 'mail') { $path_display = $restore_path_raw !== '' ? display_restore_path($restore_path_raw) : '-'; $loss_warning = 'DANE W KATALOGU ' . $path_display . ' ZOSTANĄ UTRACONE.'; } ob_start(); ?>
false, 'error' => 'json_encode_failed'], JSON_UNESCAPED_UNICODE); } $marker = '__BORG_PICKER_JSON__'; header('Content-Type: text/plain; charset=utf-8'); echo $marker . "\n" . $json . "\n" . $marker; } function picker_base_for_scope(array $params, string $web_location, string $mail_location, bool $block_restore_outside_location, string $da_user): string { $restore_type = strtolower((string)($params['restore_type'] ?? 'web')); if ($restore_type !== 'mail') { $restore_type = 'web'; } $scope = strtolower((string)($params['scope'] ?? 'restore')); $base = $restore_type === 'mail' ? $mail_location : $web_location; if ($scope === 'target') { $home_base = '/home'; if ($da_user !== '') { $home_base .= '/' . $da_user; } return $home_base; } if ($scope !== 'restore' || !$block_restore_outside_location) { return ''; } return $base; } function file_picker_handle(array $params, string $path_prefix): void { $base = $path_prefix !== '' ? $path_prefix : '/'; $mode = strtolower((string)($params['mode'] ?? 'dir')); $mode = ($mode === 'file') ? 'file' : 'dir'; $requested = trim((string)($params['path'] ?? '')); if ($requested !== '' && $requested[0] !== '/') { $requested = '/' . $requested; } $selected = ''; if ($mode === 'file' && $requested !== '') { $real_req = @realpath($requested); if ($real_req !== false && is_file($real_req)) { $selected = $real_req; $requested = dirname($real_req); } } [$resolved, $base, $ok] = resolve_picker_path($requested, $base); if ($base !== '/' && $selected !== '') { if (strpos($selected, $base . '/') !== 0 && $selected !== $base) { $selected = ''; } } $dir_items = []; $file_items = []; if ($ok && is_dir($resolved)) { $entries = @scandir($resolved); if (is_array($entries)) { foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') { continue; } $full = $resolved . '/' . $entry; if (@is_dir($full)) { $dir_items[] = $entry; } elseif ($mode === 'file' && @is_file($full)) { $file_items[] = $entry; } } } } natcasesort($dir_items); natcasesort($file_items); $items = []; foreach ($dir_items as $name) { $items[] = ['name' => $name, 'type' => 'dir']; } foreach ($file_items as $name) { $items[] = ['name' => $name, 'type' => 'file']; } picker_output([ 'ok' => $ok, 'path' => $resolved, 'base' => $base, 'items' => $items, 'selected' => $selected, ]); } function file_picker_create(array $params, string $path_prefix, string $da_user, bool $prevent_main_domains, bool $prevent_main_imap): void { $base = $path_prefix !== '' ? $path_prefix : '/'; $parent_req = trim((string)($params['path'] ?? '')); if ($parent_req !== '' && $parent_req[0] !== '/') { $parent_req = '/' . $parent_req; } [$parent, $base, $ok] = resolve_picker_path($parent_req, $base); $name = trim((string)($params['name'] ?? '')); if ($name === '' || $name === '.' || $name === '..' || strpos($name, '/') !== false || strpos($name, '\\') !== false) { picker_output(['ok' => false, 'error' => 'invalid_name']); return; } if (!$ok || !is_dir($parent)) { picker_output(['ok' => false, 'error' => 'invalid_parent']); return; } if ($da_user !== '') { $parent_real = @realpath($parent); if ($parent_real !== false) { $domains_root = '/home/' . $da_user . '/domains'; $imap_root = '/home/' . $da_user . '/imap'; if ($prevent_main_domains && $parent_real === $domains_root) { picker_output(['ok' => false, 'error' => 'main_domain_blocked']); return; } if ($prevent_main_imap && $parent_real === $imap_root) { picker_output(['ok' => false, 'error' => 'main_imap_blocked']); return; } } } $new_path = rtrim($parent, '/') . '/' . $name; if ($base !== '/' && strpos($new_path, $base . '/') !== 0 && $new_path !== $base) { picker_output(['ok' => false, 'error' => 'outside_base']); return; } if (file_exists($new_path)) { picker_output(['ok' => false, 'error' => 'exists']); return; } if (!@mkdir($new_path, 0700, true)) { picker_output(['ok' => false, 'error' => 'mkdir_failed']); return; } @chown($new_path, $da_user); @chgrp($new_path, $da_user); picker_output(['ok' => true, 'path' => $parent, 'base' => $base, 'created' => $new_path]); } function render_jobs_section(array $jobs, string $csrf_token, bool $enable_logs_overlay): string { ob_start(); if (!empty($jobs)) { ?>