File: //usr/local/mailchannels/storage/file/EximSuspendedListReader.php
<?php
namespace MailChannels;
class EximSuspendedListReader
{
protected string $path;
private array $entries = array();
private bool $hasOnlyTheDefaultEntry = false;
/**
* FileReader constructor
* @param $path string path to a file to read.
* @throws FileNotReadableException
*/
public function __construct(string $path = "/etc/exim_suspended_list") {
$this->path = $path;
$file = fopen($this->path, 'r');
if ($file) {
while (($line = fgets($file)) !== false) {
if (str_starts_with($line, '#') || trim($line) === '') {
continue;
}
$r = $this->generateEximSuspendedRegexFromLine($line);
$this->entries[] = $r;
}
fclose($file);
// if there's only one entry, it's the default entry
if (count($this->entries) == 1) {
$this->hasOnlyTheDefaultEntry = true;
}
} else {
throw new FileNotReadableException("Unable to open file: $this->path");
}
}
/**
* @return bool true if the exim_suspended_list has only the default entry for the Email Delivery Behavior
*/
public function hasOnlyTheDefaultEntry()
{
return $this->hasOnlyTheDefaultEntry;
}
/**
* @return string represents how the mail system processes incoming email for a suspended account
*/
public function getDefaultBehavior()
{
// the last line in the exim_suspended_list is the default entry, so the last element in the $entries array
// is the default behavior
$default = $this->entries[array_key_last($this->entries)]->getAction();
App::getLogger()->debug("exim_suspended_list default behavior: $default");
return $default;
}
/**
* returns true if a domain wildcard matches a regex from the exim_suspended_list
*
* @param string $domain The domain to be checked against the regex patterns.
* @return string the action to take if a domain matches one of the entries in the exim_suspended_list
*/
public function getAction(string $domain)
{
foreach ($this->entries as $eximRegex) {
$r = $eximRegex->getRegex();
$a = $eximRegex->getAction();
if (preg_match($r, $domain)) {
App::getLogger()->debug("regex match $r and $domain, using action $a");
return $a;
}
}
// this case shouldn't happen, a domain should always match the default entry. this could indicate a bug, or an
// error in /etc/exim_suspended_list . we'll return unknown: ":unknown: or an empty aliases will use normal delivery logic."
App::getLogger()->warning("no exim_suspended_list regex matches for $domain");
return "unknown";
}
private function generateEximSuspendedRegexFromLine($line)
{
$parts = explode(':', $line);
$regexSubString = trim($parts[0]);
App::getLogger()->debug("exim_suspended_list raw regex from file: $regexSubString");
$action = trim($parts[2]);
App::getLogger()->debug("exim_suspended_list action from file: $action");
$escapedRegex = preg_quote($regexSubString, '/');
App::getLogger()->debug("exim_suspended_list escaped regex: $escapedRegex");
$regex = '/^' . str_replace('\*', '.*', $escapedRegex) . '$/';
App::getLogger()->debug("exim_suspended_list regex: $regex");
return new EximSuspendedRegex($regex, $action);
}
}