Subject: [bug#56046] [PATCH opensmtpd-records v3] services (opensmtpd): add opensmtpd records to enhance opensmtpd-configuration.

From: Liliana Marie Prikler <[email-masked]>
Date: Mon, 26 Dec 2022 20:34:43 +0100

Am Freitag, dem 23.12.2022 um 08:52 -0500 schrieb Joshua Branson:
> 
> * gnu/services/mail.scm:
> (opensmtpd-table,
> opensmtpd-ca,
> opensmtpd-pki,
> opensmtpd-action-local-delivery,
> opensmtpd-maildir,
> opensmtpd-mda,
> opensmtpd-lmtp,
> opensmtpd-relay,
> opensmtpd-option,
> opensmtpd-filter-phase,
> opensmtpd-filter,
> opensmtpd-interface,
> opensmtpd-socket,
> opensmtpd-match,
> opensmtpd-smtp,
> opensmtpd-srs,
> opensmtpd-queue, and
> opensmtpd-configuration): New records.
Don't forget to put closing parentheses at the end of lines.  Also,
feel free to group them to save vertical space.

> (false?, is-value-right-type, add-comma-or-string,
> list-of-procedures->string, string-in-list?, my-sanitize,
> opensmtpd-filter-chain?, throw-error-duplicate-option,
> sanitize-list-of-options-for-match, sanitize-filters,
> list-has-duplicates-or-non-filters?,
> filter-phase-has-message-and-value?,
> filter-phase-decision-lacks-proper-message?,
> filter-phase-lacks-proper-value?,
> filter-phase-has-incorrect-junk-or-bypass?,
> filter-phase-junks-after-commit?,
> list-of-unique-filter-or-filter-phase?, throw-error,
> contains-duplicate?, list-of-type?, list-of-strings?,
> list-of-unique-opensmtpd-option?,
> list-of-opensmtpd-ca?,
> list-of-opensmtpd-pki?,
> list-of-opensmtpd-listen-on?,
> list-of-unique-opensmtpd-match?, list-of-strings->string,
> assoc-list? assoc-list, variable->string,
> tables-data-are-assoc-list?,
> tables-data-are-a-list-of-strings?,
> table-data-are-a-nested-list-of-strings?,
> assoc-list->string,
> opensmtpd-table->string,
> opensmtpd-listen-on->string,
> opensmtpd-listen-on-socket->string,
> opensmtpd-action-relay->string,
> opensmtpd-lmtp->string,
> opensmtpd-mda->string,
> opensmtpd-maildir->string,
> opensmtpd-action-local-delivery->string,
> opensmtpd-action->string, opensmtpd-option->string,
> opensmtpd-match->string,
> opensmtpd-ca->string, opensmtpd-pki->string,
> generate-filter-chain-name, opensmtpd-filter-chain->string,
> opensmtpd-filter-phase->string, opensmtpd-filters->string,
> opensmtpd-listen->string,
> opensmtpd-srs->string,
> opensmtpd-smtp->string,
> opensmtpd-queue->string, get-opensmtpd-actions,
> get-opensmtpd-pkis, get-opensmtpd-filters, flatten,
> get-opensmtpd-tables, opensmtpd-fieldname->string,
> list-of-records->string, opensmtpd->mixed-text-file): New
> procedures.
> 
> * gnu/tests/mail.scm : new tests for various opensmtpd records.
> 
> * doc/guix.texi (OpenSMTPD Service): Added documentation for the
> new records for opensmtpd.
> ---
>  doc/guix.texi         | 1065 ++++++++++++++++-
>  gnu/services/mail.scm | 2560
> ++++++++++++++++++++++++++++++++++++++++-
>  gnu/tests/mail.scm    |  713 ++++++++++++
>  3 files changed, 4310 insertions(+), 28 deletions(-)
> 
> diff --git a/doc/guix.texi b/doc/guix.texi
> index 535c8cdfc3..879a2ad233 100644
> --- a/doc/guix.texi
> +++ b/doc/guix.texi
> @@ -25407,16 +25407,66 @@ could instantiate a dovecot service like
> this:
>  @end lisp
>  
>  @subsubheading OpenSMTPD Service
> +@cindex opensmtpd
>  
>  @deffn {Scheme Variable} opensmtpd-service-type
> -This is the type of the @uref{https://www.opensmtpd.org, OpenSMTPD}
> -service, whose value should be an @code{opensmtpd-configuration}
> object
> -as in this example:
> +OpenSMTPD is an easy-to-use mail transfer agent (MTA).  OpenSMTPD
> +@strong{listens} for incoming mail and @strong{matches} the mail to
> +@strong{actions}. The following records represent those stages:
>  
> -@lisp
> -(service opensmtpd-service-type
> -         (opensmtpd-configuration
> -           (config-file (local-file "./my-smtpd.conf"))))
> +@multitable {aaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
I hope this will receive proper documentation.
> +@item @strong{listens}
> +@tab @code{<opensmtpd-interface>}
> +@item
> +@tab @code{<opensmtpd-socket>}
> +@item
> +@tab
> +@item @strong{matches}
> +@tab @code{<opensmtpd-match>}
> +@item
> +@tab
> +@item @strong{actions}
> +@tab @code{<opensmtpd-local-delivery>}
> +@item
> +@tab @code{<opensmtpd-relay>}
> +@end multitable
> +
> +Additionally, each @code{<opensmtpd-interface>} and
> +@code{<opensmtpd-socket>} may use a list of @code{<opensmtpd-
> filter>},
> +and/or @code{<opensmtpd-filter-phase>} records to filter
> +email/spam. Also numerous records' fieldnames use
> +@code{<opensmtpd-table>} records to hold lists or key value pairs of
> +data.  Be sure to read the @code{<opensmtpd-table>} section to learn
> the
> +differance between a @code{mapping table} and a @code{list table}.
> +
> +Finally, both @code{<opensmtpd-match>} and
> +@code{<opensmtpd-filter-phase>} use @code{<opensmtpd-option>} to
> +configure various options.
> +
> +A simple example opensmtpd configuration is below:
> +
> +@lisp
> +(let ((smtp.gnu.org (opensmtpd-pki
> +                        (domain "smtp.gnu.org")
> +                        (cert "file.cert")
> +                        (key "file.key"))))
> +  (service opensmtpd-service-type
> +           (opensmtpd-configuration
> +            (interfaces (list
> +                         (opensmtpd-interface
> +                          (pki smtp.gnu.org))
> +                         (opensmtpd-interface
> +                          (pki smtp.gnu.org)
> +                          (secure-connection "smtps"))))
> +            (matches (list
> +                      (opensmtpd-match
> +                       (action
> +                        (opensmtpd-local-delivery
> +                         (name "local-delivery"))))
> +                      (opensmtpd-match
> +                       (action
> +                        (opensmtpd-relay
> +                         (name "relay")))))))))
>  @end lisp
>  @end deffn
>  
> @@ -25425,7 +25475,7 @@ Data type representing the configuration of
> opensmtpd.
>  
>  @table @asis
>  @item @code{package} (default: @var{opensmtpd})
> -Package object of the OpenSMTPD SMTP server.
> +Package object of the OpenSMTPD server.
>  
>  @item @code{config-file} (default: @code{%default-opensmtpd-config-
> file})
>  File-like object of the OpenSMTPD configuration file to use.  By
> default
> @@ -25433,14 +25483,1013 @@ it listens on the loopback network
> interface, and allows for mail from
>  users and daemons on the local machine, as well as permitting email
> to
>  remote servers.  Run @command{man smtpd.conf} for more information.
>  
> +@item @code{bounce} (default: @code{(list "4h")})
> +@code{bounce} is a list of strings, which send warning messages to
> the
> +envelope sender when temporary delivery failures cause a message to
> +remain in the queue for longer than a specified delay. Each delay
> option
> +is a string parameter beginning with a positive decimal integer and
> a
> +unit, which can be 's', 'm', 'h', or 'd'. At most four delay
> parameters
> +can be specified.
> +
> +@item @code{interfaces} default:
> +@lisp
> +(list
> +  (opensmtpd-interface
> +    (interface "lo")
> +    (port 25)))
> +@end lisp
> +@code{interfaces} is a list of @code{<opensmtpd-interface>} records.
> +This list details what interfaces and ports OpenSMTPD listens on as
> well as
> +other options.
> +
> +@item @code{socket} (default: @code{(opensmtpd-socket)})
> +Listens for incoming connections on the Unix domain socket.
> +
> +@item @code{includes} (default: @code{#f})
> +@code{includes} is a list of string filenames. Each filename's
> contents is
> +additional configuration that is inserted into the top of the
> configuration
> +file.  Run @code{man smtpd.conf} for more information.
> +
> +@item @code{matches} default:
> +@lisp
> +(list (opensmtpd-match
> +       (action (opensmtpd-local-delivery
> +                (name "local")
> +                (method "mbox")
> +                (options
> +                 (list
> +                  (opensmtpd-option
> +                   (option "for local")))))))
> +      (opensmtpd-match
> +       (action (opensmtpd-relay
> +                (name "outbound")))
> +       (options
> +        (list
> +         (opensmtpd-option
> +          (option "from local"))
> +         (opensmtpd-option
> +          (option "for any"))))))
> +@end lisp
> +@code{matches} is a list of @code{<opensmtpd-match>} records, which
> +matches incoming mail and sends it to a correspending action. The
> match
> +records are evaluated sequentially, with the first match winning.
> +Therefore @emph{the order that you arrange your matches is
> important}.
> +It's a good idea to put specific matches first and an all
> emcompassing
> +match (like @code{(option "for any")}) @strong{last}. If an incoming
> +mail does not match any match records, then it is rejected.
> +
> +@item @code{mta-max-deferred} (default: @code{100})
> +When delivery to a given host is suspended due to temporary
> failures, cache
> +at most number envelopes for that host such that they can be
> delivered as
> +soon as another delivery succeeds to that host. The default is 100.
> +
> +@item @code{queue} (default: @code{#f})
> +@code{queue} expects an @code{<opensmtpd-queue>} record. With it,
> one may
> +compress and encrypt queue-ed emails as well as set the default
> expiration
> +time for temporarily undeliverable messages.
> +
> +@item @code{smtp} (default: @code{#f})
> +@code{smtp} expects an @code{<opensmtpd-smtp>} record, which lets
> one
> +specifiy how large email may be along with other settings.
> +
> +@item @code{srs} (default: @code{#f})
> +@code{srs} expects an @code{<opensmtpd-srs>} record, which lets one
> set
> +up SRS, the Sender Rewritting Scheme.
> +
>  @item @code{setgid-commands?} (default: @code{#t})
>  Make the following commands setgid to @code{smtpq} so they can be
>  executed: @command{smtpctl}, @command{sendmail}, @command{send-
> mail},
>  @command{makemap}, @command{mailq}, and @command{newaliases}.
>  @xref{Setuid Programs}, for more information on setgid programs.
> +
>  @end table
>  @end deftp
>  
> +@itemize
> +@item Data Type: opensmtpd-interface
> +Data type representing the configuration of an
> +@code{<opensmtpd-interface>}. It listens on the fieldname
> +@code{interface} for incoming connections, using the same syntax as
> +@code{ifconfig}. The interface parameter may also be an string
> interface
> +group, an string IP address, or a string domain name. Listening can
> +optionally be restricted to a specific address via the fieldname
> +@code{family}, which can be either @code{"inet4"} or @code{"inet6"}.
> +
> +@itemize
> +@item @code{interface} (default: @code{"lo"})
> +
> +The string interface to listen for incoming connections.  This
> string
> +may be an interface group, an IP address, or a domain name. These
> +interfaces can usually be found by the command @code{ip link}.
> +
> +@item @code{family} (default: @code{#f})
> +
> +Only listen on a specific address family.  Valid strings are
> +@code{"inet4"} or @code{"inet6"}, which will only listen on IPv4 or
> IPv6
> +respectfully.  If @code{(family #f)}, then opensmtpd will listen on
> both
> +IPv4 and IPv6.
> +
> +@item @code{auth} (default: @code{#f})
> +Support SMTPAUTH: clients may only start SMTP transactions after
> +successful authentication. If @code{auth} is @code{#t}, then users
> are
> +authenticated against their own normal login credentials.
> Alternatively
> +@code{auth} may be a @code{mapping table} that maps usernames to
> +encrypted passwords.  The password can be encrypted via the
> +@code{smtpctl} @code{encrypt} subcommand.
> +
> +@item @code{auth-optional} (default: @code{#f})
> +Support SMTPAUTH optionally: clients need not authenticate, but may
> do
> +so.  This allows the @code{<opensmtpd-interface>} to both accept
> +incoming mail from untrusted senders and permit outgoing mail from
> +authenticated users. It can be used in situations where it is not
> +possible to listen on a separate port (usually the submission port,
> 587)
> +for users to authenticate.  This option also accepts a @code{mapping
> +table} that maps usernames to encrypted passwords.
> +
> +@item @code{filters} (default: @code{#f})
> +A list of one or many @code{<opensmtpd-filter>} or
> +@code{<opensmtpd-filter-phase>} records. The filters are applied
> +sequentially. These records listen and filter on connections handled
> by this
> +listener.
> +
> +@item @code{hostname} (default: @code{#f})
> +Change the default server name in the greeting banner instead of the
> +default one.
> +
> +@item @code{hostnames} (default: @code{#f})
> +Override the server name for specific addresses. Use a @code{mapping
> +table} that maps string IP addresses to string hostnames. If the
> address
> +on which the connection arrives appears in the mapping, the
> associated
> +hostname is used.
> +
> +@item @code{mask-src} (default: @code{#f})
> +If @code{#t}, then omit the from part when prepending “Received”
> headers.
> +
> +@item @code{disable-dsn} (default: @code{#f})
> +When @code{#t}, then disable the DSN (Delivery Status Notification)
> extension.
> +
> +@item @code{pki} (default: @code{#f})
> +For secure connections, use an @code{<opensmtpd-pki>} record to
> prove a
> +mail server's identity.
> +
> +@item @code{port} (default: @code{25})
> +Listen on the integer port instead of the default port of 25.
> +
> +@item @code{proxy-v2} (default: @code{#f})
> +If @code{#t}, then support the PROXYv2 protocol, rewriting
> appropriately source
> +address received from proxy.
> +
> +@item @code{received-auth} (default: @code{#f})
> +If @code{#t}, then in “Received” headers, report whether the session
> was
> +authenticated and by which local user.
> +
> +@item @code{senders} (default: @code{#f})
> +Look up the authenticated user in the supplied @code{mapping table}
> to
> +find the email addresses that user is allowed to submit mail as.
> +
> +@item @code{masquerade} (default: @code{#f})
> +@code{masquerade}, is used in conjunction with @code{senders}.  If
> +@code{#t}, then the From header is rewritten to match the sender
> +provided in the SMTP session.  If @code{senders} is @code{#false},
> then
> +@code{masquerade} cannot be @code{#t}.
> +
> +@item @code{secure-connection} (default: @code{#f})
> +This is a string of one of these options:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
Same here
> +@item @code{"smtps"}
> +@tab Support SMTPS, by default on port 465.
> +@item @code{"tls"}
> +@tab Support STARTTLS, by default on port 25.
> +@item @code{"tls-require"}
> +@tab Like @code{"tls"}, but force clients to
> +@item
> +@tab establish a secure connection before being
> +@item
> +@tab allowed to start an SMTP transaction.
> +@item @code{"tls-require-verify"}
> +@tab Like @code{"tls-require"}, but clients must
> +@item
> +@tab also provide a valid certificate
> +@item
> +@tab to establish an SMTP session.
> +@end multitable
> +
> +@item @code{tag} (default: @code{#f})
> +Clients connecting to the listener are tagged with the given string
> tag.
> +@end itemize
> +
> +@item Data Type: opensmtpd-socket
> +Data type representing the configuration of an
> +@code{<opensmtpd-socket>}. Listen for incoming SMTP connections on
> the
> +Unix domain socket @samp{/var/run/smtpd.sock}. This is done by
> default,
> +even if the record is absent.
> +
> +@itemize
> +@item @code{filters} (default: @code{#f})
> +A list of one or many @code{<opensmtpd-filter>} or
> +@code{<opensmtpd-filter-phase>} records. These filter incoming
> +connections handled by this listener.
> +
> +@item @code{mask-src} (default: @code{#f})
> +If @code{#t}, then omit the from part when prepending “Received”
> headers.
> +
> +@item @code{tag} (default: @code{#f})
> +Clients connecting to the listener are tagged with the given string
> tag.
> +@end itemize
> +
> +@item Data Type: opensmtpd-match
> +@cindex opensmtpd-match
> +This data type represents the configuration of an
> +@code{<opensmtpd-match>} record.
> +
> +If at least one mail envelope matches the options of one match
> record,
> +receive the incoming message, put a copy into each matching
> envelope,
> +and atomically save the envelopes to the mail spool for later
> processing
> +by the respective @code{<opensmtpd-action>} found in fieldname
> +@code{action}.  Here is an example @code{opensmtpd-match}
> +record.
> +
> +@lisp
> +(opensmtpd-match
> + (action (opensmtpd-local-delivery
> +          (name "receive")
> +          (method (opensmtpd-maildir
> +                   (pathname "/home/%@{rcpt.user@}/Maildir")
> +                   (junk #t)))
> +          (virtual (opensmtpd-table
> +                    (name "virt")
> +                    (data '(("carmen" . "carmen@@gnu.org")))))))
> + (options (list (opensmtpd-option
> +                 (option "from any"))
> +                (opensmtpd-option
> +                 (option "for domain")
> +                 (data (opensmtpd-table
> +                        (name "domain-table")
> +                        (data (list "gnu.org" "fsf.org"))))))))
> +@end lisp
> +
> +@itemize
> +@item @code{action} (default: @code{#f})
> +
> +If mail matches this match configuration, then do this action. Valid
> values
> +include @code{<opensmtpd-local-delivery>} or
> +@code{<opensmtpd-relay>}.
> +
> +@item @code{options} (default: @code{#f})
> +The fieldname @code{option} is a list of unique
> +@code{<opensmtpd-option>} records.
> +
> +There are some mutually exclusive options: there can be only one
> ``for''
> +and only one ``from'' option.
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
and here.
> +@headitem for
> +@tab from
> +@item only use one of the following:
> +@tab only use one of the following:
> +@item @code{"for any"}
> +@tab @code{"from any"}
> +@item @code{"for local"}
> +@tab @code{"from auth"}
> +@item @code{"for domain"}
> +@tab @code{"from local"}
> +@item @code{"for rcpt-to"}
> +@tab @code{"from mail-from"}
> +@item
> +@tab @code{"from socket"}
> +@item
> +@tab @code{"from src"}
> +@end multitable
> +
> +Additionally, some options require additional data via
> +@code{<opensmtpd-option>}'s fieldname @code{data}.  The following
> list
> +will explain the below syntax.
> +
> +@itemize
> +@item @code{"for any"}
> +This option only requires fieldname @code{option} to have the string
> +@code{"for any"}:
> +
> +@lisp
> +      (opensmtpd-option
> +       (option "for any"))
> +@end lisp
> +
> +@item @code{"tag"} _tag_
> +This option only requires fieldname @code{option} to have the string
> +@code{"tag"} with a string in fieldname @code{data}:
> +
> +@lisp
> +      (opensmtpd-option
> +       (option "tag")
> +       (data "this-tag"))
> +@end lisp
> +
> +@item @code{"for rcpt"} _domain_ | <list table>
> +This option requires fieldname @code{data} to have a string domain
> or
> +@code{list table}:
> +
> +@lisp
> +      (opensmtpd-option
> +       (option "for rcpt")
> +       (data "gnu.org"))
> +@end lisp
> +
> +OR
> +
> +@lisp
> +      (opensmtpd-option
> +       (option "for rcpt")
> +       (data (list "gnu.org" "fsf.org")))
> +@end lisp
> +@end itemize
> +
> +The following matching options are supported and can all be negated
> (via not
> +#t). The options that support a table (anything surrounded with '<'
> and '>'
> +eg: <table>), also support specifying regex via (regex #t).
> +
> +@itemize
> +@item @code{"for any"}
> +Specify that session may address any destination.
> +
> +@item @code{"for local"}
> +Specify that session may address any local domain.  This is the
> default,
> +and may be omitted.
> +
> +@item @code{"for domain"} _domain_ | <domain>
> +Specify that session may address the string _domain_ or
> +@code{list table} <domain>.
> +
> +@item @code{"for rcpt-to"} _recipient_ | <recipient>
> +Specify that session may address the string _recipient_ or list
> table
> +<recipient>.
> +
> +@item @code{"from any"}
> +Specify that session may originate from any source.
> +
> +@item @code{"from auth"}
> +Specify that session may originate from any authenticated user, no
> matter
> +the source IP address.
> +
> +@item @code{"from auth"} _user_ | <user>
> +Specify that the session may originate from authenticated _user_ or
> +@code{list table} <user>, no matter the source IP address.
> +
> +@item @code{"from local"}
> +Specify that session may only originate from a local IP address, or
> from
> +the local enqueuer.  This is the default, and may be omitted.
> +
> +@item @code{"from mail-from"} _sender_ | <sender>
> +Specify that session may originate from _sender_ or @code{list
> table}
> +<sender>, no matter the source IP address.
> +
> +@item @code{"from rdns"}
> +Specify that session may only originate from an IP address that
> resolves
> +to a reverse DNS@.
> +
> +@item @samp{"from rdns"} _hostname_ | <hostname>
> +Specify that session may only originate from an IP address that
> resolves
> +to a reverse DNS matching string _hostname_ or @code{list table}
> +<hostname>.
> +
> +@item @samp{"from socket"}
> +Specify that session may only originate from the local enqueuer.
> +
> +@item @code{"from src"} _address_ | <address>
> +Specify that session may only originate from string _address_ or
> +@code{list table} <address> which can be a specific address or a
> subnet
> +expressed in CIDR-notation.
> +
> +@item @code{"auth"}
> +Matches transactions which have been authenticated.
> +
> +@item @code{"auth"} _username_ | <username>
> +Matches transactions which have been authenticated for string _user_
> or
> +@code{list table} <username>.
> +
> +@item @code{"helo"} _helo-name_ | <helo-name>
> +Specify that session's HELO / EHLO should match the string _helo-
> name_
> +or @code{list table} <helo-name>.
> +
> +@item @code{"mail-from"} _sender_ | <sender>
> +Specify that transactions's MAIL FROM should match the string
> _sender_
> +or @code{list table} <sender>.
> +
> +@item @code{"rcpt-to"} _recipient_ | <recipient>
> +Specify that transaction's RCPT TO should match the string
> _recipient_
> +or @code{list table} <recipient>.
> +
> +@item @code{"tag"} _tag_
> +Matches transactions tagged with the given tag.
> +
> +@item @code{"tls"}
> +Specify that transaction should take place in a TLS channel.
> +@end itemize
> +
> +@end itemize
> +
> +@item Data Type: opensmtpd-local-delivery
> +This data type represents the configuration of an
> +@code{<opensmtpd-local-delivery>} record.
> +
> +@itemize
> +@item @code{name} (default: @code{#f})
> +@code{name} is the string name of the relay action.
> +
> +@item @code{method} (default: @code{"mbox"})
> +The email delivery option.  Valid options are:
> +
> +@itemize
> +@item @code{"mbox"}
> +Deliver the message to the user's mbox with mail.local(8).
> +
> +@item @code{"expand-only"}
> +Only accept the message if a delivery method was specified in an
> aliases
> +or .forward file.
> +
> +@item @code{"forward-only"}
> +Only accept the message if the recipient results in a remote address
> after
> +the processing of aliases or forward file.
> +
> +@item @code{<opensmtpd-lmtp>}
> +Deliver the message to an LMTP server at @code{<opensmtpd-lmtp>}'s
> +fieldname @code{destination}. The location may be expressed as
> string
> +host:port or as a UNIX socket. Optionally, @code{<opensmtpd-lmtp>}'s
> +fieldname @code{rcpt-to} might be specified to use the recipient
> email
> +address (after expansion) instead of the local user in the LMTP
> session
> +as RCPT TO.
> +
> +@item @code{<opensmtpd-maildir>}
> +Deliver the message to the maildir in
> +@code{<opensmtpd-maildir>}'s fieldname @code{pathname} if specified,
> +or by default to @code{"~/Maildir"}.
> +
> +The pathname may contain format specifiers that are expanded before
> use
> +(see the below section about Format Specifiers).
> +
> +If @code{<opensmtpd-maildir>}'s record fieldname @code{junk} is
> @code{#t},
> +then message will be moved to the ‘Junk’ folder if it contains a
> positive
> +‘X-Spam’ header. This folder will be created under fieldname
> @code{pathname} if
> +it does not yet exist.
> +
> +@item @code{<opensmtpd-mda>}
> +Delegate the delivery to the @code{<opensmtpd-mda>}'s fieldname
> +@code{command} (type string) that receives the message on its
> standard input.
> +
> +The @code{command} may contain format specifiers that are expanded
> before use
> +(see Format Specifiers).
> +@end itemize
> +
> +@item @code{alias} (default: @code{#f})
> +Use the @code{mapping table} for aliases expansion.
> +
> +@item @code{ttl} (default: @code{#f})
> +@code{ttl} is a string specify how long a message may remain in the
> queue.  It's
> +format is @code{n@{s|m|h|d@}}.  eg: @code{"4m"} is four minutes.
> +
> +@item @code{user} (default: @code{#f} )
> +@code{user} is the string username for performing the delivery, to
> be looked up
> +with getpwnam(3).
> +
> +This is used for virtual hosting where a single username is in
> charge of
> +handling delivery for all virtual users.
> +
> +This option is not usable with the mbox delivery method.
> +
> +@item @code{userbase} (default: @code{#f})
> +@code{userbase} is an @code{<opensmtpd-table>} record for mapping
> user
> +lookups instead of the getpwnam(3) function.
> +
> +The fieldnames @code{user} and @code{userbase} are mutually
> exclusive.
> +
> +@item @code{virtual} (default: @code{#f})
> +@code{virtual} is an @code{<opensmtpd-table>} record is used for
> virtual
> +expansion.
> +@end itemize
> +
> +@item Data Type: opensmtpd-relay
> +This data type represents the configuration of an
> +@code{<opensmtpd-relay>} record.
> +
> +@itemize
> +@item @code{name} (default: @code{#f})
> +@code{name} is the string name of the relay action.
> +
> +@item @code{backup} (default: @code{#f})
> +When @code{#t}, operate as a backup mail exchanger delivering
> messages to any
> +mail exchanger with higher priority.
> +
> +@item @code{backup-mx} (default: @code{#f})
> +Operate as a backup mail exchanger delivering messages to any mail
> exchanger
> +with higher priority than mail exchanger identified as string name.
> +
> +@item @code{helo} (default: @code{#f})
> +Advertise string heloname as the hostname to other mail exchangers
> during
> +the HELO phase.
> +
> +@item @code{helo-src} (default: @code{#f} )
> + Use the mapping @code{<opensmtpd-table>} to look up a hostname
> +matching the source address, to advertise during the HELO phase.
> +
> +@item @code{domain} (default: @code{#f})
> +Do not perform MX lookups but look up destination domain in an
> +@code{<opensmtpd-table>} and use matching relay url as relay host.
> +
> +@item @code{host} (default: @code{#f})
> +Do not perform MX lookups but relay messages to the relay host
> described by
> +the string relay-url. The format for relay-url is
> +@samp{[proto://[label@@]]host[:port]}. The following protocols are
> available:
> +
> +@multitable {aaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
> +@item @code{smtp}
> +@tab Normal SMTP session with opportunistic STARTTLS (the default).
> +@item @code{smtp+tls}
> +@tab Normal SMTP session with mandatory STARTTLS@.
> +@item @code{smtp+notls}
> +@tab Plain text SMTP session without TLS@.
> +@item @code{lmtp}
> +@tab LMTP session.  port is required.
> +@item @code{smtps}
> +@tab SMTP session with forced TLS on connection, default port is
> +@item
> +@tab 465.
> +@end multitable
> +
> +Unless noted, port defaults to 25.
> +
> +The label corresponds to an entry in a credentials table, as
> documented in
> +@code{man table}. It is used with the @code{"smtp+tls"} and
> @code{"smtps"} protocols for
> +authentication. Server certificates for those protocols are verified
> by
> +default.
> +
> +@item @code{pki} (default: @code{#f})
> +For secure connections, use the certificate associated with
> +@code{<opensmtpd-pki>} (declared in a pki directive) to prove the
> +client's identity to the remote mail server.
> +
> +@item @code{srs} (default: @code{#f})
> +If @code{#t}, then when relaying a mail resulting from a forward,
> use the Sender
> +Rewriting Scheme to rewrite sender address.
> +
> +@item @code{tls} (default: @code{#f}) boolean or string ``no-
> verify''
Instead of a string, take 'no-verify as symbol perhaps?
> +When @code{#t}, Require TLS to be used when relaying, using
> mandatory STARTTLS by
> +default. When used with a smarthost, the protocol must not be
> +@samp{"smtp+notls://"}. When string @code{"no-verify"}, then do not
> require a valid
> +certificate.
> +
> +@item @code{auth} (default: @code{#f}) @code{<opensmtpd-table>}
> +Use the alist @code{<opensmtpd-table>} for connecting to relay-url
> +using credentials. This option is usable only with fieldname
> @code{host} option.
> +
> +@item @code{mail-from} (default: @code{#f}) string
> +Use the string mailaddress as MAIL FROM address within the SMTP
> transaction.
> +
> +@item @code{src} (default: @code{#f}) string | @code{<opensmtpd-
> table>}
> +Use the string or @code{<opensmtpd-table>} sourceaddr for the
> +source IP address, which is useful on machines with multiple
> interfaces. If
> +the list contains more than one address, all of them are used in
> such a way
> +that traffic is routed as efficiently as possible.
> +@end itemize
> +
> +@item Data Type: opensmtpd-filter
> +This data type represents the configuration of an
> +@code{<opensmtpd-filter>}. This is the filter record one should use
> +if they want to use an external package to filter email eg: rspamd
> or
> +spamassassin.
> +
> +@itemize
> +@item @code{name} (default: @code{#f})
> +The string name of the filter.
> +
> +@item @code{proc} (default: @code{#f})
> +The string command or process name.  If @code{proc-exec} is
> @code{#t}, @code{proc} is
> +treated as a command to execute.  Otherwise, it is a process name.
> +
> +@item @code{proc-exec} (default: @code{#f})
> +If @code{#t}, then execute the command in @code{proc}.
> +@end itemize
> +
> +@item Data Type: opensmtpd-filter-phase
> +This data type represents the configuration of an
> +@code{<opensmtpd-filter-phase>}.
> +
> +In a regular workflow, @code{smtpd(8)} may accept or reject a
> message
> +based only on the content of envelopes. Its decisions are about the
> +handling of the message, not about the handling of an active
> session.
> +
> +Filtering extends the decision making process by allowing
> +@code{smtpd(8)} to stop at each phase of an SMTP session, check that
> +options are met, then decide if a session is allowed to move
> forward.
> +
> +With filtering via an @code{<opensmtpd-filter-phase>} record, a
> +session may be interrupted at any phase before an envelope is
> complete. A
> +message may also be rejected after being submitted, regardless of
> whether the
> +envelope was accepted or not.
> +
> +@itemize
> +@item @code{name} (default: @code{#f})
> +
> +The string name of the filter phase.
> +
> +@item @code{phase-name} (default: @code{#f})
> +The string name of the phase. Valid values are:
> +
> +@multitable {aaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @code{"connect"}
> +@tab upon connection, before a banner is displayed
> +@item @code{"helo"}
> +@tab after HELO command is submitted
> +@item @code{"ehlo"}
> +@tab after EHLO command is submitted
> +@item @code{"mail-from"}
> +@tab after MAIL FROM command is submitted
> +@item @code{"rcpt-to"}
> +@tab after RCPT TO command is submitted
> +@item @code{"data"}
> +@tab after DATA command is submitted
> +@item @code{"commit"}
> +@tab after message is fully is submitted
> +@end multitable
> +
> +@item @code{options} (default @code{#f})
> +A list of unique @code{<opensmtpd-option>} records.
> +
> +At each phase, various options, specified by a list of
> +@code{<opensmtpd-option>}, may be checked. The
> +@code{<opensmtpd-option>}'s fieldname @code{option} values of:
> +@code{"fcrdns"}, @code{"rdns"}, and @code{"src"} data are available
> in
> +all phases, but other data must have been already submitted before
> they
> +are available. Options with a @code{<table>} next to them require
> the
> +@code{<opensmtpd-option>}'s fieldname @code{data} to be an
> +@code{<opensmtpd-table>}. There are the available options:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @code{"fcrdns"}
> +@tab forward-confirmed reverse DNS is valid
> +@item @code{"rdns"}
> +@tab session has a reverse DNS
> +@item @code{"rdns"} <table>
> +@tab session has a reverse DNS in table
> +@item @code{"src"} <table>
> +@tab source address is in table
> +@item @code{"helo"} <table>
> +@tab helo name is in table
> +@item @code{"auth"}
> +@tab session is authenticated
> +@item @code{"auth"} <table>
> +@tab session username is in table
> +@item @code{"mail-from"} <table>
> +@tab sender address is in table
> +@item @code{"rcpt-to"} <table>
> +@tab recipient address is in table
> +@end multitable
> +
> +These conditions may all be negated by setting
> +@code{(opensmtpd-option (bool #f))}.
> +
> +Any conditions that require a table may indicate that tables include
> regexs
> +setting @code{(opensmtpd-option (regex #t))}.
> +
> +@item @code{decision}
> +A string decision to be taken. Some decisions require an
> @code{message}
> +or @code{value}.  The value and message may be put in the
> +@code{<opensmtpd-option>}'s fieldname @code{data}.  Valid strings
> are:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @code{"bypass"}
> +@tab the session or transaction bypasses filters
> +@item @code{"disconnect"} message
> +@tab the session is disconnected with message
> +@item @code{"junk"}
> +@tab the session or transaction is junked,
> +@item
> +@tab  i.e., an ‘X-Spam: yes’ header is added to
> +@item
> +@tab any messages
> +@item @code{"reject"} message
> +@tab the command is rejected with message
> +@item @code{"rewrite"} value
> +@tab the command parameter is rewritten with value
> +@end multitable
> +
> +Decisions that involve a message require that the message be RFC
> valid,
> +meaning that they should either start with a 4xx or 5xx status code.
> +Descisions can be taken at any phase, though junking can only happen
> before
> +a message is committed.
> +
> +@item @code{message} (default @code{#f})
> +A string message beginning with a 4xx or 5xx status code.
> +
> +@item @code{value} (default: @code{#f})
> +A number value.  @code{value} and @code{message} are mutually
> exclusive.
> +@end itemize
> +
> +@item Data Type: opensmtpd-option
> +This data type represents the configuration of an
> +@code{<opensmtpd-option>}, which is used by
> +@code{<opensmtpd-filter-phase>} and @code{<opensmtpd-match>}
> +to match various options for email.
> +
> +@itemize
> +@item @code{option} (default @code{#f}) string
> +
> +A string option to be taken. Some options require the fieldname
> +@code{data} to have a string or an @code{<opensmtpd-table>}. When
> the
> +option record is used inside of an @code{<opensmtpd-filter-phase>},
> then
> +valid strings for fieldname @code{option} are:
> +
> +@itemize
> +@item @code {"fcrdns"}
> +@item @code {"rdns"}
> +@item @code {"src"}
> +@item @code {"helo"}
> +@item @code {"auth"}
> +@item @code {"mail-from"}
> +@item @code {"rcpt-to"}
> +@end itemize
> +
> +When @code{<opensmtpd-option>} is used inside of an
> +@code{<opensmtpd-match>}, then valid strings for fieldname
> @code{option}
> +are:
> +
> +@itemize
> +@item @code {"for"}
> +@item @code {"for any"}
> +@item @code {"for local"}
> +@item @code {"for domain"}
> +@item @code {"for rcpt-to"}
> +@item @code {"from any"}
> +@item @code {"from auth"}
> +@item @code {"from local"}
> +@item @code {"from mail-from"}
> +@item @code {"from rdns"}
> +@item @code {"from socket"}
> +@item @code {"from src"}
> +@item @code {"auth"}
> +@item @code {"helo"}
> +@item @code {"mail-from"}
> +@item @code {"rcpt-to"}
> +@item @code {"tag"}
> +@item @code {"tls"}
> +@end itemize
> +
> +@item @code{data} (default @code{#f}) string | @code{<opensmtpd-
> table>}
> +Some options require a string or @code{<opensmtpd-table>} to be
> +present. One would specify that table here.
> +
> +@item @code{regex} (default: @code{#f}) boolean
> +Any options using a table may indicate that tables hold regular
> +expressions by setting this option to @code{#t}.
> +
> +@item @code{bool} (default: @code{#t}) boolean
> +When @code{(bool #f)}, this option record is negated.
> +@end itemize
> +
> +@item Data Type: opensmtpd-table
> +This data type represents the configuration of an
> +@code{<opensmtpd-table>}.
> +
> +@itemize
> +@item @code{name} (default @code{#f})
> +@code{name} is the name of the @code{<opensmtpd-table>} record.
> +
> +@item @code{data} (default: @code{#f}) string | list | alist |
> nested-list
> +@code{data} expects a string, a list of strings, an alist of
> strings, or
> +a nested list of strings.
> +eg:
> +
> +@itemize
> +
> +@item string
> +@lisp
> +(data "dev@@gnu.org")
> +@end lisp
> +
> +A table of this type is called a @code{string table}.
> +
> +@item list
> +@lisp
> +(data (list ("gnu.org" "fsf.org")))
> +@end lisp
> +
> +A table of this type is called a @code{list table}.
> +
> +@item alist
> +@lisp
> +(data '(("james" . "$encryptedPassword")
> +        ("jennifer" . "$encryptedPassword2)))
> +@end lisp
> +
> +A table of this type is called a @code{mapping table}.
> +
> +@item nested-list
> +@lisp
> +(data '(("user1" "root@@gnu.org" "admin@@gnu.org")
> +        ("user2" "james@@guix.gnu.org" "sarah@@fsf.org")))
> +@end lisp
> +
> +A table of this type is also called a @code{mapping table}.
> +
> +@end itemize
> +@end itemize
> +
> +@item Data Type: opensmtpd-pki
> +This data type represents the configuration of an
> +@code{<opensmtpd-pki>}.
> +
> +@itemize
> +@item @code{domain} (default @code{#f})
> +@code{domain} is the string name of the @code{<opensmtpd-pki>}
> record.
> +
> +@item @code{cert} (default: @code{#f})
> +@code{cert} (default: @code{#f})
> +
> +@code{cert} is the string certificate filename to use for this pki.
> +
> +@item @code{key} (default: @code{#f})
> +@code{key} is the string certificate falename to use for this pki.
> +
> +@item @code{dhe} (default: @code{"none"})
> +Specify the DHE string parameter to use for DHE cipher suites with
> host
> +pkiname. Valid parameter values are @code{"none"}, @code{"legacy"},
> or
> +@code{"auto"}. For @code{"legacy"}, a fixed key length of 1024 bits
> is
> +used, whereas for @code{"auto"}, the key length is determined
> +automatically. The default is @code{"none"}, which disables DHE
> cipher
> +suites.
> +@end itemize
> +
> +@item Data Type: opensmtpd-maildir
> +@itemize
> +@item @code{pathname} (default: @code{"~/Maildir"})
> +Deliver the message to the maildir if pathname if specified, or by
> default
> +to @code{"~/Maildir"}.
> +
> +The pathname may contain format specifiers that are expanded before
> use
> +(see FORMAT SPECIFIERS).
> +
> +@item @code{junk} (default: @code{#f})
> +If the junk argument is @code{#t}, then the message will be moved to
> the @samp{‘Junk’}
> +folder if it contains a positive @samp{‘X-Spam’} header. This folder
> will be
> +created under pathname if it does not yet exist.
> +@end itemize
> +
> +@item Data Type: opensmtpd-mda
> +This record lets you delegate the delivery to a command that
> receives
> +the message on its standard input.
> +
> +@itemize
> +@item @code{name}
> +The string name for this MDA command.
> +
> +@item @code{command}
> +The command to that delivers the mail.
> +
> +The command may contain format specifiers that are expanded before
> use (see
> +FORMAT SPECIFIERS).
> +@end itemize
> +
> +@item Data Type: opensmtpd-queue
> +@itemize
> +@item @code{compression} (default @code{#f})
> +Store queue files in a compressed format. This may be useful to save
> disk
> +space.
> +
> +@item @code{encryption} (default @code{#f})
> +Encrypt queue files with EVP@math{_aes}@math{_256}@math{_gcm}(3). If
> no key is specified, it is
> +read with getpass(3). If the string stdin or a single dash (‘-’) is
> given
> +instead of a key, the key is read from the standard input.
> +
> +@item @code{ttl-delay} (default @code{#f})
> +Set the default expiration time for temporarily undeliverable
> messages,
> +given as a positive decimal integer followed by a unit s, m, h, or
> d. The
> +default is four days (@code{"4d"}).
> +@end itemize
> +
> +@item Data Type: opensmtpd-smtp
> +Data type representing an @code{<opensmtpd-smtp>} record.
> +
> +@itemize
> +@item @code{ciphers} (default: @code{#f})
> +Set the control string for
> SSL@math{_CTX}@math{_set}@math{_cipher}@math{_list}(3).  The default
> is
> +         ``HIGH:!aNULL:!MD5''.
> +
> +@item @code{limit-max-mails} (default: @code{100})
> +Limit the number of messages to count for each sessio
> +
> +@item @code{limit-max-rcpt} (default: @code{1000})
> +Limit the number of recipients to count for each transaction.
> +
> +@item @code{max-message-size} (default: @code{35M})
> +Reject messages larger than size, given as a positive number of
> bytes or as
> +a string to be parsed with scan@math{_scaled}(3).
> +
> +@item @code{sub-addr-delim character} (default: @code{+})
> +When resolving the local part of a local email address, ignore the
> ASCII
> +character and all characters following it. This is helpful for email
> +filters. @samp{"admin+bills@@gnu.org"} is the same email address as
> +@samp{"admin@@gnu.org"}. BUT an email filter can filter emails
> addressed to first
> +email address into a 'Bills' email folder.
> +@end itemize
> +
> +@item Data Type: opensmtpd-srs
> +Use this record to set up the Sender Rewriting Scheme (SRS).
> +
> +@itemize
> +@item @code{key} (default: @code{#f})
> +Set the secret key to use for SRS, the Sender Rewriting Scheme.
> +
> +@item @code{backup-key} (default: @code{#f})
> +Set a backup secret key to use as a fallback for SRS@. This can be
> used to
> +implement SRS key rotation.
> +
> +@item @code{ttl-delay} (default: @code{"4d"})
> +Set the time-to-live delay for SRS envelopes. After this delay, a
> bounce
> +reply to the SRS address will be discarded to limit risks of forged
> +addresses.
> +@end itemize
> +
> +@item Format Specifiers
> +Some configuration records support expansion of their parameters at
> +runtime. Such records (for example
> +@code{<opensmtpd-maildir>}, @code{<opensmtpd-mda>}) may use
> +format specifiers which are expanded before delivery or relaying.
> The
> +following formats are currently supported:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @samp{%@{sender@}}
> +@tab sender email address, may be empty string
> +@item @samp{%@{sender.user@}}
> +@tab user part of the sender email address, may be empty
> +@item @samp{%@{sender.domain@}}
> +@tab domain part of the sender email address, may be empty
> +@item @samp{%@{rcpt@}}
> +@tab recipient email address
> +@item @samp{%@{rcpt.user@}}
> +@tab user part of the recipient email address
> +@item @samp{%@{rcpt.domain@}}
> +@tab domain part of the recipient email address
> +@item @samp{%@{dest@}}
> +@tab recipient email address after expansion
> +@item @samp{%@{dest.user@}}
> +@tab user part after expansion
> +@item @samp{%@{dest.domain@}}
> +@tab domain part after expansion
> +@item @samp{%@{user.username@}}
> +@tab local user
> +@item @samp{%@{user.directory@}}
> +@tab home directory of the local user
> +@item @samp{%@{mbox.from@}}
> +@tab name used in mbox From separator lines
> +@item @samp{%@{mda@}}
> +@tab mda command, only available for mda wrappers
> +@end multitable
> +
> +Expansion formats also support partial expansion using the optional
> bracket notations
> +with substring offset.  For example, with recipient domain
> @samp{“example.org”}:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaa}
> +@item @samp{%@{rcpt.domain[0]@}}
> +@tab expands to “e”
> +@item @samp{%@{rcpt.domain[1]@}}
> +@tab expands to “x”
> +@item @samp{%@{rcpt.domain[8:]@}}
> +@tab expands to “org”
> +@item @samp{%@{rcpt.domain[-3:]@}}
> +@tab expands to “org”
> +@item @samp{%@{rcpt.domain[0:6]@}}
> +@tab expands to “example”
> +@item @samp{%@{rcpt.domain[0:-4]@}}
> +@tab expands to “example”
> +@end multitable
> +
> +In addition, modifiers may be applied to the token.  For example,
> with recipient
> +@samp{“User+Tag@@Example.org”}:
> +
> +@multitable {aaaaaaaaaaaaaaaaaaaaaaaa}
> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @samp{%@{rcpt:lowercase@}}
> +@tab expands to “user+tag@@example.org”
> +@item @samp{%@{rcpt:uppercase@}}
> +@tab expands to “USER+TAG@@EXAMPLE.ORG”
> +@item @samp{%@{rcpt:strip@}}
> +@tab expands to “User@@Example.org”
> +@item @samp{%@{rcpt:lowercasestrip@}}
> +@tab expands to “user@@example.org”
> +@end multitable
> +
> +For security concerns, expanded values are sanitized and potentially
> dangerous
> +characters are replaced with ‘:’. In situations where they are
> desirable, the
> +“raw” modifier may be applied. For example, with recipient
> +@samp{“user+t?g@@example.org”}:
> +
> +@multitable {aaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
> +@item @samp{%@{rcpt@}}
> +@tab expands to “user+t:g@@example.org”
> +@item @samp{%@{rcpt:raw@}}
> +@tab expands to “user+t?g@@example.org”
> +@end multitable
> +@end itemize
> +
>  @subsubheading Exim Service
>  
>  @cindex mail transfer agent (MTA)
> diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm
> index 43f144a42d..4175cab375 100644
> --- a/gnu/services/mail.scm
> +++ b/gnu/services/mail.scm
> @@ -35,6 +35,10 @@ (define-module (gnu services mail)
>    #:use-module (gnu packages admin)
>    #:use-module (gnu packages dav)
>    #:use-module (gnu packages tls)
> +  #:use-module (guix i18n)
> +  #:use-module (guix diagnostics)
> +  #:use-module (guix ui)
> +  #:use-module (guix utils)
>    #:use-module (guix records)
>    #:use-module (guix packages)
>    #:use-module (guix gexp)
> @@ -58,10 +62,149 @@ (define-module (gnu services mail)
>              mailbox-configuration
>              namespace-configuration
>  
> +            opensmtpd-table
> +            opensmtpd-table?
> +            opensmtpd-table-name
> +            opensmtpd-table-data
> +
> +            opensmtpd-ca
> +            opensmtpd-ca?
> +            opensmtpd-ca-name
> +            opensmtpd-ca-file
> +
> +            opensmtpd-pki
> +            opensmtpd-pki?
> +            opensmtpd-pki-domain
> +            opensmtpd-pki-cert
> +            opensmtpd-pki-key
> +            opensmtpd-pki-dhe
> +
> +            opensmtpd-local-delivery
> +            opensmtpd-local-delivery?
> +            opensmtpd-local-delivery-method
> +            opensmtpd-local-delivery-alias
> +            opensmtpd-local-delivery-ttl
> +            opensmtpd-local-delivery-user
> +            opensmtpd-local-delivery-userbase
> +            opensmtpd-local-delivery-virtual
> +            opensmtpd-local-delivery-wrapper
> +
> +            opensmtpd-maildir
> +            opensmtpd-maildir?
> +            opensmtpd-maildir-pathname
> +            opensmtpd-maildir-junk
> +
> +            opensmtpd-mda
> +            opensmtpd-mda-name
> +            opensmtpd-mda-command
> +
> +            opensmtpd-lmtp
> +            opensmtpd-lmtp-destination
> +            opensmtpd-lmtp-rcpt
> +
> +            opensmtpd-relay
> +            opensmtpd-relay?
> +            opensmtpd-relay-name
> +            opensmtpd-relay-backup
> +            opensmtpd-relay-backup-mx
> +            opensmtpd-relay-helo
> +            opensmtpd-relay-domain
> +            opensmtpd-relay-host
> +            opensmtpd-relay-pki
> +            opensmtpd-relay-srs
> +            opensmtpd-relay-tls
> +            opensmtpd-relay-auth
> +            opensmtpd-relay-mail-from
> +            opensmtpd-relay-src
> +
> +            opensmtpd-option
> +            opensmtpd-option?
> +            opensmtpd-option-option
> +            opensmtpd-option-bool
> +            opensmtpd-option-regex
> +            opensmtpd-option-data
> +
> +            opensmtpd-filter-phase
> +            opensmtpd-filter-phase?
> +            opensmtpd-filter-phase-name
> +            opensmtpd-filter-phase-phase
> +            opensmtpd-filter-phase-options
> +            opensmtpd-filter-phase-decision
> +            opensmtpd-filter-phase-message
> +            opensmtpd-filter-phase-value
> +
> +            opensmtpd-filter
> +            opensmtpd-filter?
> +            opensmtpd-filter-name
> +            opensmtpd-filter-proc
> +
> +            opensmtpd-interface
> +            opensmtpd-interface?
> +            opensmtpd-interface-interface
> +            opensmtpd-interface-family
> +            opensmtpd-interface-auth
> +            opensmtpd-interface-auth-optional
> +            opensmtpd-interface-filters
> +            opensmtpd-interface-hostname
> +            opensmtpd-interface-hostnames
> +            opensmtpd-interface-mask-src
> +            opensmtpd-interface-disable-dsn
> +            opensmtpd-interface-pki
> +            opensmtpd-interface-port
> +            opensmtpd-interface-proxy-v2
> +            opensmtpd-interface-received-auth
> +            opensmtpd-interface-senders
> +            opensmtpd-interface-masquerade
> +            opensmtpd-interface-secure-connection
> +            opensmtpd-interface-tag
> +
> +            opensmtpd-socket
> +            opensmtpd-socket?
> +            opensmtpd-socket-filters
> +            opensmtpd-socket-mask-src
> +            opensmtpd-socket-tag
> +
> +            opensmtpd-match
> +            opensmtpd-match?
> +            opensmtpd-match-action
> +            opensmtpd-match-options
> +
> +            opensmtpd-smtp
> +            opensmtpd-smtp?
> +            opensmtpd-smtp-ciphers
> +            opensmtpd-smtp-limit-max-mails
> +            opensmtpd-smtp-limit-max-rcpt
> +            opensmtpd-smtp-max-message-size
> +            opensmtpd-smtp-sub-addr-delim character
> +
> +            opensmtpd-srs
> +            opensmtpd-srs?
> +            opensmtpd-srs-key
> +            opensmtpd-srs-backup-key
> +            opensmtpd-srs-ttl-delay
> +
> +            opensmtpd-queue
> +            opensmtpd-queue?
> +            opensmtpd-queue-compression
> +            opensmtpd-queue-encryption
> +            opensmtpd-queue-ttl-delay
> +
>              opensmtpd-configuration
>              opensmtpd-configuration?
> +            opensmtpd-package
> +            opensmtpd-config-file
> +            opensmtpd-configuration-bounce
> +            opensmtpd-configuration-cas
> +            opensmtpd-configuration-interfaces
> +            opensmtpd-configuration-socket
> +            opensmtpd-configuration-includes
> +            opensmtpd-configuration-matches
> +            ;;opensmtpd-configuration-mda-wrappers
> +            opensmtpd-configuration-mta-max-deferred
> +            opensmtpd-configuration-srs
> +            opensmtpd-configuration-smtp
> +            opensmtpd-configuration-queue
>              opensmtpd-service-type
> -            %default-opensmtpd-config-file
>  
>              mail-aliases-service-type
>  
> @@ -1641,22 +1784,2351 @@ (define (generate-dovecot-documentation)
>         (listeners unix-listener-configuration fifo-listener-
> configuration
>                    inet-listener-configuration))
>        (protocol-configuration ,protocol-configuration-fields))
> -  'dovecot-configuration))
> +   'dovecot-configuration))
>  
>  
> -;;;
>  ;;; OpenSMTPD.
>  ;;;
> +;;; This next bit of code helps me create my own sanitizer
> functions.
> +
> +;; some fieldnames have a default value of #f, which is ok.  They
> cannot have
> +;; a value of #t.
> +;; for example opensmtpd-table-data can be #f, BUT NOT true.
> +;; my/sanitize procedure tests values to see if they are of the
> right kind.
> +;; procedure false? is needed to allow fields like 'values' to be
> blank,
> +;; (empty), or #f BUT also have a value like a list of strings.
Use less egocentric comments ;)

> +(define (false? var)
> +  (eq? #f var))
> +
> +;; TODO I have to have this procedure, or I need to change
> my/sanitize
> +;; procedure.
> +(define (my-file-exists? file)
> +  (and (string? file)
> +       (access? file F_OK)))
Does file-exists? not work for you?

> +;; This procedure takes in a var and a list of procedures.  It loops
> through
> +;; list of procedures passing in var to each.
> +;; if one procedure returns #t, the function returns true. 
> Otherwise #f.
> +;; TODO for fun rewrite this using map
> +;; If I rewrote it in map, then it may help with sanitizing.
> +;; eg: I could then potentially easily sanitize vars with lambda
> procedures.
> +(define (is-value-right-type? var list-of-procedures record
> fieldname)
> +  (if (null? list-of-procedures)
> +      #f
> +      (if ((car list-of-procedures) var)
> +          #t
> +          (is-value-right-type? var (cdr list-of-procedures) record
> +                                fieldname))))
Alternatively, (any (cut <> var) list-of-procedures).

> +;; converts strings like this:
> +;; "apple, ham, cherry" -> "apple, ham, or cherry"
> +;; "pineapple" -> "pinneapple".
> +;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam"
> +(define (add-comma-or string)
> +  (define last-comma-location (string-rindex string #\,))
> +  (if last-comma-location
> +      (if (string-contains string ", or" last-comma-location)
> +          string
> +          (string-replace string ", or" last-comma-location
> +                          (+ 1 last-comma-location)))
> +      string))
> +
> +
> +(define (list-of-procedures->string procedures)
> +  (define string
> +    (let loop ((procedures procedures))
> +      (if (null? procedures)
> +          ""
> +          (begin
> +            (string-append
> +             (cond ((eq? false? (car procedures))
> +                    "#f, ")
> +                   ((eq? boolean? (car procedures))
> +                    "a boolean, ")
> +                   ((eq? string? (car procedures))
> +                    "a string, ")
> +                   ((eq? integer? (car procedures))
> +                    "an integer, ")
> +                   ((eq? list-of-strings? (car procedures))
> +                    "a list of strings, ")
> +                   ((eq? assoc-list? (car procedures))
> +                    "an association list of strings, ")
> +                   ((eq? nested-list? (car procedures))
> +                    "a nested-list of strings, ")
> +                   ((eq? opensmtpd-pki? (car procedures))
> +                    "an <opensmtpd-pki> record, ")
> +                   ((eq? opensmtpd-table? (car procedures))
> +                    "an <opensmtpd-table> record, ")
> +                   ((eq? list-of-opensmtpd-match? (car procedures))
> +                    "a list of unique <opensmtpd-match> records, ")
> +                   ((eq? list-of-strings-or-gexps? (car procedures))
> +                    "a list of strings or gexps, ")
> +                   ;; TODO can I remove the next two procedures?
> +                   ;; tables-data-are-a* ?  I think I can.
> +                   ((eq? tables-data-are-assoc-list? (car
> procedures))
> +                    (string-append
> +                     "an <opensmtpd-table> record whose fieldname
> 'data' are "
> +                     "an assoc-list.\nFor example: (opensmtpd-table 
> "
> +                     "(name \"hostnames\") , "
> +                     "(data '((\"124.394.23.1\" . \"gnu.org\"))))"))
> +                   ((eq? tables-data-are-a-list-of-strings?
> +                         (car procedures))
> +                    (string-append
> +                     "on <opensmtpd-table> record whose fieldname
> 'data' is "
> +                     "a list of strings.\n"
> +                     "For example: (opensmtpd-table (name
> \"domains\") , "
> +                     "(data (list \"gnu.org\" \"guix.gnu.org\")))"))
> +                   ((eq? my-file-exists? (car procedures))
> +                    "a file, ")
> +                   (else "has an incorrect value, "))
> +             (loop (cdr procedures)))))))
> +  (add-comma-or (string-append (string-drop-right string 2) ".\n")))
(define (procedure->string) ...)
(define (procedures->string list)
  (define strings (map procedure->string list))
  (string-append
   (string-join (drop-right strings 1) ", ")
   (if (> (length list) 1) ", or")
   (last strings)
   ".\n"))

> +(define (list-of-strings-or-gexps? list)
> +  (and (list? list)
> +       (cond ((null? list)
> +              #t)
> +             ((or (string? (car list))
> +                  (gexp? (car list))
> +                  (local-file? (car list))
> +                  (file-append? (car list))
> +                  (plain-file? (car list))
> +                  (computed-file? (car list))
> +                  (program-file? (car list)))
> +              (list-of-strings-or-gexps? (cdr list)))
> +             (else #f))))
> +
> +(define (my/sanitize var record fieldname list-of-procedures)
> +  (define try-string
> +    (string-append "Try " (list-of-procedures->string list-of-
> procedures)))
> +  (if (is-value-right-type? var list-of-procedures record fieldname)
> +      var
> +      (begin
> +        (cond ((string? var)
> +               (report-error (G_ "(~a \"~a\") is invalid.~%")
> fieldname var))
> +              ((or (number? var) (boolean? var))
> +              (report-error (G_ "(~a ~a) is invalid.~%") fieldname
> var) )
> +              (else
> +               (report-error (G_ "(~a ...) is invalid.~%Value is:
> ~a~%")
> +                             fieldname var)))
> +        (display-hint (G_ try-string))
> +        (throw 'bad! var))))
This procedure needs a proper name, like sanitize/check-type, but more
importantly, why not simply use define-configuration?

Cheers