services: monitoring: Add 'zabbix-server'.
[jackhill/guix/guix.git] / gnu / services / mail.scm
index b211ab6..fcaedd0 100644 (file)
@@ -1,7 +1,8 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2015 Andy Wingo <wingo@igalia.com>
-;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
+;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
 ;;; Copyright © 2017 Carlo Zancanaro <carlo@zancanaro.id.au>
+;;; Copyright © 2017 Tobias Geerinckx-Rice <me@tobias.gr>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -35,6 +36,7 @@
   #:use-module (guix gexp)
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
+  #:use-module (srfi srfi-1)
   #:export (dovecot-service
             dovecot-service-type
             dovecot-configuration
@@ -57,6 +59,8 @@
             opensmtpd-service-type
             %default-opensmtpd-config-file
 
+            mail-aliases-service-type
+
             exim-configuration
             exim-configuration?
             exim-service-type
 ;;;
 ;;; Code:
 
+(define (uglify-field-name field-name)
+  (let ((str (symbol->string field-name)))
+    (string-join (string-split (if (string-suffix? "?" str)
+                                   (substring str 0 (1- (string-length str)))
+                                   str)
+                               #\-)
+                 "_")))
+
+(define (serialize-field field-name val)
+  (format #t "~a=~a\n" (uglify-field-name field-name) val))
+
+(define (serialize-string field-name val)
+  (serialize-field field-name val))
+
+(define (space-separated-string-list? val)
+  (and (list? val)
+       (and-map (lambda (x)
+                  (and (string? x) (not (string-index x #\space))))
+                val)))
+(define (serialize-space-separated-string-list field-name val)
+  (serialize-field field-name (string-join val " ")))
 
 (define (comma-separated-string-list? val)
   (and (list? val)
 (define (serialize-comma-separated-string-list field-name val)
   (serialize-field field-name (string-join val ",")))
 
+(define (file-name? val)
+  (and (string? val)
+       (string-prefix? "/" val)))
+(define (serialize-file-name field-name val)
+  (serialize-string field-name val))
+
 (define (colon-separated-file-name-list? val)
   (and (list? val)
        ;; Trailing slashes not needed and not
 (define (serialize-colon-separated-file-name-list field-name val)
   (serialize-field field-name (string-join val ":")))
 
+(define (serialize-boolean field-name val)
+  (serialize-string field-name (if val "yes" "no")))
+
 (define (non-negative-integer? val)
   (and (exact-integer? val) (not (negative? val))))
 (define (serialize-non-negative-integer field-name val)
 @samp{pam}, @samp{passwd}, @samp{shadow}, @samp{bsdauth}, and
 @samp{static}.")
   (args
-   (free-form-args '())
-   "A list of key-value args to the passdb driver."))
+   (space-separated-string-list '())
+   "Space separated list of arguments to the passdb driver."))
 
 (define (serialize-passdb-configuration field-name val)
   (format #t "passdb {\n")
    "The driver that the userdb should use.  Valid values include
 @samp{passwd} and @samp{static}.")
   (args
-   (free-form-args '())
-   "A list of key-value args to the userdb driver.")
+   (space-separated-string-list '())
+   "Space separated list of arguments to the userdb driver.")
   (override-fields
    (free-form-args '())
    "Override fields from passwd."))
 
 (define-configuration unix-listener-configuration
   (path
-   (file-name (configuration-missing-field 'unix-listener 'path))
-   "The file name on which to listen.")
+   (string (configuration-missing-field 'unix-listener 'path))
+   "Path to the file, relative to @code{base-dir} field.  This is also used as
+the section name.")
   (mode
    (string "0600")
    "The access mode for the socket.")
 
 (define-configuration fifo-listener-configuration
   (path
-   (file-name (configuration-missing-field 'fifo-listener 'path))
-   "The file name on which to listen.")
+   (string (configuration-missing-field 'fifo-listener 'path))
+   "Path to the file, relative to @code{base-dir} field.  This is also used as
+the section name.")
   (mode
    (string "0600")
    "The access mode for the socket.")
    "Listeners for the service.  A listener is either an
 @code{unix-listener-configuration}, a @code{fifo-listener-configuration}, or
 an @code{inet-listener-configuration}.")
+  (client-limit
+   (non-negative-integer 0)
+   "Maximum number of simultaneous client connections per process.  Once this
+number of connections is received, the next incoming connection will prompt
+Dovecot to spawn another process.  If set to 0, @code{default-client-limit} is
+used instead.")
   (service-count
    (non-negative-integer 1)
    "Number of connections to handle before starting a new process.
 Typically the only useful values are 0 (unlimited) or 1.  1 is more
 secure, but 0 is faster.  <doc/wiki/LoginProcess.txt>.")
+  (process-limit
+   (non-negative-integer 0)
+   "Maximum number of processes that can exist for this service.  If set to 0,
+@code{default-process-limit} is used instead.")
   (process-min-avail
    (non-negative-integer 0)
    "Number of processes to always keep waiting for more connections.")
@@ -439,6 +485,8 @@ complex, customize the address and port fields of the
     (list
      (service-configuration
       (kind "imap-login")
+      (client-limit 0)
+      (process-limit 0)
       (listeners
        (list
         (inet-listener-configuration (protocol "imap") (port 143) (ssl? #f))
@@ -451,24 +499,33 @@ complex, customize the address and port fields of the
         (inet-listener-configuration (protocol "pop3s") (port 995) (ssl? #t)))))
      (service-configuration
       (kind "lmtp")
+      (client-limit 1)
+      (process-limit 0)
       (listeners
        (list (unix-listener-configuration (path "lmtp") (mode "0666")))))
-     (service-configuration (kind "imap"))
-     (service-configuration (kind "pop3"))
-     (service-configuration (kind "auth")
-      ;; In what could be taken to be a bug, the default value of 1 for
-      ;; service-count makes it so that a PAM auth worker can't fork off
-      ;; subprocesses for making blocking queries.  The result is that nobody
-      ;; can log in -- very secure, but not very useful!  If we simply omit
-      ;; the service-count, it will default to the value of
-      ;; auth-worker-max-count, which is 30, instead of defaulting to 1, which
-      ;; is the default for all other services.  As a hack, bump this value to
-      ;; 30.
-      (service-count 30)
+     (service-configuration
+      (kind "imap")
+      (client-limit 1)
+      (process-limit 1024))
+     (service-configuration
+      (kind "pop3")
+      (client-limit 1)
+      (process-limit 1024))
+     (service-configuration
+      (kind "auth")
+      (service-count 0)
+      (client-limit 0)
+      (process-limit 1)
       (listeners
        (list (unix-listener-configuration (path "auth-userdb")))))
-     (service-configuration (kind "auth-worker"))
-     (service-configuration (kind "dict")
+     (service-configuration
+      (kind "auth-worker")
+      (client-limit 1)
+      (process-limit 0))
+     (service-configuration
+      (kind "dict")
+      (client-limit 1)
+      (process-limit 0)
       (listeners (list (unix-listener-configuration (path "dict")))))))
    "List of services to enable.  Available services include @samp{imap},
 @samp{imap-login}, @samp{pop3}, @samp{pop3-login}, @samp{auth}, and
@@ -710,12 +767,6 @@ allowed too, like 10.0.0.10-10.0.0.30.")
    "How long to redirect users to a specific server after it no longer
 has any connections.")
 
-  (director-doveadm-port
-   (non-negative-integer 0)
-   "TCP/IP port that accepts doveadm connections (instead of director
-connections) If you enable this, you'll also need to add
-@samp{inet-listener} for the port.")
-
   (director-username-hash
    (string "%Lu")
    "How the username is translated before being hashed.  Useful values
@@ -796,7 +847,7 @@ string.")
 string, %$ contains the data we want to log.")
 
   (mail-log-prefix
-   (string "\"%s(%u): \"")
+   (string "\"%s(%u)<%{pid}><%{session}>: \"")
    "Log prefix for mail processes.  See doc/wiki/Variables.txt for list
 of possible variables you can use.")
 
@@ -1110,7 +1161,7 @@ files.  If an index file already exists it's still read, just not
 updated.")
 
   (mdbox-rotate-size
-   (non-negative-integer #e2e6)
+   (non-negative-integer #e10e6)
    "Maximum dbox file size until it's rotated.")
 
   (mdbox-rotate-interval
@@ -1227,18 +1278,12 @@ it, set @samp{auth-ssl-require-client-cert? #t} in auth section.")
 x500UniqueIdentifier are the usual choices.  You'll also need to set
 @samp{auth-ssl-username-from-cert? #t}.")
 
-  (ssl-parameters-regenerate
-   (hours 168)
-   "How often to regenerate the SSL parameters file.  Generation is
-quite CPU intensive operation.  The value is in hours, 0 disables
-regeneration entirely.")
-
-  (ssl-protocols
-   (string "!SSLv2")
-   "SSL protocols to use.")
+  (ssl-min-protocol
+   (string "TLSv1")
+   "Minimum SSL protocol version to accept.")
 
   (ssl-cipher-list
-   (string "ALL:!LOW:!SSLv2:!EXP:!aNULL")
+   (string "ALL:!kRSA:!SRP:!kDHd:!DSS:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!RC4:!ADH:!LOW@STRENGTH")
    "SSL ciphers to use.")
 
   (ssl-crypto-device
@@ -1321,14 +1366,15 @@ get \"Too long argument\" or \"IMAP command line too large\" errors
 often.")
 
   (imap-logout-format
-   (string "in=%i out=%o")
+   (string "in=%i out=%o deleted=%{deleted} expunged=%{expunged} trashed=%{trashed} hdr_count=%{fetch_hdr_count} hdr_bytes=%{fetch_hdr_bytes} body_count=%{fetch_body_count} body_bytes=%{fetch_body_bytes}")
    "IMAP logout format string:
 @table @code
 @item %i
 total number of bytes read from client
 @item %o
 total number of bytes sent to client.
-@end table")
+@end table
+See @file{doc/wiki/Variables.txt} for a list of all the variables you can use.")
 
   (imap-capability
    (string "")
@@ -1410,90 +1456,91 @@ greyed out, instead of only later giving \"not selectable\" popup error.
          (home-directory "/var/empty")
          (shell (file-append shadow "/sbin/nologin")))))
 
-(define %dovecot-activation
+(define (%dovecot-activation config)
   ;; Activation gexp.
-  #~(begin
-      (use-modules (guix build utils))
-      (define (mkdir-p/perms directory owner perms)
-        (mkdir-p directory)
-        (chown "/var/run/dovecot" (passwd:uid owner) (passwd:gid owner))
-        (chmod directory perms))
-      (define (build-subject parameters)
-        (string-concatenate
-         (map (lambda (pair)
-                (let ((k (car pair)) (v (cdr pair)))
-                  (define (escape-char str chr)
-                    (string-join (string-split str chr) (string #\\ chr)))
-                  (string-append "/" k "="
-                                 (escape-char (escape-char v #\=) #\/))))
-              (filter (lambda (pair) (cdr pair)) parameters))))
-      (define* (create-self-signed-certificate-if-absent
-                #:key private-key public-key (owner (getpwnam "root"))
-                (common-name (gethostname))
-                (organization-name "GuixSD")
-                (organization-unit-name "Default Self-Signed Certificate")
-                (subject-parameters `(("CN" . ,common-name)
-                                      ("O" . ,organization-name)
-                                      ("OU" . ,organization-unit-name)))
-                (subject (build-subject subject-parameters)))
-        ;; Note that by default, OpenSSL outputs keys in PEM format.  This
-        ;; is what we want.
-        (unless (file-exists? private-key)
-          (cond
-           ((zero? (system* (string-append #$openssl "/bin/openssl")
-                            "genrsa" "-out" private-key "2048"))
-            (chown private-key (passwd:uid owner) (passwd:gid owner))
-            (chmod private-key #o400))
-           (else
-            (format (current-error-port)
-                    "Failed to create private key at ~a.\n" private-key))))
-        (unless (file-exists? public-key)
-          (cond
-           ((zero? (system* (string-append #$openssl "/bin/openssl")
-                            "req" "-new" "-x509" "-key" private-key
-                            "-out" public-key "-days" "3650"
-                            "-batch" "-subj" subject))
-            (chown public-key (passwd:uid owner) (passwd:gid owner))
-            (chmod public-key #o444))
-           (else
-            (format (current-error-port)
-                    "Failed to create public key at ~a.\n" public-key)))))
-      (let ((user (getpwnam "dovecot")))
-        (mkdir-p/perms "/var/run/dovecot" user #o755)
-        (mkdir-p/perms "/var/lib/dovecot" user #o755)
-        (mkdir-p/perms "/etc/dovecot" user #o755)
-        (mkdir-p/perms "/etc/dovecot/private" user #o700)
-        (create-self-signed-certificate-if-absent
-         #:private-key "/etc/dovecot/private/default.pem"
-         #:public-key "/etc/dovecot/default.pem"
-         #:owner (getpwnam "root")
-         #:common-name (format #f "Dovecot service on ~a" (gethostname))))))
+  (let ((config-str
+         (cond
+          ((opaque-dovecot-configuration? config)
+           (opaque-dovecot-configuration-string config))
+          (else
+           (with-output-to-string
+             (lambda ()
+               (serialize-configuration config
+                                        dovecot-configuration-fields)))))))
+    #~(begin
+        (use-modules (guix build utils))
+        (define (mkdir-p/perms directory owner perms)
+          (mkdir-p directory)
+          (chown "/var/run/dovecot" (passwd:uid owner) (passwd:gid owner))
+          (chmod directory perms))
+        (define (build-subject parameters)
+          (string-concatenate
+           (map (lambda (pair)
+                  (let ((k (car pair)) (v (cdr pair)))
+                    (define (escape-char str chr)
+                      (string-join (string-split str chr) (string #\\ chr)))
+                    (string-append "/" k "="
+                                   (escape-char (escape-char v #\=) #\/))))
+                (filter (lambda (pair) (cdr pair)) parameters))))
+        (define* (create-self-signed-certificate-if-absent
+                  #:key private-key public-key (owner (getpwnam "root"))
+                  (common-name (gethostname))
+                  (organization-name "GuixSD")
+                  (organization-unit-name "Default Self-Signed Certificate")
+                  (subject-parameters `(("CN" . ,common-name)
+                                        ("O" . ,organization-name)
+                                        ("OU" . ,organization-unit-name)))
+                  (subject (build-subject subject-parameters)))
+          ;; Note that by default, OpenSSL outputs keys in PEM format.  This
+          ;; is what we want.
+          (unless (file-exists? private-key)
+            (cond
+             ((zero? (system* (string-append #$openssl "/bin/openssl")
+                              "genrsa" "-out" private-key "2048"))
+              (chown private-key (passwd:uid owner) (passwd:gid owner))
+              (chmod private-key #o400))
+             (else
+              (format (current-error-port)
+                      "Failed to create private key at ~a.\n" private-key))))
+          (unless (file-exists? public-key)
+            (cond
+             ((zero? (system* (string-append #$openssl "/bin/openssl")
+                              "req" "-new" "-x509" "-key" private-key
+                              "-out" public-key "-days" "3650"
+                              "-batch" "-subj" subject))
+              (chown public-key (passwd:uid owner) (passwd:gid owner))
+              (chmod public-key #o444))
+             (else
+              (format (current-error-port)
+                      "Failed to create public key at ~a.\n" public-key)))))
+        (let ((user (getpwnam "dovecot")))
+          (mkdir-p/perms "/var/run/dovecot" user #o755)
+          (mkdir-p/perms "/var/lib/dovecot" user #o755)
+          (mkdir-p/perms "/etc/dovecot" user #o755)
+          (copy-file #$(plain-file "dovecot.conf" config-str)
+                     "/etc/dovecot/dovecot.conf")
+          (mkdir-p/perms "/etc/dovecot/private" user #o700)
+          (create-self-signed-certificate-if-absent
+           #:private-key "/etc/dovecot/private/default.pem"
+           #:public-key "/etc/dovecot/default.pem"
+           #:owner (getpwnam "root")
+           #:common-name (format #f "Dovecot service on ~a" (gethostname)))))))
 
 (define (dovecot-shepherd-service config)
   "Return a list of <shepherd-service> for CONFIG."
-  (let* ((config-str
-          (cond
-           ((opaque-dovecot-configuration? config)
-            (opaque-dovecot-configuration-string config))
-           (else
-            (with-output-to-string
-              (lambda ()
-                (serialize-configuration config
-                                         dovecot-configuration-fields))))))
-         (config-file (plain-file "dovecot.conf" config-str))
-         (dovecot (if (opaque-dovecot-configuration? config)
-                      (opaque-dovecot-configuration-dovecot config)
-                      (dovecot-configuration-dovecot config))))
+  (let ((dovecot (if (opaque-dovecot-configuration? config)
+                     (opaque-dovecot-configuration-dovecot config)
+                     (dovecot-configuration-dovecot config))))
     (list (shepherd-service
            (documentation "Run the Dovecot POP3/IMAP mail server.")
            (provision '(dovecot))
            (requirement '(networking))
            (start #~(make-forkexec-constructor
                      (list (string-append #$dovecot "/sbin/dovecot")
-                           "-F" "-c" #$config-file)))
+                           "-F")))
            (stop #~(make-forkexec-constructor
                     (list (string-append #$dovecot "/sbin/dovecot")
-                          "-c" #$config-file "stop")))))))
+                          "stop")))))))
 
 (define %dovecot-pam-services
   (list (unix-pam-service "dovecot")))
@@ -1508,7 +1555,7 @@ greyed out, instead of only later giving \"not selectable\" popup error.
                        (service-extension pam-root-service-type
                                           (const %dovecot-pam-services))
                        (service-extension activation-service-type
-                                          (const %dovecot-activation))))))
+                                          %dovecot-activation)))))
 
 (define* (dovecot-service #:key (config (dovecot-configuration)))
   "Return a service that runs @command{dovecot}, a mail server that can run
@@ -1629,6 +1676,31 @@ accept from local for any relay
                              opensmtpd-shepherd-service)))))
 
 \f
+;;;
+;;; mail aliases.
+;;;
+
+(define (mail-aliases-etc aliases)
+  `(("aliases" ,(plain-file "aliases"
+                            ;; Ideally we'd use a format string like
+                            ;; "~:{~a: ~{~a~^,~}\n~}", but it gives a
+                            ;; warning that I can't figure out how to fix,
+                            ;; so we'll just use string-join below instead.
+                            (format #f "~:{~a: ~a\n~}"
+                                    (map (match-lambda
+                                           ((alias addresses ...)
+                                            (list alias (string-join addresses ","))))
+                                         aliases))))))
+
+(define mail-aliases-service-type
+  (service-type
+   (name 'mail-aliases)
+   (extensions
+    (list (service-extension etc-service-type mail-aliases-etc)))
+   (compose concatenate)
+   (extend append)))
+
+\f
 ;;;
 ;;; Exim.
 ;;;
@@ -1639,9 +1711,7 @@ accept from local for any relay
   (package       exim-configuration-package ;<package>
                  (default exim))
   (config-file   exim-configuration-config-file ;file-like
-                 (default #f))
-  (aliases       exim-configuration-aliases ;; list of lists
-                 (default '())))
+                 (default #f)))
 
 (define %exim-accounts
   (list (user-group
@@ -1668,7 +1738,7 @@ exim_group = exim
 
 (define exim-shepherd-service
   (match-lambda
-    (($ <exim-configuration> package config-file aliases)
+    (($ <exim-configuration> package config-file)
      (list (shepherd-service
             (provision '(exim mta))
             (documentation "Run the exim daemon.")
@@ -1681,7 +1751,7 @@ exim_group = exim
 
 (define exim-activation
   (match-lambda
-    (($ <exim-configuration> package config-file aliases)
+    (($ <exim-configuration> package config-file)
      (with-imported-modules '((guix build utils))
        #~(begin
            (use-modules (guix build utils))
@@ -1694,20 +1764,6 @@ exim_group = exim
            (zero? (system* #$(file-append package "/bin/exim")
                            "-bV" "-C" #$(exim-computed-config-file package config-file))))))))
 
-(define exim-etc
-  (match-lambda
-    (($ <exim-configuration> package config-file aliases)
-     `(("aliases" ,(plain-file "aliases"
-                               ;; Ideally we'd use a format string like
-                               ;; "~:{~a: ~{~a~^,~}\n~}", but it gives a
-                               ;; warning that I can't figure out how to fix,
-                               ;; so we'll just use string-join below instead.
-                               (format #f "~:{~a: ~a\n~}"
-                                       (map (lambda (entry)
-                                              (list (car entry)
-                                                    (string-join (cdr entry) ",")))
-                                            aliases))))))))
-
 (define exim-profile
   (compose list exim-configuration-package))
 
@@ -1719,4 +1775,4 @@ exim_group = exim
           (service-extension account-service-type (const %exim-accounts))
           (service-extension activation-service-type exim-activation)
           (service-extension profile-service-type exim-profile)
-          (service-extension etc-service-type exim-etc)))))
+          (service-extension mail-aliases-service-type (const '()))))))