1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016 Sou Bunnbu <iyzsong@member.fsf.org>
3 ;;; Copyright © 2017 Carlo Zancanaro <carlo@zancanaro.id.au>
4 ;;; Copyright © 2017, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
5 ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
6 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
7 ;;; Copyright © 2019 Christopher Baines <mail@cbaines.net>
8 ;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
10 ;;; This file is part of GNU Guix.
12 ;;; GNU Guix is free software; you can redistribute it and/or modify it
13 ;;; under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 3 of the License, or (at
15 ;;; your option) any later version.
17 ;;; GNU Guix is distributed in the hope that it will be useful, but
18 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
25 (define-module (gnu tests mail)
26 #:use-module (gnu tests)
27 #:use-module (gnu packages mail)
28 #:use-module (gnu system)
29 #:use-module (gnu system accounts)
30 #:use-module (gnu system shadow)
31 #:use-module (gnu system vm)
32 #:use-module (gnu services)
33 #:use-module (gnu services base)
34 #:use-module (gnu services getmail)
35 #:use-module (gnu services mail)
36 #:use-module (gnu services networking)
37 #:use-module (guix gexp)
38 #:use-module (guix store)
39 #:use-module (ice-9 ftw)
40 #:export (%test-opensmtpd
46 (simple-operating-system
47 (service dhcp-client-service-type)
48 (service opensmtpd-service-type
49 (opensmtpd-configuration
51 (plain-file "smtpd.conf" "
54 match from any for local action inbound
57 (define (run-opensmtpd-test)
58 "Return a test of an OS running OpenSMTPD service."
61 (operating-system (marionette-operating-system
63 #:imported-modules '((gnu services herd))))
64 (port-forwardings '((1025 . 25)))))
67 (with-imported-modules '((gnu build marionette))
69 (use-modules (rnrs base)
73 (gnu build marionette))
76 (make-marionette '(#$vm)))
78 (define (read-reply-code port)
79 "Read a SMTP reply from PORT and return its reply code."
80 (let* ((line (read-line port))
81 (mo (string-match "([0-9]+)([ -]).*" line))
82 (code (string->number (match:substring mo 1)))
83 (finished? (string= " " (match:substring mo 2))))
86 (read-reply-code port))))
88 (test-runner-current (system-test-runner #$output))
89 (test-begin "opensmptd")
91 (test-assert "service is running"
94 (use-modules (gnu services herd))
95 (start-service 'smtpd))
98 (test-assert "mbox is empty"
100 '(and (file-exists? "/var/spool/mail")
101 (not (file-exists? "/var/spool/mail/root")))
104 (test-eq "accept an email"
106 (let* ((smtp (socket AF_INET SOCK_STREAM 0))
107 (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
110 (read-reply-code smtp) ;220
112 (write-line "EHLO somehost" smtp)
113 (read-reply-code smtp) ;250
115 (write-line "MAIL FROM: <someone>" smtp)
116 (read-reply-code smtp) ;250
117 ;; Set recipient email.
118 (write-line "RCPT TO: <root>" smtp)
119 (read-reply-code smtp) ;250
121 (write-line "DATA" smtp)
122 (read-reply-code smtp) ;354
123 (write-line "Subject: Hello" smtp)
125 (write-line "Nice to meet you!" smtp)
126 (write-line "." smtp)
127 (read-reply-code smtp) ;250
129 (write-line "QUIT" smtp)
130 (read-reply-code smtp) ;221
134 (test-assert "mail arrived"
137 (use-modules (ice-9 popen)
140 (define (queue-empty?)
141 (let* ((pipe (open-pipe* OPEN_READ
142 #$(file-append opensmtpd
145 (line (read-line pipe)))
150 (cond ((queue-empty?)
151 (file-exists? "/var/spool/mail/root"))
153 (error "root mailbox didn't show up"))
155 (sleep 1) (wait (- n 1))))))
160 (gexp->derivation "opensmtpd-test" test))
162 (define %test-opensmtpd
165 (description "Send an email to a running OpenSMTPD server.")
166 (value (run-opensmtpd-test))))
170 (simple-operating-system
171 (service dhcp-client-service-type)
172 (service mail-aliases-service-type '())
173 (service exim-service-type
176 (plain-file "exim.conf" "
177 primary_hostname = komputilo
178 domainlist local_domains = @
179 domainlist relay_to_domains =
180 hostlist relay_from_hosts = localhost
184 acl_smtp_rcpt = acl_check_rcpt
185 acl_smtp_data = acl_check_data
195 (define (run-exim-test)
196 "Return a test of an OS running an Exim service."
199 (operating-system (marionette-operating-system
201 #:imported-modules '((gnu services herd))))
202 (port-forwardings '((1025 . 25)))))
205 (with-imported-modules '((gnu build marionette))
207 (use-modules (rnrs base)
212 (gnu build marionette))
215 (make-marionette '(#$vm)))
217 (define (read-reply-code port)
218 "Read a SMTP reply from PORT and return its reply code."
219 (let* ((line (read-line port))
220 (mo (string-match "([0-9]+)([ -]).*" line))
221 (code (string->number (match:substring mo 1)))
222 (finished? (string= " " (match:substring mo 2))))
225 (read-reply-code port))))
227 (define smtp (socket AF_INET SOCK_STREAM 0))
228 (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
230 (test-runner-current (system-test-runner #$output))
233 (test-assert "service is running"
236 (use-modules (gnu services herd))
237 (start-service 'exim))
240 (sleep 1) ;; give the service time to start talking
244 (test-eq "greeting received"
245 220 (read-reply-code smtp))
247 (write-line "EHLO somehost" smtp)
248 (test-eq "greeting successful"
249 250 (read-reply-code smtp))
251 (write-line "MAIL FROM: test@example.com" smtp)
252 (test-eq "sender set"
253 250 (read-reply-code smtp)) ;250
254 ;; Set recipient email.
255 (write-line "RCPT TO: root@komputilo" smtp)
256 (test-eq "recipient set"
257 250 (read-reply-code smtp)) ;250
259 (write-line "DATA" smtp)
260 (test-eq "data begun"
261 354 (read-reply-code smtp)) ;354
262 (write-line "Subject: Hello" smtp)
264 (write-line "Nice to meet you!" smtp)
265 (write-line "." smtp)
266 (test-eq "message sent"
267 250 (read-reply-code smtp)) ;250
269 (write-line "QUIT" smtp)
270 (test-eq "quit successful"
271 221 (read-reply-code smtp)) ;221
274 (test-eq "the email is received"
278 (use-modules (ice-9 ftw))
279 (length (scandir "/var/spool/exim/msglog"
280 (lambda (x) (not (string-prefix? "." x))))))
285 (gexp->derivation "exim-test" test))
290 (description "Send an email to a running an Exim server.")
291 (value (run-exim-test))))
294 (simple-operating-system
295 (service dhcp-client-service-type)
296 (dovecot-service #:config
297 (dovecot-configuration
298 (disable-plaintext-auth? #f)
300 (auth-mechanisms '("anonymous"))
301 (auth-anonymous-username "alice")
303 (string-append "maildir:~/Maildir"
304 ":INBOX=~/Maildir/INBOX"
307 (define (run-dovecot-test)
308 "Return a test of an OS running Dovecot service."
311 (operating-system (marionette-operating-system
313 #:imported-modules '((gnu services herd))))
314 (port-forwardings '((8143 . 143)))))
317 (with-imported-modules '((gnu build marionette))
319 (use-modules (gnu build marionette)
327 (make-marionette '(#$vm)))
329 (define* (message-length message #:key (encoding "iso-8859-1"))
330 (bytevector-length (string->bytevector message encoding)))
332 (define message "From: test@example.com\n\
333 Subject: Hello Nice to meet you!")
335 (test-runner-current (system-test-runner #$output))
336 (test-begin "dovecot")
338 ;; Wait for dovecot to be up and running.
339 (test-assert "dovecot running"
342 (use-modules (gnu services herd))
343 (start-service 'dovecot))
346 ;; Check Dovecot service's PID.
347 (test-assert "service process id"
349 (number->string (wait-for-file "/var/run/dovecot/master.pid"
351 (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
354 (test-assert "accept an email"
355 (let ((imap (socket AF_INET SOCK_STREAM 0))
356 (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
361 (write-line "a AUTHENTICATE ANONYMOUS" imap)
363 (write-line "c2lyaGM=" imap)
365 ;; Create a TESTBOX mailbox
366 (write-line "a CREATE TESTBOX" imap)
368 ;; Append a message to a TESTBOX mailbox
369 (write-line (format #f "a APPEND TESTBOX {~a}"
370 (number->string (message-length message)))
373 (write-line message imap)
376 (write-line "a LOGOUT" imap)
380 (test-equal "mail arrived"
384 (use-modules (ice-9 ftw)
386 (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
387 (match (scandir TESTBOX/new)
388 (("." ".." message-file)
389 (call-with-input-file
390 (string-append TESTBOX/new message-file)
396 (gexp->derivation "dovecot-test" test))
398 (define %test-dovecot
401 (description "Connect to a running Dovecot server.")
402 (value (run-dovecot-test))))
406 (inherit (simple-operating-system))
408 ;; Set a password for the user account; the test needs it.
409 (users (cons (user-account
411 (password (crypt "testpass" "$6$abc"))
412 (comment "Bob's sister")
414 (supplementary-groups '("wheel" "audio" "video")))
415 %base-user-accounts))
417 (services (cons* (service dhcp-client-service-type)
418 (service dovecot-service-type
419 (dovecot-configuration
420 (disable-plaintext-auth? #f)
422 (auth-mechanisms '("anonymous" "plain"))
423 (auth-anonymous-username "alice")
425 (string-append "maildir:~/Maildir"
426 ":INBOX=~/Maildir/INBOX"
428 (service getmail-service-type
430 (getmail-configuration
433 (directory "/var/lib/getmail/alice")
436 (getmail-configuration-file
438 (getmail-retriever-configuration
439 (type "SimpleIMAPRetriever")
444 '((password . "testpass")
445 (mailboxes . ("TESTBOX"))))))
447 (getmail-destination-configuration
449 (path "/home/alice/TestMaildir/")))
451 (getmail-options-configuration
455 (define (run-getmail-test)
456 "Return a test of an OS running Getmail service."
459 (operating-system (marionette-operating-system
461 #:imported-modules '((gnu services herd))))
462 (port-forwardings '((8143 . 143)))))
465 (with-imported-modules '((gnu build marionette))
467 (use-modules (gnu build marionette)
475 (make-marionette '(#$vm)))
477 (define* (message-length message #:key (encoding "iso-8859-1"))
478 (bytevector-length (string->bytevector message encoding)))
480 (define message "From: test@example.com\n\
481 Subject: Hello Nice to meet you!")
483 (test-runner-current (system-test-runner #$output))
484 (test-begin "getmail")
486 ;; Wait for dovecot to be up and running.
487 (test-assert "dovecot running"
490 (use-modules (gnu services herd))
491 (start-service 'dovecot))
494 ;; Wait for getmail to be up and running.
495 (test-assert "getmail-test running"
497 '(let* ((pw (getpw "alice"))
498 (uid (passwd:uid pw))
499 (gid (passwd:gid pw)))
500 (use-modules (gnu services herd))
506 '("/home/alice/TestMaildir"
507 "/home/alice/TestMaildir/cur"
508 "/home/alice/TestMaildir/new"
509 "/home/alice/TestMaildir/tmp"
510 "/home/alice/TestMaildir/TESTBOX"
511 "/home/alice/TestMaildir/TESTBOX/cur"
512 "/home/alice/TestMaildir/TESTBOX/new"
513 "/home/alice/TestMaildir/TESTBOX/tmp"))
515 (start-service 'getmail-test))
518 ;; Check Dovecot service's PID.
519 (test-assert "service process id"
521 (number->string (wait-for-file "/var/run/dovecot/master.pid"
523 (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
526 (test-assert "accept an email"
527 (let ((imap (socket AF_INET SOCK_STREAM 0))
528 (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
533 (write-line "a AUTHENTICATE ANONYMOUS" imap)
535 (write-line "c2lyaGM=" imap)
537 ;; Create a TESTBOX mailbox
538 (write-line "a CREATE TESTBOX" imap)
540 ;; Append a message to a TESTBOX mailbox
541 (write-line (format #f "a APPEND TESTBOX {~a}"
542 (number->string (message-length message)))
545 (write-line message imap)
548 (write-line "a LOGOUT" imap)
554 (test-assert "mail arrived"
558 (use-modules (ice-9 ftw)
560 (let ((TESTBOX/new "/home/alice/TestMaildir/new/"))
561 (match (scandir TESTBOX/new)
562 (("." ".." message-file)
563 (call-with-input-file
564 (string-append TESTBOX/new message-file)
571 (gexp->derivation "getmail-test" test))
573 (define %test-getmail
576 (description "Connect to a running Getmail server.")
577 (value (run-getmail-test))))