#!/usr/local/bin/php 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', ]; if ($config['user'] === '' || $config['key'] === '') { fwrite(STDERR, "DirectAdmin API credentials are required for cleanup\n"); exit(2); } $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"; } /** * @return list> */ 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; } /** * @param array $entry * @param list> $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 $config * @return mixed */ function mailLoginCleanupApiRequest(string $method, string $url, array $config): mixed { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Cannot initialize curl'); } curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPAUTH => CURLAUTH_BASIC, CURLOPT_USERPWD => $config['user'] . ':' . $config['key'], CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 20, CURLOPT_HTTPHEADER => ['Accept: application/json'], ]); $body = curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $error = curl_error($ch); curl_close($ch); if ($body === false || $code >= 400) { throw new RuntimeException($error !== '' ? $error : 'DirectAdmin API request failed'); } if ($method === 'DELETE') { return true; } return json_decode((string)$body, true, 512, JSON_THROW_ON_ERROR); } /** * @param array $fields */ function mailLoginCleanupAudit(string $path, array $fields): void { $dir = dirname($path); if (!is_dir($dir)) { mkdir($dir, 0700, true); } $fields['timestamp'] = gmdate('c'); file_put_contents($path, json_encode($fields, JSON_UNESCAPED_SLASHES) . "\n", FILE_APPEND | LOCK_EX); chmod($path, 0600); }