/* * Using the web admin interface: Set your SSH to do portforwarding of some local port to port 80 of the VM and * and use your personal admin account or create one using the fallback admin password. * Create users with mail boxes: Go to the admin interface and create them. * Stalwart mailserver docs can be found at https://stalw.art/docs * DNS-Records: Collect the right DNS entries from the management interface and copy them to the DNS hoster. Caution: * Not all entries are applicable since we relay via HRZ. */ { config, lib, ... }: let inherit (lib) mkIf mkEnableOption mkOption ; inherit (lib.types) listOf strMatching str path; cfg = config.services.mathebau-mail; in { options.services.mathebau-mail = { enable = mkEnableOption "mathebau mail service"; stalwartAdminHash = mkOption { type = str; description = "String containing the hashed fallback admin password"; }; }; config = mkIf cfg.enable { services = { stalwart-mail = { enable = true; openFirewall = true; settings = { server = { lookup.default.hostname = "fb04184.mathematik.tu-darmstadt.de"; # Because the DNS PTR of 130.83.2.184 is this and this should be used in SMTP EHLO. listener = { "smtp" = { bind = ["[::]:25"]; protocol = "smtp"; }; "submissions" = { bind = ["[::]:465"]; protocol = "smtp"; tls.implicit = true; }; "imaptls" = { bind = ["[::]:993"]; protocol = "imap"; tls.implicit = true; }; "management" = { # Cthulhu forwards requests for http://fb04184.mathematik.tu-darmstadt.de/.well-known/acme-challenge/ http://imap.mathebau.de/.well-known/acme-challenge/ and http://smtp.mathebau.de/.well-known/acme-challenge/ # for TLS certificate challenge validation # whereas the rest of the management interface is not available publically. # It can be reached via SSH and portforwarding. bind = ["[::]:80"]; protocol = "http"; }; }; }; acme.letsencrypt = { directory = "https://acme-v02.api.letsencrypt.org/directory"; # This setting is necessary for this block to be activated challenge = "http-01"; contact = ["root@mathebau.de"]; domains = ["fb04184.mathematik.tu-darmstadt.de" "imap.mathebau.de" "smtp.mathebau.de"]; default = true; }; spam.header.is-spam = "Dummyheader"; # disable moving to spam which would conflict with forwarding auth = { # TODO check if HRZ conforms to these standards and we can validate them strictly dkim.verify = "relaxed"; arc.verify = "relaxed"; dmarc.verify = "relaxed"; iprev.verify = "relaxed"; spf.verify.ehlo = "relaxed"; spf.verify.mail-from = "relaxed"; }; # Forward outgoing mail to HRZ or mail VMs. # see https://stalw.art/docs/smtp/outbound/routing/ relay host example queue.outbound = { next-hop = [ { "if" = "rcpt_domain = 'lists.mathebau.de'"; "then" = "'mailman'"; } { "if" = "is_local_domain('', rcpt_domain)"; "then" = "'local'"; } {"else" = "'hrz'";} ]; tls = { # we only talk to HRZ and our own VMs anyway mta-sts = "disable"; dane = "disable"; starttls = "optional"; # e.g. Lobon does not offer starttls }; }; remote."hrz" = { address = "mailout.hrz.tu-darmstadt.de"; port = 25; protocol = "smtp"; tls.implicit = false; # Don't assume TLS on this port but use STARTTLS }; remote."mailman" = { address = "lobon.mathebau.de"; # must be created in DNS as a MX record because this field does not accept ip addresses. port = 25; protocol = "smtp"; tls.implicit = false; # Don't assume TLS on this port but use STARTTLS }; session.rcpt = { # In order to accept mail that we only forward # without having to generate an account. # Invalid addresses are filtered by DFN beforehand. catch-all = true; relay = [ { "if" = "!is_empty(authenticated_as) || rcpt_domain == 'lists.mathebau.de' || starts_with(remote_ip, '192.168.0.')"; #TODO restrict trust by IP "then" = true; } {"else" = false;} ]; }; authentication.fallback-admin = { user = "admin"; # see passwd on azathoth for plaintext or machine secret in encoded format for HTTP Basic AUTH secret = cfg.stalwartAdminHash; }; store = { # structured data in SQLite, blobs on filesystem db.type = "sqlite"; db.path = "/var/lib/stalwart-mail/data/index.sqlite3"; fs.type = "fs"; fs.path = "/var/lib/stalwart-mail/data/blobs"; }; }; }; }; environment.persistence.${config.impermanence.name} = { directories = [ "/var/lib/stalwart-mail" ]; files = ["/root/.ssh/known_hosts"]; # for the backup server bragi }; # Backups services.borgbackup.jobs.mail = { paths = [ "/var/lib/stalwart-mail/data" ]; encryption.mode = "none"; # Otherwise the key is next to the backup or we have human interaction. environment = { BORG_RSH = "ssh -i /run/secrets/backupKey"; # “Borg ensures that backups are not created on random drives that ‘just happen’ to contain a Borg repository.” # https://borgbackup.readthedocs.io/en/stable/deployment/automated-local.html # We don't want this in order to not need to persist borg cache and simplify new deployments. BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK = "yes"; }; repo = "borg@192.168.1.11:kaluut"; # TODO for https://gitea.mathebau.de/Fachschaft/nixConfig/issues/33 startAt = "daily"; user = "root"; group = "root"; }; }; }