#!/usr/local/bin/php $env */ public function handle(array $env, string $message): int { if ($this->settings->backendMode() !== 'exim') { return 0; } $sender = self::firstEnv($env, ['SENDER_ADDRESS', 'sender_address', 'SENDER', 'sender']); $recipient = self::recipientFromEnv($env); if ($sender === '' || $recipient === '' || !self::isEmail($sender) || !self::isEmail($recipient)) { return 0; } $headers = self::parseHeaders($message); if (MessageClassifier::shouldSuppress($headers, $sender)) { return 0; } $resolved = $this->mailboxes->resolveRecipient($recipient); if ($resolved === null) { return 0; } $sent = 0; $owner = $resolved['owner']; $recipient = $resolved['email']; foreach ($this->rules->all($owner) as $rule) { if (!$this->isActiveRule($rule)) { continue; } if (!$this->ruleIncludesRecipient($rule, $recipient)) { continue; } $ruleId = (string)($rule['id'] ?? ''); if ($ruleId === '') { continue; } $repeatMinutes = !empty($rule['reply_every_message']) ? 0 : max(0, (int)($rule['repeat_minutes'] ?? 1440)); if (!$this->repeat->shouldSend($owner, $ruleId, $recipient, $sender, $repeatMinutes)) { continue; } $replySubject = self::replySubject( (string)($rule['subject_prefix'] ?? $rule['subject'] ?? ''), self::headerValue($headers, 'subject') ); $ok = (bool)call_user_func( $this->send, $recipient, strtolower($sender), $replySubject, (string)$rule['body'] ); if ($ok) { $this->repeat->record($owner, $ruleId, $recipient, $sender); $sent++; } } return $sent; } /** @param array $rule */ private function isActiveRule(array $rule): bool { if (empty($rule['enabled'])) { return false; } $now = $this->now ?? time(); return (int)($rule['start_ts'] ?? 0) <= $now && (int)($rule['end_ts'] ?? 0) >= $now; } /** @param array $rule */ private function ruleIncludesRecipient(array $rule, string $recipient): bool { if (!array_key_exists('dynamic_scope', $rule) || !empty($rule['dynamic_scope'])) { return true; } $snapshot = $rule['mailbox_snapshot'] ?? []; if (!is_array($snapshot)) { return false; } foreach ($snapshot as $mailbox) { if (is_array($mailbox) && strtolower((string)($mailbox['email'] ?? '')) === strtolower($recipient)) { return true; } } return false; } /** @param array $env @param string[] $keys */ private static function firstEnv(array $env, array $keys): string { foreach ($keys as $key) { $value = trim((string)($env[$key] ?? '')); if ($value !== '') { return strtolower($value); } } return ''; } /** @param array $env */ private static function recipientFromEnv(array $env): string { $direct = self::firstEnv($env, ['LOCAL_PART_DOMAIN', 'RECIPIENT', 'ORIGINAL_RECIPIENT', 'local_part_domain']); if ($direct !== '') { return $direct; } $local = self::firstEnv($env, ['LOCAL_PART', 'local_part']); $domain = self::firstEnv($env, ['DOMAIN', 'domain']); return ($local !== '' && $domain !== '') ? $local . '@' . $domain : ''; } private static function isEmail(string $value): bool { return (bool)preg_match('/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+$/', $value); } /** @return array */ public static function parseHeaders(string $message): array { $headers = []; $current = ''; foreach (preg_split('/\r\n|\n|\r/', $message) ?: [] as $line) { if ($line === '') { break; } if ($current !== '' && preg_match('/^[ \t]/', $line)) { $headers[$current] .= ' ' . trim($line); continue; } if (!str_contains($line, ':')) { continue; } [$key, $value] = explode(':', $line, 2); $current = trim($key); if ($current !== '') { $headers[$current] = trim($value); } } return $headers; } /** @param array $headers */ private static function headerValue(array $headers, string $name): string { foreach ($headers as $key => $value) { if (strcasecmp($key, $name) === 0) { return trim($value); } } return ''; } private static function replySubject(string $prefix, string $originalSubject): string { $prefix = trim($prefix); $originalSubject = trim($originalSubject); if ($prefix !== '' && $originalSubject !== '') { return str_ends_with($prefix, ':') ? $prefix . ' ' . $originalSubject : $prefix . ': ' . $originalSubject; } if ($prefix !== '') { return $prefix; } return $originalSubject !== '' ? $originalSubject : 'Automatic reply'; } } if (PHP_SAPI === 'cli' && realpath((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) { $settings = Settings::load(Settings::EXTERNAL_SETTINGS); $sender = new MailSender(); $worker = new GlobalAutoresponderWorker( $settings, new RuleRepository(), new MailboxDirectory(), new RepeatState(), static fn (string $from, string $to, string $subject, string $body): bool => $sender->send('/usr/sbin/sendmail', $from, $to, $subject, $body) ); $worker->handle(getenv(), stream_get_contents(STDIN) ?: ''); }