'danger', 'msg' => 'Token inválido.']; header('Location: ' . $selfUrl); exit; } if (($_POST['action'] ?? 'save') === 'refresh') { // Corre o mesmo script que o cron corre. Não se duplica a lógica de // validação aqui: é ela que impede um download mau de substituir uma // lista boa, e duas cópias divergiriam. $countries = geo_allowed_countries(); if (!$countries) { $_SESSION['flash'] = ['type' => 'warning', 'msg' => 'Defina primeiro os países permitidos.']; } else { $script = __DIR__ . '/bin/geo-update.sh'; $out = []; $code = 1; exec(escapeshellcmd($script) . ' ' . implode(' ', array_map('escapeshellarg', $countries)) . ' 2>&1', $out, $code); $_SESSION['flash'] = $code === 0 ? ['type' => 'success', 'msg' => 'Listas actualizadas. ' . implode(' · ', array_slice($out, 0, 6))] : ['type' => 'danger', 'msg' => 'A actualização falhou (as listas anteriores mantêm-se). ' . implode(' · ', array_slice($out, 0, 6))]; } header('Location: ' . $selfUrl); exit; } $mode = strtolower(trim((string) ($_POST['geo_mode'] ?? 'off'))); if (!in_array($mode, ['off', 'forms', 'site'], true)) { $mode = 'off'; } // Aceita "PT, ES" ou "pt es"; guarda normalizado e sem repetições. Um // código que não seja ISO-3166 alfa-2 é recusado com aviso, e não engolido: // um país mal escrito na lista é indistinguível de "ninguém passa". $rejected = []; $valid = []; foreach (preg_split('/[\s,;]+/', (string) ($_POST['geo_countries'] ?? ''), -1, PREG_SPLIT_NO_EMPTY) ?: [] as $cc) { $up = strtoupper($cc); if (preg_match('/^[A-Z]{2}$/', $up)) { $valid[$up] = true; } else { $rejected[] = $cc; } } $countries = implode(',', array_keys($valid)); $failClosed = !empty($_POST['geo_fail_closed']) ? 'true' : 'false'; // Guarda-rede: ligar o modo `site` sem dados é tirar a plataforma do ar // para toda a gente. Com fail-open não chega a acontecer, mas a combinação // `site` + fail-closed + sem listas é irrecuperável pelo browser. $haveData = false; foreach (array_keys($valid) as $cc) { if (geo_ranges($cc, false) || geo_ranges($cc, true)) { $haveData = true; break; } } if ($mode !== 'off' && $failClosed === 'true' && !$haveData) { $_SESSION['flash'] = ['type' => 'danger', 'msg' => 'Recusado: com "falhar fechado" e sem listas descarregadas, ninguém ' . 'conseguiria entrar — incluindo o utilizador. Actualize as listas primeiro.']; header('Location: ' . $selfUrl); exit; } db_update_setting($pdo, 'geo_mode', $mode, $uid); db_update_setting($pdo, 'geo_allow_countries', $countries, $uid); db_update_setting($pdo, 'geo_fail_closed', $failClosed, $uid); $_SESSION['flash'] = $rejected ? ['type' => 'warning', 'msg' => 'Guardado, mas isto não são códigos de país válidos e foi ignorado: ' . implode(', ', $rejected)] : ['type' => 'success', 'msg' => 'Configuração guardada.']; header('Location: ' . $selfUrl); exit; } // ─── Estado actual ─────────────────────────────────────────────────────────── $mode = geo_mode(); $countries = geo_allowed_countries(); $failClosed = geo_fail_closed(); // De onde veio cada valor — é o que explica um "guardei e não mudou nada". $origin = function (string $key) use ($pdo): string { $db = trim((string) platform_settings_get($pdo, strtolower($key), '')); if ($db !== '') return 'base de dados'; return (function_exists('env') && (string) env($key, '') !== '') ? '.env' : 'omissão'; }; $dataFiles = []; foreach ($countries as $cc) { foreach (['v4', 'v6'] as $fam) { $f = GEO_DATA_DIR . '/' . strtolower($cc) . '.' . $fam; $dataFiles[] = [ 'cc' => $cc, 'fam' => $fam, 'exists' => is_file($f), 'ranges' => is_file($f) ? count(geo_ranges($cc, $fam === 'v6')) : 0, 'mtime' => is_file($f) ? date('Y-m-d H:i', filemtime($f)) : '—', ]; } } if ($probe !== '') { $probeResult = filter_var($probe, FILTER_VALIDATE_IP) ? ['ip' => $probe, 'reason' => geo_block_reason($probe), 'internal' => geo_ip_is_internal($probe)] : ['ip' => $probe, 'invalid' => true]; } $myIp = platform_client_ip() ?? ''; // O stub pode dizer onde fica o hub e a página de submissões bloqueadas; nem // todos os sites os têm no mesmo caminho (o design usa views/admin/). $hubUrl = $platform_dashboard_url ?? (function_exists('platform_admin_url') ? platform_admin_url('index.php') : 'index.php'); $blockedUrl = $platform_blocked_url ?? (function_exists('base_url') ? base_url('views/admin/blocked_submissions.php') : null); $pageTitle = 'Bloqueio por país'; $hdr = dirname(__DIR__, 2) . '/includes/header.php'; if (is_file($hdr)) { include $hdr; } $csrfField = function_exists('csrf_field') ? csrf_field() : (function_exists('platform_csrf_field') ? platform_csrf_field() : ''); $h = fn($s) => htmlspecialchars((string) $s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>
Recusa submissões — ou o site inteiro — a quem chega de fora da lista.
Sem países definidos.
| País | Família | Ranges | Actualizado |
|---|---|---|---|
| = $h($d['cc']) ?> | = $h($d['fam']) ?> |
= (int) $d['ranges'] ?> em falta | = $h($d['mtime']) ?> |
Origem: ipdeny.com. O cron refresca-as mensalmente; as alocações movem-se devagar. Um download curto ou inválido é recusado e a lista anterior mantém-se.
= $h($probeResult['ip']) ?> não é um endereço IP válido.= $h($probeResult['ip']) ?> passa
= !empty($probeResult['internal']) ? ' — é um endereço interno, e esses passam sempre (cron e health checks do próprio contentor).' : '.' ?>
= $h($probeResult['ip']) ?> seria recusado: = $h($probeResult['reason']) ?>
O seu endereço agora é = $h($myIp) ?>
— = $mine === null ? 'passa' : 'seria recusado: ' . $h($mine) . '' ?>.
As recusas ficam registadas com o motivo e o IP em Submissões bloqueadas.