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

From: Joshua Branson <[email-masked]>
Date: Tue, 27 Dec 2022 19:16:15 -0500

Liliana Marie Prikler <[email-masked]> writes:

> 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.

Thanks again for your speedy review! I am sending this from my gnucode.me email
address you'll notice. And it is using these new records. :)

>
>> (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.

Users can still use

(service opensmtpd-service-type
         (opensmtpd-configuration
           (config-file (local-file "./my-smtpd.conf"))))

config-file is still a fieldname of <opensmtpd-configuration>.  Is that what you mean?

Also the generated documentation is available here:

https://notabug.org/jbranso/linode-guix-system-configuration/src/master/opensmtpd-records-documentation.txt

>> +@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

Are you saying secure-connection is not documented?

It's documented in the table just below:

>> +@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.

If you are referring to <opensmtpd-option> is it documented like this:

• Data Type: opensmtpd-option This data type represents the
  configuration of an ‘<opensmtpd-option>’, which is used by
  ‘<opensmtpd-filter-phase>’ and ‘<opensmtpd-match>’ to match various
  options for email.

(sorry about the above formatting). That is currently how the documentation is
generated. I need to go through the documentation is fix that. I did
<opensmtpd-configuration> correctly.

>> +@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.

Are you giving me a triple A+ ? :) Org generated the the like that. I think you
mentioned that I should use fractions last time. Sorry I did not do that. If I
wait 'til I implement every one of your suggestions, I will probably never
submit it. I am really probably "perfecting" this service.

>> +@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?

Sounds good to me. May I ask why you prefer a symbol instead of a string?

>> +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 ;)

I'm not sure what you mean here? I know I had a comment in my task list that
said something like my sanitizer function are probably better than those found
in guix. Apologies for that.

>
>> +(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?

The file-exists? function causes my-sanitize function to break. I think. If you
get rid of it, then what happens when a user types in (file 4), you get an
raise-exception. I can probably just rework my-sanitizer function to deal with
that possibility, but I have not yet. I would love some guidance on how to do
that. Because I feel like having to handle that exception is hard.

>
>> +;; 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).

You mentioned that in the last review, I just can't figure out how to use your
suggestion. This is the code that I have in the task list WIP:

*** TODO simplify my sanitizing funcions  (any (cut <> var))

#+BEGIN_SRC scheme
(use-modules (ice-9 curried-definitions)
             (srfi srfi-26))

(define (((expect-any predicates) record field) var)
  (if (any (cut <> var) predicates)
      var
      (begin
        ;; code code code
        ;; how do I tell the user which function failed?
        (display "error")
        (throw 'bad! var))))

;; here is how you use it.
  (name opensmtpd-table-name ;; string
        (default #f)
        (sanitize (lambda (var)
                    (((expect-any (list string? number?)) "hello" "that") var))))

#+END_SRC

Does that look close to what you want? I feel like it is way off, but I don't
know. Honestly when I say this suggestion I was completely blown away, I have
been using (any ) and (every) in a few places to get rid of some uses of
primitive eval.

>
>> +;; 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?

Yes! I have slowly been realizing that I have been clumsily re-inventing
define-configuration. I hope to switch to define-configuration, because a lot of
this code would go away. But I need to explore how define-configuration works.
That would be quite a major change. :)

> Cheers