Status Update April 2022 — GNUcode.me

Status Update April 2022

by Joshua Branson — April 01, 2022

I am back to working on various records to support <opensmtpd-configuration> for the opensmtpd-service-type. I decided about a week ago, to just do some of the changes in the records that I want to do. Once I am satisfied with the updates, then I will work on making the code output the smtpd.conf file. I am fairly please with some of the changes to the records that I have made.

Feel free to play with the examples below from my repo!

https://notabug.org/jbranso/linode-guix-system-configuration.git
guile -L .
scheme@(guile-user)> ,use (opensmtpd-records)
scheme@(guile-user)> ,m (opensmtpd-records)
(opensmtpd-table (name "table") (values (list "hello" "world")))

Please note, that I am still working on changing the record names, so various things that work as described in the blog post may not work in a week or two.

a pretty useful <opensmtpd-configuration> gives no errors

(define example-opensmtpd-configuration
  (let ([interface "lo"]
        [creds-table (opensmtpd-table
                      (name "creds")
                      (values
                       (list
                        (cons "joshua"
                              "$some$encrypted$password"))))]
        [receive-action (opensmtpd-action-local-delivery-configuration
                         (name "receive")
                         (method (opensmtpd-maildir-configuration
                                  (pathname "/home/%{rcpt.user}/Maildir")
                                  (junk #t)))
                         (virtual (opensmtpd-table
                                   (name "virtual")
                                   (values (list "josh" "jbranso@dismail.de")))))]
        [filter-dkimsign (opensmtpd-filter
                          (name "dkimsign")
                          (exec #t)
                          (proc (string-append "/path/to/dkimsign  -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
                                               "/path/to/dkimsign-key user nobody group nobody")))]
        [smtp.gnucode.me (opensmtpd-pki
                          (domain "smtp.gnucode.me")
                          (cert "opensmtpd.scm")
                          (key "opensmtpd.scm"))])
    (opensmtpd-configuration
     (mta-max-deferred 50)
     (queue
      (opensmtpd-queue-configuration
       (compression #t)))
     (smtp
      (opensmtpd-smtp-configuration
       (max-message-size "10M")))
     (srs
      (opensmtpd-srs-configuration
       (ttl-delay "5d")))
     (listen-ons
      (list
       (opensmtpd-listen-on
        (interface interface)
        (port 25)
        (secure-connection "tls")
        (filters (list (opensmtpd-filter-phase
                        (name "noFRDNS")
                        (phase "commit")
                        (conditions (list (opensmtpd-conditions-configuration
                                           (condition "fcrdns")
                                           (not #t))))
                        (decision "disconnect")
                        (message "No FCRDNS"))))
        (pki smtp.gnucode.me))
       ;; this lets local users logged into the system via ssh send email
       (opensmtpd-listen-on
        (interface interface)
        (port 465)
        (secure-connection "smtps")
        (pki smtp.gnucode.me)
        (auth creds-table)
        (filters (list filter-dkimsign)))
       (opensmtpd-listen-on
        (interface interface)
        (port 587)
        (secure-connection "tls-require")
        (pki smtp.gnucode.me)
        (auth creds-table)
        (filters (list filter-dkimsign)))))
     (matches (list
               (opensmtpd-match
                (action (opensmtpd-action-relay-configuration
                         (name "relay")))
                (for (opensmtpd-match-configuration
                      (option "for any")))
                (from (opensmtpd-match-configuration
                       (option "from any")))
                (auth (opensmtpd-match-configuration
                       (option "auth"))))
               (opensmtpd-match
                (action receive-action)
                (from (opensmtpd-match-configuration
                       (option "from any")))
                (for (opensmtpd-match-configuration
                      (option "for domain")
                      (value (opensmtpd-table
                              (name "domain-table")
                              (values (list "gnucode.me" "gnu-hurd.com")))))))
               (opensmtpd-match
                (action receive-action)
                (for (opensmtpd-match-configuration
                      (option "for local")))))))))

However there’s still some work to do because this doesn’t work:

(opensmtpd-configuration->mixed-text-file example-opensmtpd-configuration)

ice-9/boot-9.scm:1685:16: In procedure raise-exception:
error: value: unbound variable

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(opensmtpd-records) [12]> ,bt
In /home/joshua/prog/gnu/guix/guix-config/linode-guix-system-configuration/opensmtpd-records.scm:
   1667:3  4 (opensmtpd-configuration->mixed-text-file #<<opensmtpd-configuration> package: #<package opensmtpd@6.8.0p2 gnu/packages/mail.scm:2979 7f1e1a3…>)
   1628:9  3 (opensmtpd-configuration-fieldname->string _ _ _)
  1634:10  2 (list-of-records->string _ _)
  1669:99  1 (_ _)
In ice-9/boot-9.scm:
  1685:16  0 (raise-exception _ #:continuable? _)

I have also sanitized the <opensmtpd-conditions-configuration>

If you type in various incorrectly written <opensmtpd-conditions-configuration> records, then you will get some helpful error messages:

  1. if condition is rdns, src, helo, mail-from, rcpt-to, then they must also provide a table

    What is interesting, is that I do not know how to sanitize a whole record when the record is initiated. I can only have a parent record sanitize it. For example the following record is invalid, because if the ’condition’ is “src”, then you need to provide a table. However, the following works in a REPL.

    (opensmtpd-conditions-configuration
        (condition "src"))

    $11 = #<<opensmtpd-conditions-configuration> condition: "src" not: #f regex: #f table: #f>

    But when you put the same incorrect <opensmtpd-conditions-configuration> into an <opensmtpd-filter-phase>, then you get the right error message.

    (opensmtpd-filter-phase
     (name "filter")
     (phase "helo")
     (decision "bypass")
     (conditions
      (list
       (opensmtpd-conditions-configuration
        (condition "src")))))
    
    <opensmtpd-conditions-configuration>'s fieldname 'condition' values of
    'src', 'helo', 'mail-from', or 'rcpt-to' need a corresponding 'table'
     of type <opensmtpd-table>. eg:
    (opensmtpd-conditions-configuration
       (condition "src")
       (table (opensmtpd-table
                  (name "src-table")
                  (values (list "hello" "cat")))))
    ice-9/boot-9.scm:1685:16: In procedure raise-exception:
    Throw to key `bad!' with args `((#<<opensmtpd-conditions-configuration> condition: "mail-from" not: #f regex: #f table: #f>))'.
        
    Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
  2. make sure that there are no duplicate conditions

    (opensmtpd-filter-phase
     (name "noFRDNS")
     (phase "commit")
     (conditions (list (opensmtpd-conditions-configuration
                        (condition "fcrdns")
                        (not #t))
                       (opensmtpd-conditions-configuration
                        (condition "fcrdns")
                        (not #t))))
     (decision "disconnect")
     (message "No FCRDNS"))
    
    <opensmtpd-filter-phase> fieldname: 'conditions' is a list of unique
    <opensmtpd-conditions-configuration> records.
    ice-9/boot-9.scm:1685:16: In procedure raise-exception:
    Throw to key `bad!' with args `((#<<opensmtpd-conditions-configuration> condition: "fcrdns" not: #t regex: #f table: #f> #<<opensmtpd-conditions-configuration> condition: "fcrdns" not: #t regex: #f table: #f>))'.
        
    Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
  3. sanitize the phase-name

    (opensmtpd-filter-phase
     (name "filter")
     (phase "hello")
     (decision "bypass")
     (conditions
      (list
       (opensmtpd-conditions-configuration
        (condition "auth")))))
    
    <opensmtpd-filter-phase> fieldname: 'phase' is of type string.  The string can be either 'connect', 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'
     ice-9/boot-9.scm:1685:16: In procedure raise-exception:
    Throw to key `bad!' with args `("hello")'.
        
    Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.

Changes to the records eleminate potential errors like

misspelling a table name, or calling an action that is not defined.

The <opensmtpd-configuration> used to be defined this way:

(service opensmtpd-service
         (opensmtpd-configuration
          (includes ...)
          (tables ...)
          (pkis ...)
          (filters ...)
          (listen-on ...)
          (actions ...)
          (matches ...)))

It would be possible to give a table a name of “password-table”, but then later to refer to it as “passwords-table”, which would NOT have worked. Like so:

(service opensmtpd-service
         (opensmtpd-configuration
          (tables (list (opensmtpd-table
                         (name "<passwords-table>")
                         (values
                          (list
                           (cons "joshua"
                                 "$encrypted$password"))))))
          (listen-on
           (list (opensmtpd-listen-on
                  (auth "<password-table>" ))))
          (actions ...)
          (matches ...)))

Now instead, you define the table where it is used!

(opensmtpd-listen-on
 (interface interface)
 (port 587)
 (secure-connection "tls-require")
 (pki smtp.gnucode.me)
 (auth (opensmtpd-table
        (name "creds")
        (values
         (list
          (cons "joshua"
                "$encrypted$password")))))
 (filters (list filter-dkimsign)))