Administration

In the following chapter we explain the inner workings of Æther including monitoring and usermanagement.

Managing User accounts

User management is done through a centralized LDAP server which is connected to:
  • Nextcloud

  • Postfix (MTA, through dovecot)

  • Dovecot (MDA)

  • Mailman (Malinglists)

  • Grafana (Monitoring)

Users can be added to groups which grant access to different services:

admin

Users in this group are granted administrative rights to mailman and can login to the monitoring system.

lldap_admin

Users in this group can create other users and groups in lldap.

lldap_password_manager

This group is only meant for services that need to query information from the LDAP server.

lldap_strict_readonly

Same as above.

mail

Users in this group have an standard email address.

mailalias-<name>

Mail for <name>@domain is delivered to every member of this group, see Mail Aliases and Shared Mailboxes.

sharedmail-<name>

Members of this group can open the shared mailbox <name>, see Mail Aliases and Shared Mailboxes.

mailman

Users in this group have access to mailman.

nextcloud

This group grants access to nextcloud.

staff

This group is meant to give access to user management or mailinglist management without being able to change system settings.

Creating New Accounts

New user accounts can be created via the webinterface at https://auth.windsbraut-ev.de. Don’t set a password during creation, instead use the password reset form to generate an email that gets sent to the users address. After creation, groups can be added at the bottom of the user detail page.

Mail Aliases and Shared Mailboxes

Every user in the mail group has a mailbox at username@domain. Additional addresses are groups with a naming convention, managed like any other group at https://auth.windsbraut-ev.de. Only members that are also in the mail group are considered.

Mail alias

Create a group mailalias-<name> and add users to it. Mail for <name>@domain is delivered to the personal mailbox of every member. A group with several members works as a distribution list. Delete the group to remove the alias.

Shared mailbox

Create a group sharedmail-<name> and add users to it. Mail for <name>@domain is stored in a separate mailbox that every member can open by logging in as username/<name> with their own password (see Accessing Your Mailbox). The mailbox stays on disk after the group is deleted and has to be removed manually if desired.

Group names are case insensitive. The <name> part may contain letters, digits, dots, dashes and underscores.

The same groups decide who may send as an address. On the submission port a login may use its own address, any alias it is a member of and any shared mailbox it is a member of (or is logged in to) as the envelope sender, and the From header has to be that same address. Anything else, including addresses outside the domain, is rejected during submission: this is what keeps a single leaked mail password from sending signed mail in somebody else’s name.

A few addresses are expected to exist on every mail domain. The lldap role creates them as mailalias-<name> groups (ae_lldap_mail_operator_aliases) and adds every member of the admin group (ae_lldap_mail_operator_group) to them on each run; administrators who are not also in the mail group receive nothing, as with any alias. Further members can be added by hand and stay:

postmaster, abuse

Required by the mail standards and the addresses other operators and abuse desks write to when something is wrong with mail from this server.

dmarc

Receives the aggregate DMARC reports other providers send about mail claiming to be from this domain, once dmarc_policy in tf/config.auto.tfvars names it. Run the playbook before tofu apply so the address exists when the first report arrives. Reports are XML attachments, one per reporter and day; a message from a large provider that lists a source other than this server is the signal that somebody sends as this domain from elsewhere.

tlsrpt

Receives SMTP TLS reports once tlsrpt_rua names it. They are what turns the MTA-STS policy’s testing mode into information: a sender that could not verify the policy or the certificate says so here, while the mail still gets delivered.

Deleting Accounts

Accounts can be deleted via the user overview page. Once deleted only the account on the lldap server is gone. Mailboxes and files in Nextcloud still exist on disk and have to be deleted manually if desired.

Spam Filtering

Incoming mail passes through rspamd before Postfix accepts it. Rspamd adds up the scores of its rules and acts on the total (ae_rspamd_action_*):

greylist

4

The sending server is told to try again later, most spam bots never do.

add_header

6

The message is delivered with an X-Spam: Yes header; a global sieve script files it into the Junk folder of the recipient.

reject

15

The message is refused during the SMTP dialogue.

The rules fall into three groups. Reputation lookups ask DNS blocklists (Spamhaus, dnswl.org, SURBL, URIBL and others) about the sending server and the domains in the message; these need the resolver described under Blocklists and DNS. Static rules look at the message itself; among them FROM_SUSPICIOUS_TLD adds ae_rspamd_suspicious_tld_score for sender domains ending in one of ae_rspamd_suspicious_tlds, the cheap top-level domains that carry most of the spam that otherwise passes SPF, DKIM and DMARC. Finally the Bayes classifier compares the words of a message with what it has been trained on, contributing up to -3 for ham and +5.1 for spam.

Training Bayes

Bayes learns from three sources:

  • Messages a user moves into Junk are learned as spam, messages moved out of Junk (except into Trash) as ham. This works from any mail client.

  • Mail that users send through the server is learned as ham automatically.

  • Mail that the other rules already tag as spam is learned as spam automatically.

Bayes stays silent until both classes have at least ae_rspamd_bayes_min_learns messages; the counts are shown by:

podman exec ae-rspamd rspamc -h /run/rspamd-sockets/worker-controller stat

To train from mail that is already on disk, point the helper at a maildir folder or at single message files; messages it already knows are skipped:

