HEX
Server: Apache
System: Linux hz.vslconceptsdomains.com 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
User: dkfounda (3233)
PHP: 8.1.34
Disabled: exec,passthru,shell_exec,system
Upload Files
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);
    }

}