Skip to content
MSFE-NG edited this page Nov 4, 2026 · 12 revisions

Config

Every configuration file of the scanning chain, edited in place with validation, history and a tester — plus the maintenance tools. Everything here is also available from the CLI.

Configuration files

The file tree

The left column lists every file under MailScanner's configuration directory (/etc/MailScanner, or /usr/mailscanner/etc on a ConfigServer layout) plus MSFE-NG's own config.toml, grouped by directory: the main files, conf.d/ fragments, mcp/, rules/, custom/ and the reports/<language>/ notification templates. Each file carries a badge for the editor it gets and a lock when it is read-only. find a file… filters the tree by name; search in files… (Enter) looks for a text, case-insensitively, inside every file: the results replace the tree, grouped by file with the line number and the line, and clicking one opens that file at that line (the raw editor, scrolled and selected). Escape or clearing the box brings the tree back. CLI: msfe-ng conf grep <text> prints id:line: text for the same search.

Badge Files Editor
settings MailScanner.conf, conf.d/*.conf, config.toml form: one field per setting, comments shown in between
ruleset rules/*.rules that sync does not generate table: direction, pattern, value; drag rows (⋮⋮) or ↑ ↓ to reorder
file rules filename.rules.conf, filetype.rules.conf, archives.* table: action, pattern, log text, user text; drag rows (⋮⋮) or ↑ ↓ to reorder
RBL list spam.lists.conf table: name, DNS zone
scanners virus.scanners.conf table: name, wrapper, install dir
hosts phishing.*.custom one hostname per row (paste many at once)
text spamassassin.conf, mcp/*, country.domains.conf, reports/* raw text
🔒 the ten rulesets sync generates, phishing.*.conf (ms-update-phishing), custom/*.pm, defaults, *.rpmnew view only — the lock's tooltip says who owns the file

Order matters. Rulesets and file rules are read top to bottom and the first match wins, so a rule placed below a broader one may never fire. Drag a row by its ⋮⋮ handle (or use ↑ ↓) to move it; its comment moves with it and the file's header stays on top. In file rules, ⚠ marks a rule that an earlier one hides — the earlier rule matches every example name of it first and has a different action (same-action duplicates are not flagged); hover the ⚠ for an example name, click it to jump to the rule that wins. The tester and msfe-ng conf test report the same as a warning.

Form ⇄ Raw text switches any editor to the plain file. + New creates a conf.d/<name>.conf fragment (read after MailScanner.conf, so it overrides it) or a new rules/<name>.rules ruleset. The filter box finds a file by name.

MailScanner.conf settings

With the engine installed, every setting is typed from MailScanner's own ConfigDefs.pl: yes/no settings are selects, numbers are number fields, and settings that may take a ruleset get a ruleset… picker listing rules/*.rules. ≠ default marks a value that differs from the engine default (click it to reset), unknown a directive the engine does not know (a typo — MailScanner ignores it silently), overridden in … a setting a conf.d fragment sets again. Only settings that differ from the engine default shows what this host actually changed.

Saving

Every save goes through the same pipeline, for every file kind:

  1. the file's own parser checks the syntax (a table cannot hold a line MailScanner would misread);
  2. the change is validated on a staged copy of the whole configuration directory: MailScanner --lint for MailScanner.conf, rulesets, file rules, spam lists and scanner definitions, spamassassin --lint -p for spamassassin.conf — the live file is untouched until the lint passes (conf.d fragments, which the engine always reads from the live directory, are linted right after apply and put back if that fails);
  3. the previous version is copied into the file's History (backup_dir/conf/…, the newest 50 kept);
  4. the file is written atomically, mode and owner preserved;
  5. MailScanner is restarted for files it reads only at start (MailScanner.conf, spamassassin.conf, spam.lists.conf, virus.scanners.conf, conf.d, mcp) or reloaded for the ones it re-reads per batch (rulesets, file rules, phishing lists); the badge next to the file name says which.

The report under the editor shows the lint findings, the transcript and the restart outcome. save without lint skips step 2 (the syntax check still runs); apply without restart/reload skips step 5 — the tab then shows a Restart pending banner naming the files the running engine has not read, and the doctor warns about it. A file that changed on disk while you were editing is refused and reloaded (stale), never overwritten.

History lists every kept version with a line diff against the current file; Restore this version goes through the same pipeline, so the file you replace is kept too. The one-time .msfe-ng.bak older releases wrote appears as original.

Configuration test

Test configuration runs MailScanner --lint, spamassassin --lint and MSFE-NG's own cross-file checks and turns them into one list of findings, each naming the file and setting it is about — click one to open it there:

  • from MailScanner --lint: rulesets it cannot open, values that cannot be rulesets, syntax errors with line numbers, the virus scanners it found versus the ones Virus Scanners names, the phishing list sizes, whether SpamAssassin loaded cleanly, whether the EICAR test batch was caught;
  • from spamassassin --lint with MailScanner's spamassassin.conf: lines it cannot parse, unknown options;
  • from MSFE-NG: every file, directory and command a setting names exists; every ruleset parses and its values fit the setting's type (a ruleset without a default line is a warning, except where no value means off — Archive Mail, where "no match" is the only way to say "archive nothing else", is reported as OK by design); the scanners and spam lists named are defined (defunct blocklists flagged); filename patterns compile; conf.d fragments and phishing lists parse; directives the engine does not know (typos); files the scanning user cannot read; and the doctor checks that judge the same chain.

Test with my changes on the editor runs the same test on a staged copy that includes what you typed but did not save — nothing goes live. The lint runs a real test batch through the scanners, so a test takes 5–20 s.

Test message (simulation)

Under Maintenance & tools: pick a sample (clean, the GTUBE spam string, the EICAR test file as an attachment) or upload a .eml, and Run simulation asks each stage directly — SpamAssassin with MailScanner's spamassassin.conf as the scanning user (offline skips DNS, Razor and Pyzor), the virus scanner through clamdscan, the filename/filetype rules over the attachments (and, for a zip, the Archives: filename rules over each member's stored path, with an encrypted member refused unless Allow Password-Protected Archives is on; members are not unpacked, so the archive filetype rules and nested archives stay with the engine) — then predicts MailScanner's actions for that sender and recipient from your rulesets (spam checks, scores, spam/high/non-spam actions, virus handling). It is a simulation: nothing is delivered. Send real test mail submits the three test messages through the MTA instead; their real verdicts appear on the Messages tab once scanned.

Snapshots: export and import

Export snapshot downloads one tar.gz holding MailScanner's configuration directory (everything except the generated phishing lists) and /etc/msfe-ng (config and policy), with a manifest; a copy stays in backup_dir/snapshots. It contains config.toml with the database password — keep it private.

Import snapshot takes an uploaded file (up to about 2.9 MB through the panel; larger ones: msfe-ng snapshot import on the server) or a snapshot kept on this server, and shows every file with its status (same, changed, new, read-only) and a diff on click. Changed and new files are preselected; config.toml is not, because it is host-specific. The selection is imported as one set — validated on a staged copy, one history copy per file, one restart or reload — and refused as a whole if the lint fails. Policy files regenerate the rulesets. Old msfe-ng backup tarballs import too.

Spam checks

Enable spam checks for all domains sets MailScanner's global Spam Checks = yes (the authoritative switch that overrides any per-domain rule) and restarts MailScanner.

Maintenance cards

MailScanner engine perl

The RPM engine ships with #!/usr/bin/perl shebangs: system perl, where every module MailScanner needs has to be installed (cpanm for what EL does not package) and where a second SpamAssassin ends up beside cPanel's — with its own sa-update and its own rule directory. cPanel's perl (/usr/local/cpanel/3rdparty/bin/perl) already carries Mail::SpamAssassin with the rules cPanel keeps updated, DBI, DBD::mysql, MIME::Parser and the rest, which is how the ConfigServer engine always ran.

engine_perl = "cpanel" (this card, or the key in config.toml, MSFE_NG_ENGINE_PERL=cpanel at install) points the engine's five perl scripts (MailScanner, ms-create-locks, ms-peek, ms-sa-cache, ms-upgrade-conf) at the real path of cPanel's perl (for example /usr/local/cpanel/3rdparty/perl/542/bin/perl, never the symlink, so a cPanel update cannot silently switch the engine to a perl nobody checked) and restarts MailScanner. The card shows what each script runs under now.

What keeps it true: Configure for Exim, the engine upgrade (an RPM update resets the shebangs) and msfe-ng engine install re-assert the choice; the five-minute monitor cron repairs scripts whose perl has disappeared (cPanel moving from perl/542 to a newer one removes the old directory and MailScanner would not start) and sends a Telegram alert when it did; the doctor check MailScanner engine perl fails on a vanished perl, warns on a stale shebang, and --fix rewrites them. cPanel's perl is used only when it loads Mail::SpamAssassin, DBI and MIME::Parser — nothing is ever installed into it; when it lacks something the engine stays on system perl and the doctor says why. The ConfigServer engine is not touched. CLI: msfe-ng engine perl [--apply] [--json].

Database & Bayes maintenance

  • Backup now — timestamped SQL dump into backup_dir (default /opt/msfe-ng/backups).
  • Fix common problems — applies pending migrations, then OPTIMIZE/ANALYZE on the MSFE-NG tables.
  • Bayes repair — expires old tokens and syncs the SpamAssassin Bayes DB.
  • Bayes recreate — takes a database backup, then wipes Bayes so it retrains from scratch (all ham/spam training is lost).

Queue auto-clean

Every 5 minutes the monitor cron removes messages from the delivery queue that match an enabled rule:

Rule Removes
Remove frozen messages older than N hours frozen (undeliverable, unbounceable) mail
Remove bounces older than N hours null-sender (∅) mail
Remove messages with spam score ≥ N mail the cPanel SpamAssassin ACL scored at or above N

0 disables a rule; everything ships off. The scanning queue is never touched, so unscanned mail cannot be deleted. Preview (dry-run) lists what would go right now; Clean now runs the rules immediately; Save rules stores them for the cron. The last automatic run is summarised above the fields.

Auto-ban spam sources (csf)

Every minute (msfe-ng autoban run, from /etc/cron.d/msfe-ng) the sources of newly logged spam are banned temporarily in ConfigServer Firewall (csf -td) when a rule fires. Everything ships off.

Thresholds — one row for high-scoring spam, one for spam: enabled, messages (count 1 = ban on the first message), within a window and ban for a duration, both as a number plus seconds / minutes / hours / days. Defaults: high spam 1 in 10 minutes → 1 day; spam 3 in 1 hour → 2 hours. Save thresholds stores them as autoban_* keys in config.toml.

Match rules — block and/or ban by content. Each rule has a field (Subject, From address, To/Cc, Body text, or any header by name), a match (contains, a literal matched case-insensitively, or regex, a Perl regex exactly as SpamAssassin reads it — /…/i is added when you give no delimiters), the pattern, Block delivery, Ban the source for a duration, and a comment. Rules become SpamAssassin rules named MSFE_MATCH_<id> in /etc/mail/spamassassin/msfe-ng-match.cf (written on every sync): a blocking rule scores 100, so the message follows the domain's high-spam action — quarantined by default, deleted if the policy says so — and is never delivered; a ban-only rule scores 0.01, which tags the message in the log without changing its verdict. The pattern is validated with the engine's own perl when you save (a bad regex is refused with perl's message) and the site config is linted afterwards; Test against recent mail shows which of the last 200 subjects or senders would match. Whitelisted mail skips SpamAssassin, so match rules never touch it. Also reject at SMTP time (not for body rules) additionally refuses matching mail in Exim's DATA ACL with a 550, before MailScanner — the source is then banned from the reject log (reason smtp); the table's SMTP column marks those rules and counts the last day's refusals. How it is wired, what is never refused and what to verify on the host: Rules → Reject at SMTP time.

Never banned, whatever the rules: your own, loopback and private addresses; anything in csf.allow or csf.ignore; an address csf already denies (permanently or temporarily); an address that also delivered clean mail inside the window — a shared provider IP (Gmail, Microsoft, a relay) with one false positive stays reachable; and an address this engine banned within the window. At most 100 bans per run; the rest re-trigger on their next message. Every ban is a row in the autoban table (Recent auto-bans, with unban) and, with Telegram configured and the box ticked, a Telegram line. Dry run now shows what the current settings would ban right now, without banning.

CLI: msfe-ng autoban status, msfe-ng autoban run [--dry-run], msfe-ng autoban rules, msfe-ng autoban rejects, msfe-ng autoban smtp-off. The doctor warns when rules are enabled without csf, when the SpamAssassin file behind the rules is stale (--fix syncs), and when the Exim fragment behind the SMTP-time rejects is stale or not included by the built exim.conf.

Telegram alerts

Create a bot with @BotFather and paste its token (never shown again once saved — the field just says configured; leave it empty to keep it) and the chat id (your own id or a group). Then set thresholds (0 = off):

  • delivery queue reaches N messages
  • scanning queue stuck for N minutes — mail waiting for MailScanner that never gets scanned (the classic "scanner wedged" incident)
  • one account sends N messages/hour — outbound bursts, the usual sign of a compromised mailbox; the alert names the account
  • repeat the same alert at most every N minutes

Send test message confirms the bot and chat id work. The doctor warns if Telegram is only half configured.

The same card holds the AbuseIPDB API key (abuseipdb_key, from the API tab of your AbuseIPDB account), used only when you report an address by hand from the client-IP view. Like the bot token it is never shown again once saved: the label says configured; leave the field empty to keep it.

OSINT settings

The OSINT view is off until OSINT lookups is switched on in the OSINT providers card (it writes osint_enabled); the OSINT view itself offers a Switch OSINT on button and a link to this card. The same card holds the optional Have I Been Pwned API key (osint_hibp_key) and the lookup mode (osint_hibp_mode): direct sends the full address to HIBP, range sends only a 6-character hash prefix and needs the Pro/High RPM plan. The key is a secret: it is never shown again once saved (the label says configured), an empty field keeps it, and Clear key removes it. Only letters, digits, - and _ are accepted. The card also takes a Search key (Brave Search API) (osint_search_key; the full address is sent in quotes to Brave Search, results are candidates only) and a Validation key (Hunter) (osint_validation_key; the full address is sent to Hunter, which checks the mailbox itself and uses the account's paid quota; the result is a vendor assertion, and Hunter is unticked by default on the OSINT view), each with its own Clear button and the same rules. The keys are stored in config.toml, which is readable by root and by the mail group (the MailScanner logging plugin needs it). Like the AbuseIPDB and Telegram secrets they are also visible in the Config tab's raw view, history and snapshot exports. The config.toml file holds the limits: osint_runs_per_min, osint_max_concurrent, osint_deadline_secs, osint_max_external_queries, osint_cache_secs and osint_retention_hours. Scheduled checks add osint_max_monitors (10), osint_history_days (90) and osint_monitor_budget (300 provider units a month, 0 = unlimited); the defaults are listed on the OSINT page, and the alerts use the Telegram settings and alert_cooldown_mins.

Clone this wiki locally