ae-rspamd-learn spam /ae/userdata/buero/mail/.Junk
ae-rspamd-learn ham /ae/userdata/buero/mail/.Sent

Resetting Bayes

Until September 2026 every message with a negative score was learned as ham. Spam that passes SPF, DKIM and DMARC scores slightly below zero, so Bayes was trained on spam and ended up vouching for it. A database with that history is worth less than an empty one. To start over, stop rspamd, flush its Redis instance (it only holds caches: the Bayes tokens, greylisting and rate-limit state and the neural network) and train it from the mail on disk:

systemctl stop ae-rspamd.service
podman exec ae-redis-rspamd redis-cli FLUSHALL
systemctl start ae-rspamd.service
ae-rspamd-learn spam /ae/userdata/*/mail/.Junk
ae-rspamd-learn ham /ae/userdata/*/mail/.Sent

Check the Junk folders for false positives before learning them, and look at the stat output afterwards. Both classes have to reach ae_rspamd_bayes_min_learns before Bayes says anything at all; the spam side is the one that lags, because everything it knows comes from mail that was filed as Junk. Lowering the variable is safe: BAYES_SPAM is worth 5.1 at most and the add_header threshold is 6, so a Bayes verdict on a thin corpus still cannot move a message into Junk without a second signal agreeing.

Blocklists and DNS

The blocklists count queries per resolver and refuse the shared ones that most servers forward to, because they cannot tell the individual users behind them apart. Every list has its own way of saying so, and rspamd reads all of them as “not listed”:

List

Answer

Meaning

Spamhaus

127.255.255.254

Query through a public or open resolver

dnswl.org

127.0.0.255

Over 100’000 queries a day from that resolver, counted across all of its users

URIBL

127.0.0.1

Query blocked, possibly due to high volume

Two things fix this together. Rspamd resolves through the unbound container, which recurses from the root servers itself instead of forwarding, so the lists see this machine. Since container names would no longer resolve that way, unbound passes the dns.podman zone back to podman’s own DNS. And Spamhaus, which refuses shared resolvers regardless of volume, gets queried under a key, see Spamhaus DQS below.

Losing dnswl.org matters as much as losing a blocklist: it is a whitelist, and without it known-good senders no longer get their negative score, which pushes ordinary mail towards the Junk folder.

To check the resolver, ask it for the test entries from inside the rspamd container. Each has to return an address in 127.0.0.0/8 other than the refusal codes above:

podman exec ae-rspamd getent hosts 2.0.0.127.zen.spamhaus.org
podman exec ae-rspamd getent hosts 2.0.0.127.list.dnswl.org
podman exec ae-rspamd getent hosts test.uribl.com.multi.uribl.com

Do not judge this by /etc/resolv.conf inside the container: it still lists podman’s own DNS addresses, and that is correct. Podman keeps itself as the container’s nameserver so that container names go on resolving, and hands our resolver to it as the upstream for everything else. The per-container upstream is visible as the last field of the container’s line in /run/containers/networks/aardvark-dns/podman. The lookups above are what tells you whether it works.

To check the whole path rather than just DNS, scan a message carrying Spamhaus’ test domain. This only scans, it sends nothing:

printf 'From: t@example.com\nTo: postmaster@example.com\nSubject: t\n\nhttp://dbltest.com/\n' > /tmp/t.eml
podman cp /tmp/t.eml ae-rspamd:/tmp/t.eml
podman exec ae-rspamd rspamc -h /run/rspamd-sockets/worker-controller symbols /tmp/t.eml

DBL_SPAM has to appear in the symbol list. Note that URIBL’s own test domain does not work this way, because rspamd reduces a URL to its registrable domain before querying, and only the longer test label is listed.

If the resolver is down, rspamd keeps running without any blocklist and Postfix still accepts mail, because milter_default_action is accept.

Spamhaus DQS

The Spamhaus lists (ZEN for sending addresses, DBL for domains) are the most valuable of the blocklists and refuse shared resolvers outright. The Data Query Service (DQS) identifies the deployment by a key in the queried name and is free for non-commercial use at this volume. It also adds the Zero Reputation Domains list, which flags domains registered or reactivated within the last day.

Register at https://www.spamhaus.com/dqs/, copy the key from the portal (section “Datafeed Query Service”) into host_vars/<host>.yml and deploy the rspamd role:

ae_rspamd_spamhaus_dqs_key: "abcdefghijklmnopqrstuvwxyz"

ansible-playbook -i hosts.ini setup.yml --tags mail

The rules are the ones Spamhaus publishes for rspamd (local.d/rbl.conf and local.d/rbl_group.conf), without the Hash Blocklist of the paid tier. Confirm the key works, the answer has to list the three test codes and not 127.255.255.254:

podman exec ae-rspamd getent hosts 2.0.0.127.<key>.zen.dq.spamhaus.net

Over time the symbol counters (rspamc ... counters) show hits for SPAMHAUS_ZEN, DBL_* and ZRD_*.

Monitoring

In order to make sure all services are operating smoothly, Æther collects non user specific metrics about the system. E.g. CPU and memory usage, list and status of all processes, number of sent emails, number of requests to the webserver. These metrics can be accessed at: https://monitoring.windsbraut-ev.de. Via alerts it is possible to receive emails if certain parameters are outside of specified limits.