fix: harden mx helper and cleanup flow

This commit is contained in:
Marek Miklewicz
2026-06-30 11:37:27 +02:00
parent ed6146c0d8
commit ad95ecdacb
8 changed files with 252 additions and 43 deletions
+91 -33
View File
@@ -2,51 +2,109 @@
<?php
declare(strict_types=1);
$config = [
'base_url' => getenv('DA_BASE_URL') ?: 'https://127.0.0.1:2222',
'user' => getenv('DA_API_USER') ?: '',
'key' => getenv('DA_API_LOGIN_KEY') ?: '',
'audit_log' => getenv('MAIL_LOGIN_AUDIT_LOG') ?: '/usr/local/hitme_plugins/mail-login-helper/logs/audit.log',
];
if ($config['user'] === '' || $config['key'] === '') {
fwrite(STDERR, "DirectAdmin API credentials are required for cleanup\n");
exit(2);
if (realpath((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
mailLoginCleanupMain();
}
$base = rtrim($config['base_url'], '/');
$now = time();
$entries = apiRequest('GET', $base . '/api/login-keys/urls', $config);
if (!is_array($entries)) {
fwrite(STDERR, "DirectAdmin returned invalid login URL list\n");
exit(1);
}
function mailLoginCleanupMain(): void
{
$config = [
'base_url' => getenv('DA_BASE_URL') ?: 'https://127.0.0.1:2222',
'user' => getenv('DA_API_USER') ?: '',
'key' => getenv('DA_API_LOGIN_KEY') ?: '',
'audit_log' => getenv('MAIL_LOGIN_AUDIT_LOG') ?: '/usr/local/hitme_plugins/mail-login-helper/logs/audit.log',
'state_dir' => getenv('MAIL_LOGIN_STATE_DIR') ?: '/usr/local/hitme_plugins/mail-login-helper/state',
];
$deleted = 0;
foreach ($entries as $entry) {
if (!is_array($entry) || empty($entry['id']) || empty($entry['expires'])) {
continue;
if ($config['user'] === '' || $config['key'] === '') {
fwrite(STDERR, "DirectAdmin API credentials are required for cleanup\n");
exit(2);
}
$expires = strtotime((string)$entry['expires']);
if ($expires !== false && $expires < $now) {
apiRequest('DELETE', $base . '/api/login-keys/urls/' . rawurlencode((string)$entry['id']), $config);
$deleted++;
$base = rtrim($config['base_url'], '/');
$now = time();
$states = mailLoginCleanupLoadStates($config['state_dir']);
$entries = mailLoginCleanupApiRequest('GET', $base . '/api/login-keys/urls', $config);
if (!is_array($entries)) {
fwrite(STDERR, "DirectAdmin returned invalid login URL list\n");
exit(1);
}
$deleted = 0;
foreach ($entries as $entry) {
if (is_array($entry) && mailLoginCleanupShouldDelete($entry, $states, $now)) {
mailLoginCleanupApiRequest('DELETE', $base . '/api/login-keys/urls/' . rawurlencode((string)$entry['id']), $config);
$deleted++;
}
}
mailLoginCleanupAudit($config['audit_log'], [
'event' => 'cleanup_expired_login_urls',
'result' => 'success',
'deleted' => $deleted,
]);
echo "Deleted expired login URLs: {$deleted}\n";
}
audit($config['audit_log'], [
'event' => 'cleanup_expired_login_urls',
'result' => 'success',
'deleted' => $deleted,
]);
/**
* @return list<array<string,mixed>>
*/
function mailLoginCleanupLoadStates(string $stateDir): array
{
$states = [];
foreach (glob(rtrim($stateDir, '/') . '/autologin-*.json') ?: [] as $file) {
$decoded = json_decode((string)file_get_contents($file), true);
if (is_array($decoded)) {
$states[] = $decoded;
}
}
return $states;
}
echo "Deleted expired login URLs: {$deleted}\n";
/**
* @param array<string,mixed> $entry
* @param list<array<string,mixed>> $states
*/
function mailLoginCleanupShouldDelete(array $entry, array $states, int $now): bool
{
if (empty($entry['id']) || empty($entry['expires'])) {
return false;
}
$entryExpires = strtotime((string)$entry['expires']);
if ($entryExpires === false || $entryExpires >= $now) {
return false;
}
foreach ($states as $state) {
if (($state['status'] ?? '') !== 'created') {
continue;
}
$stateExpires = (int)($state['expires'] ?? 0);
if ($stateExpires <= 0 || $stateExpires > $now) {
continue;
}
if ((string)($entry['redirectURL'] ?? '/') !== (string)($state['redirect_path'] ?? '/')) {
continue;
}
$entryNetworks = $entry['allowNetworks'] ?? [];
if (!is_array($entryNetworks)) {
$entryNetworks = [];
}
$stateIp = (string)($state['client_ip'] ?? '');
$ipBound = (int)($state['ip_bound'] ?? 0) === 1;
if ($ipBound && !in_array($stateIp . (str_contains($stateIp, ':') ? '/128' : '/32'), $entryNetworks, true)) {
continue;
}
return true;
}
return false;
}
/**
* @param array<string,string> $config
* @return mixed
*/
function apiRequest(string $method, string $url, array $config): mixed
function mailLoginCleanupApiRequest(string $method, string $url, array $config): mixed
{
$ch = curl_init($url);
if ($ch === false) {
@@ -77,7 +135,7 @@ function apiRequest(string $method, string $url, array $config): mixed
/**
* @param array<string,mixed> $fields
*/
function audit(string $path, array $fields): void
function mailLoginCleanupAudit(string $path, array $fields): void
{
$dir = dirname($path);
if (!is_dir($dir)) {