From 161dc84fce2c2de603d0b867d07e9a412897e51c Mon Sep 17 00:00:00 2001 From: Timo Wilken Date: Sun, 25 Aug 2024 14:24:15 +0200 Subject: Configure nullmailer on servers --- tw/services/mail.scm | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tw/services/mail.scm (limited to 'tw/services/mail.scm') diff --git a/tw/services/mail.scm b/tw/services/mail.scm new file mode 100644 index 00000000..2eb5c435 --- /dev/null +++ b/tw/services/mail.scm @@ -0,0 +1,107 @@ +(define-module (tw services mail) + #:use-module (gnu) + #:use-module ((gnu packages admin) #:select (shadow)) + #:use-module ((gnu packages mail) #:select (nullmailer)) + #:use-module (gnu services) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu system privilege) + #:use-module (guix gexp) + #:use-module (guix modules) ; `source-module-closure' + #:use-module (guix records) + #:use-module (tw services secrets) + #:export (mta-configuration + mta-service-type)) + +(define-configuration/no-serialization mta-configuration + (host-name string "The system's host name, which is needed by nullmailer.") + (user (string "mail") "The UNIX user name to allocate for the MTA.") + (group (string "mail") "The UNIX user group to allocate for the MTA.")) + +(define (mta-accounts config) + (match-record config (user group) + (list (user-account + (name user) (group group) (system? #t) + (comment "Nullmailer daemon user") + (home-directory "/var/spool/nullmailer") + (shell (file-append shadow "/sbin/nologin"))) + (user-group (name group) (system? #t))))) + +(define (mta-secrets config) + (match-record config (user group) + (list (secret (encrypted-file (local-file "files/nullmailer-remotes.enc")) + (destination "/etc/nullmailer/remotes") + (user user) (group group))))) + +(define (mta-setuid-programs config) + ;; Allow any user to send mail. This also prevents annoying failures + ;; when root tries to send mail, since nullmailer-send cannot read the + ;; messages it puts in the queue with 0600 permissions. + (match-record config (user) + (map (lambda (prog) + (privileged-program + (program (file-append nullmailer prog)) + (setuid? #t) (user user))) + '("/sbin/sendmail" "/bin/mailq")))) + +(define (mta-shepherd-services config) + (match-record config (user group) + (list (shepherd-service + (documentation "Run a basic Mail Transfer Agent.") + (provision '(nullmailer)) + (start #~(make-forkexec-constructor + (list #$(file-append nullmailer "/sbin/nullmailer-send")) + #:user #$user #:group #$group)) + (stop #~(make-kill-destructor)))))) + +;; Ideally we'd use `etc-service-type' here, but that would install +;; /etc/nullmailer as a directory symlink pointing into /gnu/store, which +;; blocks installation of /etc/nullmailer/remotes later. +(define (mta-activation config) + (match-record config (host-name user) + (with-imported-modules (source-module-closure '((guix build utils))) + #~(begin + (use-modules ((srfi srfi-26) #:select (cut)) + ((guix build utils) #:select (mkdir-p))) + (define (rm-f path) + (false-if-exception (delete-file path))) + (define (mkdir-if-not-exist path) + (catch 'system-error (lambda () (mkdir path)) + (lambda args + (or (= EEXIST (system-error-errno args)) + (apply throw args))))) + + (rm-f "/etc/nullmailer") + (mkdir-p "/etc/nullmailer") + (for-each (lambda (source target) + (rm-f target) + (symlink source target)) + '(#$(plain-file "nm-me" host-name) + #$(plain-file "nm-adminaddr" "timo@twilken.net") + #$(plain-file "nm-allmailfrom" "cron@twilken.net")) + '("/etc/nullmailer/me" + "/etc/nullmailer/adminaddr" + "/etc/nullmailer/allmailfrom")) + + ;; Create nullmailer's data directories and socket. + ;; No idea why it doesn't do this itself. + (let ((dirs '("/var/spool/nullmailer/queue" + "/var/spool/nullmailer/failed" + "/var/spool/nullmailer/tmp")) + (trigger-path "/var/spool/nullmailer/trigger")) + (for-each mkdir-if-not-exist dirs) + (unless (file-exists? trigger-path) + (system* #$(file-append coreutils "/bin/mkfifo") "-m" "600" trigger-path)) + (let ((user (getpw #$user))) + (for-each (cut chown <> (passwd:uid user) (passwd:gid user)) + (cons trigger-path dirs)))))))) + +(define mta-service-type + (service-type + (name 'mta) + (description "Run the Mail Transfer Agent @code{nullmailer}, to forward system emails.") + (extensions (list (service-extension shepherd-root-service-type mta-shepherd-services) + (service-extension privileged-program-service-type mta-setuid-programs) + (service-extension account-service-type mta-accounts) + (service-extension activation-service-type mta-activation) + (service-extension secrets-service-type mta-secrets))))) -- cgit v1.2.3