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 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))))
91 (test-begin "opensmptd")
93 (test-assert "service is running"
96 (use-modules (gnu services herd))
97 (start-service 'smtpd))
100 (test-assert "mbox is empty"
102 '(and (file-exists? "/var/spool/mail")
103 (not (file-exists? "/var/spool/mail/root")))
106 (test-eq "accept an email"
108 (let* ((smtp (socket AF_INET SOCK_STREAM 0))
109 (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
112 (read-reply-code smtp) ;220
114 (write-line "EHLO somehost" smtp)
115 (read-reply-code smtp) ;250
117 (write-line "MAIL FROM: <someone>" smtp)
118 (read-reply-code smtp) ;250
119 ;; Set recipient email.
120 (write-line "RCPT TO: <root>" smtp)
121 (read-reply-code smtp) ;250
123 (write-line "DATA" smtp)
124 (read-reply-code smtp) ;354
125 (write-line "Subject: Hello" smtp)
127 (write-line "Nice to meet you!" smtp)
128 (write-line "." smtp)
129 (read-reply-code smtp) ;250
131 (write-line "QUIT" smtp)
132 (read-reply-code smtp) ;221
136 (test-assert "mail arrived"
139 (use-modules (ice-9 popen)
142 (define (queue-empty?)
143 (let* ((pipe (open-pipe* OPEN_READ
144 #$(file-append opensmtpd
147 (line (read-line pipe)))
152 (cond ((queue-empty?)
153 (file-exists? "/var/spool/mail/root"))
155 (error "root mailbox didn't show up"))
157 (sleep 1) (wait (- n 1))))))
161 (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
163 (gexp->derivation "opensmtpd-test" test))
165 (define %test-opensmtpd
168 (description "Send an email to a running OpenSMTPD server.")
169 (value (run-opensmtpd-test))))
173 (simple-operating-system
174 (service dhcp-client-service-type)
175 (service mail-aliases-service-type '())
176 (service exim-service-type
179 (plain-file "exim.conf" "
180 primary_hostname = komputilo
181 domainlist local_domains = @
182 domainlist relay_to_domains =
183 hostlist relay_from_hosts = localhost
187 acl_smtp_rcpt = acl_check_rcpt
188 acl_smtp_data = acl_check_data
198 (define (run-exim-test)
199 "Return a test of an OS running an Exim service."
202 (operating-system (marionette-operating-system
204 #:imported-modules '((gnu services herd))))
205 (port-forwardings '((1025 . 25)))))
208 (with-imported-modules '((gnu build marionette)
211 (use-modules (rnrs base)
216 (gnu build marionette))
219 (make-marionette '(#$vm)))
221 (define (read-reply-code port)
222 "Read a SMTP reply from PORT and return its reply code."
223 (let* ((line (read-line port))
224 (mo (string-match "([0-9]+)([ -]).*" line))
225 (code (string->number (match:substring mo 1)))
226 (finished? (string= " " (match:substring mo 2))))
229 (read-reply-code port))))
231 (define smtp (socket AF_INET SOCK_STREAM 0))
232 (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
239 (test-assert "service is running"
242 (use-modules (gnu services herd))
243 (start-service 'exim))
246 (sleep 1) ;; give the service time to start talking
250 (test-eq "greeting received"
251 220 (read-reply-code smtp))
253 (write-line "EHLO somehost" smtp)
254 (test-eq "greeting successful"
255 250 (read-reply-code smtp))
257 (write-line "MAIL FROM: test@example.com" smtp)
258 (test-eq "sender set"
259 250 (read-reply-code smtp)) ;250
260 ;; Set recipient email.
261 (write-line "RCPT TO: root@komputilo" smtp)
262 (test-eq "recipient set"
263 250 (read-reply-code smtp)) ;250
265 (write-line "DATA" smtp)
266 (test-eq "data begun"
267 354 (read-reply-code smtp)) ;354
268 (write-line "Subject: Hello" smtp)
270 (write-line "Nice to meet you!" smtp)
271 (write-line "." smtp)
272 (test-eq "message sent"
273 250 (read-reply-code smtp)) ;250
275 (write-line "QUIT" smtp)
276 (test-eq "quit successful"
277 221 (read-reply-code smtp)) ;221
280 (test-eq "the email is received"
284 (use-modules (ice-9 ftw))
285 (length (scandir "/var/spool/exim/msglog"
286 (lambda (x) (not (string-prefix? "." x))))))
290 (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
292 (gexp->derivation "exim-test" test))
297 (description "Send an email to a running an Exim server.")
298 (value (run-exim-test))))
301 (simple-operating-system
302 (service dhcp-client-service-type)
303 (dovecot-service #:config
304 (dovecot-configuration
305 (disable-plaintext-auth? #f)
307 (auth-mechanisms '("anonymous"))
308 (auth-anonymous-username "alice")
310 (string-append "maildir:~/Maildir"
311 ":INBOX=~/Maildir/INBOX"
314 (define (run-dovecot-test)
315 "Return a test of an OS running Dovecot service."
318 (operating-system (marionette-operating-system
320 #:imported-modules '((gnu services herd))))
321 (port-forwardings '((8143 . 143)))))
324 (with-imported-modules '((gnu build marionette))
326 (use-modules (gnu build marionette)
334 (make-marionette '(#$vm)))
336 (define* (message-length message #:key (encoding "iso-8859-1"))
337 (bytevector-length (string->bytevector message encoding)))
339 (define message "From: test@example.com\n\
340 Subject: Hello Nice to meet you!")
345 (test-begin "dovecot")
347 ;; Wait for dovecot to be up and running.
348 (test-assert "dovecot running"
351 (use-modules (gnu services herd))
352 (start-service 'dovecot))
355 ;; Check Dovecot service's PID.
356 (test-assert "service process id"
358 (number->string (wait-for-file "/var/run/dovecot/master.pid"
360 (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
363 (test-assert "accept an email"
364 (let ((imap (socket AF_INET SOCK_STREAM 0))
365 (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
370 (write-line "a AUTHENTICATE ANONYMOUS" imap)
372 (write-line "c2lyaGM=" imap)
374 ;; Create a TESTBOX mailbox
375 (write-line "a CREATE TESTBOX" imap)
377 ;; Append a message to a TESTBOX mailbox
378 (write-line (format #f "a APPEND TESTBOX {~a}"
379 (number->string (message-length message)))
382 (write-line message imap)
385 (write-line "a LOGOUT" imap)
389 (test-equal "mail arrived"
393 (use-modules (ice-9 ftw)
395 (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
396 (match (scandir TESTBOX/new)
397 (("." ".." message-file)
398 (call-with-input-file
399 (string-append TESTBOX/new message-file)
404 (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
406 (gexp->derivation "dovecot-test" test))
408 (define %test-dovecot
411 (description "Connect to a running Dovecot server.")
412 (value (run-dovecot-test))))
416 (inherit (simple-operating-system))
418 ;; Set a password for the user account; the test needs it.
419 (users (cons (user-account
421 (password (crypt "testpass" "$6$abc"))
422 (comment "Bob's sister")
424 (supplementary-groups '("wheel" "audio" "video")))
425 %base-user-accounts))
427 (services (cons* (service dhcp-client-service-type)
428 (service dovecot-service-type
429 (dovecot-configuration
430 (disable-plaintext-auth? #f)
432 (auth-mechanisms '("anonymous" "plain"))
433 (auth-anonymous-username "alice")
435 (string-append "maildir:~/Maildir"
436 ":INBOX=~/Maildir/INBOX"
438 (service getmail-service-type
440 (getmail-configuration
443 (directory "/var/lib/getmail/alice")
446 (getmail-configuration-file
448 (getmail-retriever-configuration
449 (type "SimpleIMAPRetriever")
454 '((password . "testpass")
455 (mailboxes . ("TESTBOX"))))))
457 (getmail-destination-configuration
459 (path "/home/alice/TestMaildir/")))
461 (getmail-options-configuration
465 (define (run-getmail-test)
466 "Return a test of an OS running Getmail service."
469 (operating-system (marionette-operating-system
471 #:imported-modules '((gnu services herd))))
472 (port-forwardings '((8143 . 143)))))
475 (with-imported-modules '((gnu build marionette))
477 (use-modules (gnu build marionette)
485 (make-marionette '(#$vm)))
487 (define* (message-length message #:key (encoding "iso-8859-1"))
488 (bytevector-length (string->bytevector message encoding)))
490 (define message "From: test@example.com\n\
491 Subject: Hello Nice to meet you!")
496 (test-begin "getmail")
498 ;; Wait for dovecot to be up and running.
499 (test-assert "dovecot running"
502 (use-modules (gnu services herd))
503 (start-service 'dovecot))
506 ;; Wait for getmail to be up and running.
507 (test-assert "getmail-test running"
509 '(let* ((pw (getpw "alice"))
510 (uid (passwd:uid pw))
511 (gid (passwd:gid pw)))
512 (use-modules (gnu services herd))
518 '("/home/alice/TestMaildir"
519 "/home/alice/TestMaildir/cur"
520 "/home/alice/TestMaildir/new"
521 "/home/alice/TestMaildir/tmp"
522 "/home/alice/TestMaildir/TESTBOX"
523 "/home/alice/TestMaildir/TESTBOX/cur"
524 "/home/alice/TestMaildir/TESTBOX/new"
525 "/home/alice/TestMaildir/TESTBOX/tmp"))
527 (start-service 'getmail-test))
530 ;; Check Dovecot service's PID.
531 (test-assert "service process id"
533 (number->string (wait-for-file "/var/run/dovecot/master.pid"
535 (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
538 (test-assert "accept an email"
539 (let ((imap (socket AF_INET SOCK_STREAM 0))
540 (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
545 (write-line "a AUTHENTICATE ANONYMOUS" imap)
547 (write-line "c2lyaGM=" imap)
549 ;; Create a TESTBOX mailbox
550 (write-line "a CREATE TESTBOX" imap)
552 ;; Append a message to a TESTBOX mailbox
553 (write-line (format #f "a APPEND TESTBOX {~a}"
554 (number->string (message-length message)))
557 (write-line message imap)
560 (write-line "a LOGOUT" imap)
566 (test-assert "mail arrived"
570 (use-modules (ice-9 ftw)
572 (let ((TESTBOX/new "/home/alice/TestMaildir/new/"))
573 (match (scandir TESTBOX/new)
574 (("." ".." message-file)
575 (call-with-input-file
576 (string-append TESTBOX/new message-file)
582 (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
584 (gexp->derivation "getmail-test" test))
586 (define %test-getmail
589 (description "Connect to a running Getmail server.")
590 (value (run-getmail-test))))