148 lines
4.6 KiB
PHP
148 lines
4.6 KiB
PHP
#!/usr/local/bin/php
|
|
<?php
|
|
declare(strict_types=1);
|
|
|
|
if (realpath((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
|
mailLoginCleanupMain();
|
|
}
|
|
|
|
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',
|
|
];
|
|
|
|
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<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;
|
|
}
|
|
|
|
/**
|
|
* @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 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<string,mixed> $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);
|
|
}
|