1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291 |
- ;;; GNU Guix --- Functional package management for GNU
- ;;; Copyright © 2016 Sou Bunnbu <iyzsong@member.fsf.org>
- ;;; Copyright © 2017 Carlo Zancanaro <carlo@zancanaro.id.au>
- ;;; Copyright © 2017, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
- ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
- ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
- ;;; Copyright © 2019 Christopher Baines <mail@cbaines.net>
- ;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
- ;;;
- ;;; This file is part of GNU Guix.
- ;;;
- ;;; GNU Guix is free software; you can redistribute it and/or modify it
- ;;; under the terms of the GNU General Public License as published by
- ;;; the Free Software Foundation; either version 3 of the License, or (at
- ;;; your option) any later version.
- ;;;
- ;;; GNU Guix is distributed in the hope that it will be useful, but
- ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;;; GNU General Public License for more details.
- ;;;
- ;;; You should have received a copy of the GNU General Public License
- ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
- (define-module (gnu tests mail)
- #:use-module (gnu tests)
- #:use-module (gnu packages mail)
- #:use-module (gnu system)
- #:use-module (gnu system accounts)
- #:use-module (gnu system shadow)
- #:use-module (gnu system vm)
- #:use-module (gnu services)
- #:use-module (gnu services base)
- #:use-module (gnu services getmail)
- #:use-module (gnu services mail)
- #:use-module (gnu services networking)
- #:use-module (guix gexp)
- #:use-module (guix store)
- #:use-module (ice-9 ftw)
- #:use-module (srfi srfi-64)
- #:export (%test-opensmtpd
- %test-exim
- %test-dovecot
- %test-getmail))
- (define %opensmtpd-os
- (simple-operating-system
- (service dhcp-client-service-type)
- (service opensmtpd-service-type
- (opensmtpd-configuration
- (config-file
- (plain-file "smtpd.conf" "
- listen on 0.0.0.0
- action inbound mbox
- match from any for local action inbound
- "))))))
- (define (run-opensmtpd-test)
- "Return a test of an OS running OpenSMTPD service."
- (define vm
- (virtual-machine
- (operating-system (marionette-operating-system
- %opensmtpd-os
- #:imported-modules '((gnu services herd))))
- (port-forwardings '((1025 . 25)))))
- (define test
- (with-imported-modules '((gnu build marionette))
- #~(begin
- (use-modules (rnrs base)
- (srfi srfi-64)
- (ice-9 rdelim)
- (ice-9 regex)
- (gnu build marionette))
- (define marionette
- (make-marionette '(#$vm)))
- (define (read-reply-code port)
- "Read a SMTP reply from PORT and return its reply code."
- (let* ((line (read-line port))
- (mo (string-match "([0-9]+)([ -]).*" line))
- (code (string->number (match:substring mo 1)))
- (finished? (string= " " (match:substring mo 2))))
- (if finished?
- code
- (read-reply-code port))))
- (test-runner-current (system-test-runner #$output))
- (test-begin "opensmptd")
- (test-assert "service is running"
- (marionette-eval
- '(begin
- (use-modules (gnu services herd))
- (start-service 'smtpd))
- marionette))
- (test-assert "mbox is empty"
- (marionette-eval
- '(and (file-exists? "/var/spool/mail")
- (not (file-exists? "/var/spool/mail/root")))
- marionette))
- (test-eq "accept an email"
- #t
- (let* ((smtp (socket AF_INET SOCK_STREAM 0))
- (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
- (connect smtp addr)
- ;; Be greeted.
- (read-reply-code smtp) ;220
- ;; Greet the server.
- (write-line "EHLO somehost" smtp)
- (read-reply-code smtp) ;250
- ;; Set sender email.
- (write-line "MAIL FROM: <someone>" smtp)
- (read-reply-code smtp) ;250
- ;; Set recipient email.
- (write-line "RCPT TO: <root>" smtp)
- (read-reply-code smtp) ;250
- ;; Send message.
- (write-line "DATA" smtp)
- (read-reply-code smtp) ;354
- (write-line "Subject: Hello" smtp)
- (newline smtp)
- (write-line "Nice to meet you!" smtp)
- (write-line "." smtp)
- (read-reply-code smtp) ;250
- ;; Say goodbye.
- (write-line "QUIT" smtp)
- (read-reply-code smtp) ;221
- (close smtp)
- #t))
- (test-assert "mail arrived"
- (marionette-eval
- '(begin
- (use-modules (ice-9 popen)
- (ice-9 rdelim))
- (define (queue-empty?)
- (let* ((pipe (open-pipe* OPEN_READ
- #$(file-append opensmtpd
- "/sbin/smtpctl")
- "show" "queue"))
- (line (read-line pipe)))
- (close-pipe pipe)
- (eof-object? line)))
- (let wait ((n 20))
- (cond ((queue-empty?)
- (file-exists? "/var/spool/mail/root"))
- ((zero? n)
- (error "root mailbox didn't show up"))
- (else
- (sleep 1) (wait (- n 1))))))
- marionette))
- (test-end))))
- (gexp->derivation "opensmtpd-test" test))
- (define %test-opensmtpd
- (system-test
- (name "opensmtpd")
- (description "Send an email to a running OpenSMTPD server.")
- (value (run-opensmtpd-test))))
- ;; trying to create a bad record, should result in an error.
- ;; this function should not be able return, instead it should throw an error
- ;(define (create-bad-record record)
- ;; TODO why is this not working
- ; (with-output-to-port (%make-void-port "w")
- ; (lambda () (when record #f))))
- ;; if this caller function is reached, then trying to create the bad record
- ;; resulted in an error. So return true.
- (define (return-true error arg)
- #t)
- (define (return-false error arg)
- #f)
- (define (test-good-record func)
- (catch #t func return-false))
- (define (run-opensmtpd-record-sanitation-test)
- ;;(with-output-to-port (%make-void-port "w")
- ;; (lambda ()
- (test-begin "run-opensmtpd-record-sanitation-test")
- (test-error "(interface (filters ...)) has two filters with the same name."
- #t
- (let ((dkimsign
- (opensmtpd-filter
- (name "dkimsign")
- (exec #t)
- (proc
- (list
- (file-append
- opensmtpd-filter-dkimsign
- "/libexec/opensmtpd/filter-dkimsign")
- " -d gnucode.me -s 2021-09-22 -c "
- "relaxed/relaxed -k "
- "rando string"
- "/etc/dkim/private.key "
- "user nobody group nogroup")))))
- (opensmtpd-interface
- (interface "lo")
- (filters (list
- dkimsign
- dkimsign)))))
- ;; duplicate filter names. The filters are different, the fix
- ;; is to change one filter's 'name' field.
- (test-error "Test <interface> cannot have two filters with the same name."
- #t
- (opensmtpd-interface
- (filters (list
- (opensmtpd-filter-phase
- (name "src")
- (phase "connect")
- (options
- (list
- (opensmtpd-option
- (option "fcrdns")
- (bool #f))))
- (decision "junk"))
- (opensmtpd-filter-phase
- (name "src")
- (phase "helo")
- (options
- (list
- (opensmtpd-option
- (option "rdns")
- (bool #f))))
- (decision "reject")
- (message "433 no rdns."))))))
- (test-error "(filter (proc ...)) is a list of strings or gexps, NOT numbers."
- #t
- (opensmtpd-filter
- (name "dkimsign")
- (proc
- (list
- (file-append opensmtpd-filter-dkimsign
- "/libexec/opensmtpd/filter-dkimsign")
- " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
- 5
- "/etc/dkim/private.key "
- "user nobody group nogroup"))
- (exec #t)))
- (test-error "Test <filter-phase> fieldname 'phase' has an wrong phase name."
- #t
- (opensmtpd-filter-phase
- (name "filter")
- (phase "wrongString")
- (decision "bypass")
- (options
- (list
- (opensmtpd-option
- (option "auth"))))))
- (test-error
- "(filter-phase (decision \"reject\")) requires a 'message'."
- #t
- (opensmtpd-interface
- (filters (list
- (opensmtpd-filter-phase
- (name "src")
- (phase "connect")
- (options
- (list
- (opensmtpd-option
- (option "src")
- (data (opensmtpd-table
- (name "src-table")
- (data (list "cat" "hat")))))))
- (decision "reject"))))))
- (test-error (string-append
- "Test <filter-phase> fieldname 'decision' "
- "w/ value \"reject\" and \"disconnect\" requires a 'message'."
- " The message must begin with 4xx or 5xx.")
- #t
- (opensmtpd-interface
- (filters (list
- (opensmtpd-filter-phase
- (name "src")
- (phase "connect")
- (options
- (list
- (opensmtpd-option
- (option "src")
- (data (opensmtpd-table
- (name "src-table")
- (data (list "cat" "hat")))))))
- (decision "reject")
- (message "322 Bad data!"))))))
- (test-error
- "(filter-phase \"rewrite\") requires fieldname 'value' to have a number."
- #t
- (opensmtpd-interface
- (filters
- (list
- (opensmtpd-filter-phase
- (name "noFRDNS")
- (phase "commit")
- (options (list (opensmtpd-option
- (option "fcrdns")
- (bool #f))))
- (decision "rewrite"))))))
- (define option-string1/2 (list-ref (list "junk" "bypass")
- (random 2)))
- (test-error
- (string-append "(filter-phase \"decision\" with values 'junk' or 'bypass',"
- " then fieldname 'message' and 'value' must be blank.")
- #t
- (opensmtpd-interface
- (filters
- (list
- (opensmtpd-filter-phase
- (name "noFRDNS")
- (phase "commit")
- (options (list (opensmtpd-option
- (option "fcrdns")
- )))
- (decision option-string1/2)
- (message "This is not a good email."))))))
- (test-error "You cannot junk an email on phase commit."
- #t
- (opensmtpd-interface
- (filters
- (list
- (opensmtpd-filter-phase
- (name "junk-after-commit")
- (options (list (opensmtpd-option
- (option "fcrdns"))))
- (phase "commit")
- (decision "junk"))))))
- (define (good-interface1)
- (opensmtpd-interface
- (senders
- (opensmtpd-table
- (name "senders")
- (data '(("joshua" . "joshua@dismail.de")))))
- (masquerade #t)))
- (test-assert "good interface" (test-good-record good-interface1))
- (test-error "Test <filter-phase> has 2 duplicate options."
- #t
- (opensmtpd-filter-phase
- (name "invalid-fcrdns")
- (phase "connect")
- (options
- (list (opensmtpd-option
- (option "fcrdns"))
- (opensmtpd-option
- (option "fcrdns"))))
- (decision "reject")
- (message "422 No valid fcrdns.")))
- (test-error "Test <filter-phase> option 'src' requires a table."
- #t
- (opensmtpd-filter-phase
- (name "filter")
- (phase "helo")
- (decision "bypass")
- (options
- (list
- (opensmtpd-option
- (option "src"))))))
- (test-error "Test <filter-phase> option 'fcrdns' cannot have a table."
- #t
- (opensmtpd-filter-phase
- (name "filter")
- (phase "helo")
- (decision "bypass")
- (options
- (list
- (opensmtpd-option
- (option "fcrdns")
- (data (opensmtpd-table
- (name "table")
- (data (list "hello" "cat")))))))))
- (test-error "Test <filter-phase> must have at least one option."
- #t
- (opensmtpd-filter-phase
- (name "filter")
- (phase "helo")
- (decision "bypass")))
- (test-error
- "(filter-phase (options ...)) must be a list of <opensmtpd-option>s."
- #t
- (opensmtpd-filter-phase
- (name "rdns")
- (decision "junk")
- (phase "helo")
- (options
- (list 5))))
- (test-error "Test (decision \"junks\") is invalid."
- #t
- (opensmtpd-filter-phase
- (name "this")
- (options (list
- (opensmtpd-option
- (option "auth"))))
- (phase "helo")
- (decision "junks")))
- (test-error
- (string-append
- "(local-delivery (virtual ...) must be an <opensmtp-table> "
- "whose 'data' is an alist.")
- #t
- (opensmtpd-local-delivery
- (name "receive")
- (method (opensmtpd-maildir
- (pathname "/home/%{rcpt.user}/Maildir")))
- (virtual (opensmtpd-table
- (name "virt")
- (data (list "jbranso@dismail.de"))))))
- (test-error "(opensmtpd-match (options ...)) should not be quoted."
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options
- '((opensmtpd-option
- (option "for any"))))))
- (test-error "Test <opensmtpd-match> has duplicate 'for' options."
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option "for any"))
- (opensmtpd-option
- (option "for local"))))))
- (test-error "Test <opensmtpd-match> has duplicate 'from' options."
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option "from any"))
- (opensmtpd-option
- (option "from auth"))))))
- (define option-string1
- (list-ref (list "helo" "rcpt-to" "mail-from")
- (random 3)))
- ;; rcpt-to, mail-from, and helo must have a data field.
- (test-error (string-append "Test (opensmtpd-option (option \""
- option-string1
- "\")) must also define fieldname 'data'.")
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option option-string1))))))
- (define option-string2
- (list-ref (list "for local" "for any" "from any"
- "from local" "from socket" "tls")
- (random 6)))
- ;; "for local" "for any" "from any" "from local" "from socket" "tls"
- ;; cannot have data or regex defined
- (test-error (string-append "Test (opensmtpd-option (option \""
- option-string2
- "\")) cannot have fieldname 'data' defined.")
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option option-string2)
- (regex #t))))))
- (test-error "(opensmtpd-match (action ...)) needs to be defined."
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (option "from auth"))))))
- (test-error "(opensmtpd-match (options ...)) has duplicate 'helo's."
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option "helo")
- (bool #f))
- (opensmtpd-option
- (option "helo"))))))
- (test-error "(opensmtpd-match (options ...)) has duplicate 'mail-from's."
- #t
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list
- (opensmtpd-option
- (option "mail-from")
- (data "hello"))
- (opensmtpd-option
- (option "mail-from")
- (data "world"))))))
- (test-error
- "(opensmtpd-match (options ...)) has an invalid option name: fcrdns."
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (option "fcrdns"))))
- (action (opensmtpd-relay
- (name "relay")))))
- (test-error
- "(opensmtpd-match (options ...)) has an invalid option name: rdns."
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (option "rdns"))))
- (action (opensmtpd-relay
- (name "relay")))))
- (test-error
- (string-append
- "(opensmtpd-match (options ...)) option-name 'tag' must "
- "also have 'data' be a string.")
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (option "tag"))))
- (action (opensmtpd-relay
- (name "relay")))))
- (define option-string3
- (list-ref (list "for domain" "for rcpt-to"
- "from mail-from" "from src")
- (random 4)))
- ;; the options in this list:
- ;; (list "for domain" "for rcpt-to" "from mail-from" "from src")
- ;; must have a data field.
- (test-error
- (string-append "Test (opensmtpd-option (option \""
- option-string3 "\"))"
- " must define fieldname 'data'.")
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (option option-string3))))
- (action (opensmtpd-relay
- (name "relay")))))
- (define option-string4
- (list-ref (list "for local" "for any" "from any"
- "from local" "from socket" "tls")
- (random 6)))
- ;; the options in this list cannot have a data or regex field defined.
- ;; (list "for local" "for any" "from any" "from local" "from socket" "tls")
- (test-error (string-append "Test (opensmtpd-option (option \""
- option-string4 "\"))"
- " cannot define fieldname 'data'.")
- #t
- (opensmtpd-match
- (options (list
- (opensmtpd-option
- (regex #t)
- (option option-string4))))
- (action (opensmtpd-relay
- (name "relay")))))
- (define option-string5
- (list-ref (list "for domain" "for rcpt-to"
- "from mail-from" "from src")
- (random 4)))
- (test-error (string-append "Test (opensmtpd-option (option \""
- option-string5 "\"))"
- " must define fieldname 'data' as a string or "
- "an <opensmtpd-table>, whose 'data' \n"
- "is a list of strings.")
- #t
- (opensmtpd-match
- (options
- (list (opensmtpd-option
- (option option-string5)
- (data (opensmtpd-table
- (name "src-table")
- (data '(("127.0.0.1" . "374.394.405.23"))))))))
- (action (opensmtpd-relay
- (name "relay")))))
- ;; match must have at least one option.
- (test-error
- "(opensmtpd-match (options ...)) must have at least one <opensmtpd-option>."
- #t
- (opensmtpd-match
- (action
- (opensmtpd-local-delivery
- (name "mail")))))
- ;; you cannot have strings of length 0.
- (test-error
- (string-append
- "(opensmtpd-table (name \"table\") (data '((\"james\" . \"\")))) "
- "has an empty string.")
- #t
- (opensmtpd-table
- (name "mytable")
- (data '(("hello" . "")))))
- (define good-match1
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list (opensmtpd-option
- (option "for any"))
- (opensmtpd-option
- (option "from any"))
- (opensmtpd-option
- (option "auth"))))))
- (test-assert "good match" (test-good-record good-match1))
- ;;(test-assert "Test <opensmtpd-match> is valid.")
- ;; I used to have some code that assumed if you have
- ;; an "<interface>" and an "<socket>" using the same filter, then that
- ;; was a misconfiguration. It's not.
- (define (good-opensmtpd-configuration1)
- (let ([interface "lo"]
- [filter-dkimsign
- (opensmtpd-filter
- (name "dkimsign")
- (exec #t)
- (proc (list (file-append opensmtpd-filter-dkimsign
- "/libexec/opensmtpd/filter-dkimsign")
- " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
- "/etc/dkim/private.key "
- "user nobody group nogroup")))])
- (opensmtpd-configuration
- (interfaces
- (list
- ;; send out emails and be sure to dkimsign them.
- (opensmtpd-interface
- (interface interface)
- (filters (list filter-dkimsign)))))
- (socket
- (opensmtpd-socket
- (filters (list filter-dkimsign))))
- (matches (list
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list (opensmtpd-option
- (option "for any"))
- (opensmtpd-option
- (option "from any"))
- (opensmtpd-option
- (option "auth"))))))))))
- (test-assert
- (string-append "opensmtpd-configuration may use the same dkimsign "
- "filter on <opensmtpd-socket> and <opensmtpd-interface>.")
- (test-good-record good-opensmtpd-configuration1))
- ;; this is just the largest configuration that I can test.
- (define (good-opensmtpd-configuration2)
- (let ([interface "lo"]
- [creds-table
- (opensmtpd-table
- (name "creds")
- (data
- (list
- (cons "joshua"
- "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))]
- [receive-action
- (opensmtpd-local-delivery
- (name "receive")
- (method (opensmtpd-maildir
- (pathname "/home/%{rcpt.user}/Maildir")
- (junk #t)))
- (alias (opensmtpd-table
- (name "aliases")
- (data '(("joshua@gnucode.me" . "joshua")))))
- (virtual (opensmtpd-table
- (name "virt")
- (data '(("josh"
- . "jbranso@dismail.de"))))))]
- ;; as of 7-24-22 this proc fieldname does not actually work, but
- ;; is proper syntax.
- [filter-dkimsign (opensmtpd-filter
- (name "dkimsign")
- (exec #t)
- (proc (list
- (file-append
- opensmtpd-filter-dkimsign
- "/libexec/opensmtpd/filter-dkimsign")
- " -d gnucode.me -s 2021-09-22 -c "
- "relaxed/relaxed -k "
- "/etc/dkim/private.key "
- "user nobody group nogroup")))]
- [filter-invalid-fcrdns (opensmtpd-filter-phase
- (name "invalid-fcrdns")
- (phase "connect")
- (options
- (list (opensmtpd-option
- (option "fcrdns")
- (bool #f))))
- (decision "reject")
- (message "422 No valid fcrdns."))]
- [filter-invalid-rdns (opensmtpd-filter-phase
- (name "invalid-rdns")
- (phase "connect")
- (options
- (list (opensmtpd-option
- (option "rdns")
- (bool #f))))
- (decision "junk"))]
- [smtp.gnucode.me (opensmtpd-pki
- (domain "smtp.gnucode.me")
- (cert "guix.scm")
- (key "guix.scm"))])
- (opensmtpd-configuration
- (mta-max-deferred 50)
- (queue
- (opensmtpd-queue
- (compression #t)))
- (smtp
- (opensmtpd-smtp
- (max-message-size "10M")))
- (srs
- (opensmtpd-srs
- (ttl-delay "5d")))
- (interfaces
- (list
- (opensmtpd-interface
- (interface interface)
- (port 25)
- (secure-connection "tls")
- (filters (list filter-invalid-fcrdns
- filter-invalid-rdns))
- (pki smtp.gnucode.me))
- ;; this lets local users logged into the system via ssh send email
- ;; be sure to dkimsign them.
- (opensmtpd-interface
- (interface interface)
- (port 465)
- (secure-connection "smtps")
- (pki smtp.gnucode.me)
- (auth creds-table)
- (filters (list filter-dkimsign)))
- ;; if you uncomment this next line, then you get issues.
- ;;(opensmtpd-socket
- ;; (filters (list filter-dkimsign)))
- ;; send out emails and be sure to dkimsign them.
- (opensmtpd-interface
- (interface interface)
- (port 587)
- (secure-connection "tls-require")
- (pki smtp.gnucode.me)
- (auth creds-table)
- (filters (list filter-dkimsign)))))
- (socket
- (opensmtpd-socket
- (filters (list filter-dkimsign))
- (tag "socket")))
- (matches (list
- (opensmtpd-match
- (action (opensmtpd-relay
- (name "relay")))
- (options (list (opensmtpd-option
- (option "for any"))
- (opensmtpd-option
- (option "from any"))
- (opensmtpd-option
- (option "auth")))))
- (opensmtpd-match
- (action receive-action)
- (options (list (opensmtpd-option
- (option "from any"))
- (opensmtpd-option
- (option "for domain")
- (data (opensmtpd-table
- (name "domain-table")
- (data (list "gnucode.me"
- "gnu-hurd.com"))))))))
- (opensmtpd-match
- (action receive-action)
- (options (list (opensmtpd-option
- (option "for local"))))))))))
- (test-assert "Test my largish example <opensmtpd-configuration>."
- (test-good-record good-opensmtpd-configuration2))
- ;; the matches have two actions with the same name,
- ;; but are different actions.
- (test-error
- (string-append "Test <opensmtpd-configuration> fieldname 'matches' has "
- "two actions with the same name, but the actions are "
- "different.")
- #t
- (opensmtpd-configuration
- (matches
- (list (opensmtpd-match
- (options
- (list
- (opensmtpd-option
- (option "auth"))))
- (action
- (opensmtpd-local-delivery
- (name "my-local-delivery")
- (ttl "50m"))))
- (opensmtpd-match
- (options
- (list
- (opensmtpd-option
- (option "auth"))))
- (action
- (opensmtpd-local-delivery
- (name "my-local-delivery")
- (ttl "50h"))))))))
- ;; you can only have 1 opensmtpd-socket.
- (test-error
- (string-append
- "(opensmtpd-configuration> (listen-on ...)) may only have "
- "one <opensmtpd-socket>.")
- #t
- (let ([interface "lo"])
- (opensmtpd-configuration
- (socket
- (list
- (opensmtpd-socket)
- (opensmtpd-socket)))
- (matches (list
- (opensmtpd-match
- (options
- (list
- (opensmtpd-option
- (option "auth"))))
- (action (opensmtpd-relay
- (name "relay")))))))))
- (test-end "run-opensmtpd-record-sanitation-test"))
- (define %test-opensmtpd-record-sanitation
- (system-test
- (name "opensmtpdRecordSanitation")
- (description
- (string-append "<opensmtpd> has numerous sanity checks.\n"
- "This checks that invalid configurations, return an\n"
- "appropriate error.\n"))
- (value (run-opensmtpd-record-sanitation-test))))
- (define %exim-os
- (simple-operating-system
- (service dhcp-client-service-type)
- (service mail-aliases-service-type '())
- (service exim-service-type
- (exim-configuration
- (config-file
- (plain-file "exim.conf" "
- primary_hostname = komputilo
- domainlist local_domains = @
- domainlist relay_to_domains =
- hostlist relay_from_hosts = localhost
- never_users =
- acl_smtp_rcpt = acl_check_rcpt
- acl_smtp_data = acl_check_data
- begin acl
- acl_check_rcpt:
- accept
- acl_check_data:
- accept
- "))))))
- (define (run-exim-test)
- "Return a test of an OS running an Exim service."
- (define vm
- (virtual-machine
- (operating-system (marionette-operating-system
- %exim-os
- #:imported-modules '((gnu services herd))))
- (port-forwardings '((1025 . 25)))))
- (define test
- (with-imported-modules '((gnu build marionette))
- #~(begin
- (use-modules (rnrs base)
- (srfi srfi-64)
- (ice-9 ftw)
- (ice-9 rdelim)
- (ice-9 regex)
- (gnu build marionette))
- (define marionette
- (make-marionette '(#$vm)))
- (define (read-reply-code port)
- "Read a SMTP reply from PORT and return its reply code."
- (let* ((line (read-line port))
- (mo (string-match "([0-9]+)([ -]).*" line))
- (code (string->number (match:substring mo 1)))
- (finished? (string= " " (match:substring mo 2))))
- (if finished?
- code
- (read-reply-code port))))
- (define smtp (socket AF_INET SOCK_STREAM 0))
- (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
- (test-runner-current (system-test-runner #$output))
- (test-begin "exim")
- (test-assert "service is running"
- (marionette-eval
- '(begin
- (use-modules (gnu services herd))
- (start-service 'exim))
- marionette))
- (sleep 1) ;; give the service time to start talking
- (connect smtp addr)
- ;; Be greeted.
- (test-eq "greeting received"
- 220 (read-reply-code smtp))
- ;; Greet the server.
- (write-line "EHLO somehost" smtp)
- (test-eq "greeting successful"
- 250 (read-reply-code smtp))
- ;; Set sender email.
- (write-line "MAIL FROM: test@example.com" smtp)
- (test-eq "sender set"
- 250 (read-reply-code smtp)) ;250
- ;; Set recipient email.
- (write-line "RCPT TO: root@komputilo" smtp)
- (test-eq "recipient set"
- 250 (read-reply-code smtp)) ;250
- ;; Send message.
- (write-line "DATA" smtp)
- (test-eq "data begun"
- 354 (read-reply-code smtp)) ;354
- (write-line "Subject: Hello" smtp)
- (newline smtp)
- (write-line "Nice to meet you!" smtp)
- (write-line "." smtp)
- (test-eq "message sent"
- 250 (read-reply-code smtp)) ;250
- ;; Say goodbye.
- (write-line "QUIT" smtp)
- (test-eq "quit successful"
- 221 (read-reply-code smtp)) ;221
- (close smtp)
- (test-eq "the email is received"
- 1
- (marionette-eval
- '(begin
- (use-modules (ice-9 ftw))
- (length (scandir "/var/spool/exim/msglog"
- (lambda (x) (not (string-prefix? "." x))))))
- marionette))
- (test-end))))
- (gexp->derivation "exim-test" test))
- (define %test-exim
- (system-test
- (name "exim")
- (description "Send an email to a running an Exim server.")
- (value (run-exim-test))))
- (define %dovecot-os
- (simple-operating-system
- (service dhcp-client-service-type)
- (dovecot-service #:config
- (dovecot-configuration
- (disable-plaintext-auth? #f)
- (ssl? "no")
- (auth-mechanisms '("anonymous"))
- (auth-anonymous-username "alice")
- (mail-location
- (string-append "maildir:~/Maildir"
- ":INBOX=~/Maildir/INBOX"
- ":LAYOUT=fs"))))))
- (define (run-dovecot-test)
- "Return a test of an OS running Dovecot service."
- (define vm
- (virtual-machine
- (operating-system (marionette-operating-system
- %dovecot-os
- #:imported-modules '((gnu services herd))))
- (port-forwardings '((8143 . 143)))))
- (define test
- (with-imported-modules '((gnu build marionette))
- #~(begin
- (use-modules (gnu build marionette)
- (ice-9 iconv)
- (ice-9 rdelim)
- (rnrs base)
- (rnrs bytevectors)
- (srfi srfi-64))
- (define marionette
- (make-marionette '(#$vm)))
- (define* (message-length message #:key (encoding "iso-8859-1"))
- (bytevector-length (string->bytevector message encoding)))
- (define message "From: test@example.com\n\
- Subject: Hello Nice to meet you!")
- (test-runner-current (system-test-runner #$output))
- (test-begin "dovecot")
- ;; Wait for dovecot to be up and running.
- (test-assert "dovecot running"
- (marionette-eval
- '(begin
- (use-modules (gnu services herd))
- (start-service 'dovecot))
- marionette))
- ;; Check Dovecot service's PID.
- (test-assert "service process id"
- (let ((pid
- (number->string (wait-for-file "/var/run/dovecot/master.pid"
- marionette))))
- (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
- marionette)))
- (test-assert "accept an email"
- (let ((imap (socket AF_INET SOCK_STREAM 0))
- (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
- (connect imap addr)
- ;; Be greeted.
- (read-line imap) ;OK
- ;; Authenticate
- (write-line "a AUTHENTICATE ANONYMOUS" imap)
- (read-line imap) ;+
- (write-line "c2lyaGM=" imap)
- (read-line imap) ;OK
- ;; Create a TESTBOX mailbox
- (write-line "a CREATE TESTBOX" imap)
- (read-line imap) ;OK
- ;; Append a message to a TESTBOX mailbox
- (write-line (format #f "a APPEND TESTBOX {~a}"
- (number->string (message-length message)))
- imap)
- (read-line imap) ;+
- (write-line message imap)
- (read-line imap) ;OK
- ;; Logout
- (write-line "a LOGOUT" imap)
- (close imap)
- #t))
- (test-equal "mail arrived"
- message
- (marionette-eval
- '(begin
- (use-modules (ice-9 ftw)
- (ice-9 match))
- (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
- (match (scandir TESTBOX/new)
- (("." ".." message-file)
- (call-with-input-file
- (string-append TESTBOX/new message-file)
- get-string-all)))))
- marionette))
- (test-end))))
- (gexp->derivation "dovecot-test" test))
- (define %test-dovecot
- (system-test
- (name "dovecot")
- (description "Connect to a running Dovecot server.")
- (value (run-dovecot-test))))
- (define %getmail-os
- (operating-system
- (inherit (simple-operating-system))
- ;; Set a password for the user account; the test needs it.
- (users (cons (user-account
- (name "alice")
- (password (crypt "testpass" "$6$abc"))
- (comment "Bob's sister")
- (group "users")
- (supplementary-groups '("wheel" "audio" "video")))
- %base-user-accounts))
- (services (cons* (service dhcp-client-service-type)
- (service dovecot-service-type
- (dovecot-configuration
- (disable-plaintext-auth? #f)
- (ssl? "no")
- (auth-mechanisms '("anonymous" "plain"))
- (auth-anonymous-username "alice")
- (mail-location
- (string-append "maildir:~/Maildir"
- ":INBOX=~/Maildir/INBOX"
- ":LAYOUT=fs"))))
- (service getmail-service-type
- (list
- (getmail-configuration
- (name 'test)
- (user "alice")
- (directory "/var/lib/getmail/alice")
- (idle '("TESTBOX"))
- (rcfile
- (getmail-configuration-file
- (retriever
- (getmail-retriever-configuration
- (type "SimpleIMAPRetriever")
- (server "localhost")
- (username "alice")
- (port 143)
- (extra-parameters
- '((password . "testpass")
- (mailboxes . ("TESTBOX"))))))
- (destination
- (getmail-destination-configuration
- (type "Maildir")
- (path "/home/alice/TestMaildir/")))
- (options
- (getmail-options-configuration
- (read-all #f))))))))
- %base-services))))
- (define (run-getmail-test)
- "Return a test of an OS running Getmail service."
- (define vm
- (virtual-machine
- (operating-system (marionette-operating-system
- %getmail-os
- #:imported-modules '((gnu services herd))))
- (port-forwardings '((8143 . 143)))))
- (define test
- (with-imported-modules '((gnu build marionette))
- #~(begin
- (use-modules (gnu build marionette)
- (ice-9 iconv)
- (ice-9 rdelim)
- (rnrs base)
- (rnrs bytevectors)
- (srfi srfi-64))
- (define marionette
- (make-marionette '(#$vm)))
- (define* (message-length message #:key (encoding "iso-8859-1"))
- (bytevector-length (string->bytevector message encoding)))
- (define message "From: test@example.com\n\
- Subject: Hello Nice to meet you!")
- (test-runner-current (system-test-runner #$output))
- (test-begin "getmail")
- ;; Wait for dovecot to be up and running.
- (test-assert "dovecot running"
- (marionette-eval
- '(begin
- (use-modules (gnu services herd))
- (start-service 'dovecot))
- marionette))
- ;; Wait for getmail to be up and running.
- (test-assert "getmail-test running"
- (marionette-eval
- '(let* ((pw (getpw "alice"))
- (uid (passwd:uid pw))
- (gid (passwd:gid pw)))
- (use-modules (gnu services herd))
- (for-each
- (lambda (dir)
- (mkdir dir)
- (chown dir uid gid))
- '("/home/alice/TestMaildir"
- "/home/alice/TestMaildir/cur"
- "/home/alice/TestMaildir/new"
- "/home/alice/TestMaildir/tmp"
- "/home/alice/TestMaildir/TESTBOX"
- "/home/alice/TestMaildir/TESTBOX/cur"
- "/home/alice/TestMaildir/TESTBOX/new"
- "/home/alice/TestMaildir/TESTBOX/tmp"))
- (start-service 'getmail-test))
- marionette))
- ;; Check Dovecot service's PID.
- (test-assert "service process id"
- (let ((pid
- (number->string (wait-for-file "/var/run/dovecot/master.pid"
- marionette))))
- (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
- marionette)))
- (test-assert "accept an email"
- (let ((imap (socket AF_INET SOCK_STREAM 0))
- (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
- (connect imap addr)
- ;; Be greeted.
- (read-line imap) ;OK
- ;; Authenticate
- (write-line "a AUTHENTICATE ANONYMOUS" imap)
- (read-line imap) ;+
- (write-line "c2lyaGM=" imap)
- (read-line imap) ;OK
- ;; Create a TESTBOX mailbox
- (write-line "a CREATE TESTBOX" imap)
- (read-line imap) ;OK
- ;; Append a message to a TESTBOX mailbox
- (write-line (format #f "a APPEND TESTBOX {~a}"
- (number->string (message-length message)))
- imap)
- (read-line imap) ;+
- (write-line message imap)
- (read-line imap) ;OK
- ;; Logout
- (write-line "a LOGOUT" imap)
- (close imap)
- #t))
- (sleep 1)
- (test-assert "mail arrived"
- (string-contains
- (marionette-eval
- '(begin
- (use-modules (ice-9 ftw)
- (ice-9 match))
- (let ((TESTBOX/new "/home/alice/TestMaildir/new/"))
- (match (scandir TESTBOX/new)
- (("." ".." message-file)
- (call-with-input-file
- (string-append TESTBOX/new message-file)
- get-string-all)))))
- marionette)
- message))
- (test-end))))
- (gexp->derivation "getmail-test" test))
- (define %test-getmail
- (system-test
- (name "getmail")
- (description "Connect to a running Getmail server.")
- (value (run-getmail-test))))
|