mail.scm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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 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 Tobias Geerinckx-Rice <me@tobias.gr>
  9. ;;;
  10. ;;; This file is part of GNU Guix.
  11. ;;;
  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.
  16. ;;;
  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.
  21. ;;;
  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/>.
  24. (define-module (gnu tests mail)
  25. #:use-module (gnu tests)
  26. #:use-module (gnu packages mail)
  27. #:use-module (gnu system)
  28. #:use-module (gnu system vm)
  29. #:use-module (gnu services)
  30. #:use-module (gnu services getmail)
  31. #:use-module (gnu services mail)
  32. #:use-module (gnu services networking)
  33. #:use-module (guix gexp)
  34. #:use-module (guix store)
  35. #:use-module (ice-9 ftw)
  36. #:export (%test-opensmtpd
  37. %test-exim
  38. %test-dovecot
  39. %test-getmail))
  40. (define %opensmtpd-os
  41. (simple-operating-system
  42. (service dhcp-client-service-type)
  43. (service opensmtpd-service-type
  44. (opensmtpd-configuration
  45. (config-file
  46. (plain-file "smtpd.conf" "
  47. listen on 0.0.0.0
  48. accept from any for local deliver to mbox
  49. "))))))
  50. (define (run-opensmtpd-test)
  51. "Return a test of an OS running OpenSMTPD service."
  52. (define vm
  53. (virtual-machine
  54. (operating-system (marionette-operating-system
  55. %opensmtpd-os
  56. #:imported-modules '((gnu services herd))))
  57. (port-forwardings '((1025 . 25)))))
  58. (define test
  59. (with-imported-modules '((gnu build marionette))
  60. #~(begin
  61. (use-modules (rnrs base)
  62. (srfi srfi-64)
  63. (ice-9 rdelim)
  64. (ice-9 regex)
  65. (gnu build marionette))
  66. (define marionette
  67. (make-marionette '(#$vm)))
  68. (define (read-reply-code port)
  69. "Read a SMTP reply from PORT and return its reply code."
  70. (let* ((line (read-line port))
  71. (mo (string-match "([0-9]+)([ -]).*" line))
  72. (code (string->number (match:substring mo 1)))
  73. (finished? (string= " " (match:substring mo 2))))
  74. (if finished?
  75. code
  76. (read-reply-code port))))
  77. (mkdir #$output)
  78. (chdir #$output)
  79. (test-begin "opensmptd")
  80. (test-assert "service is running"
  81. (marionette-eval
  82. '(begin
  83. (use-modules (gnu services herd))
  84. (start-service 'smtpd))
  85. marionette))
  86. (test-assert "mbox is empty"
  87. (marionette-eval
  88. '(and (file-exists? "/var/mail")
  89. (not (file-exists? "/var/mail/root")))
  90. marionette))
  91. (test-eq "accept an email"
  92. #t
  93. (let* ((smtp (socket AF_INET SOCK_STREAM 0))
  94. (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
  95. (connect smtp addr)
  96. ;; Be greeted.
  97. (read-reply-code smtp) ;220
  98. ;; Greet the server.
  99. (write-line "EHLO somehost" smtp)
  100. (read-reply-code smtp) ;250
  101. ;; Set sender email.
  102. (write-line "MAIL FROM: <someone>" smtp)
  103. (read-reply-code smtp) ;250
  104. ;; Set recipient email.
  105. (write-line "RCPT TO: <root>" smtp)
  106. (read-reply-code smtp) ;250
  107. ;; Send message.
  108. (write-line "DATA" smtp)
  109. (read-reply-code smtp) ;354
  110. (write-line "Subject: Hello" smtp)
  111. (newline smtp)
  112. (write-line "Nice to meet you!" smtp)
  113. (write-line "." smtp)
  114. (read-reply-code smtp) ;250
  115. ;; Say goodbye.
  116. (write-line "QUIT" smtp)
  117. (read-reply-code smtp) ;221
  118. (close smtp)
  119. #t))
  120. (test-assert "mail arrived"
  121. (marionette-eval
  122. '(begin
  123. (use-modules (ice-9 popen)
  124. (ice-9 rdelim))
  125. (define (queue-empty?)
  126. (eof-object?
  127. (read-line
  128. (open-input-pipe
  129. (string-append #$(file-append opensmtpd "/sbin/smtpctl")
  130. " show queue")))))
  131. (let wait ()
  132. (if (queue-empty?)
  133. (file-exists? "/var/mail/root")
  134. (begin (sleep 1) (wait)))))
  135. marionette))
  136. (test-end)
  137. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  138. (gexp->derivation "opensmtpd-test" test))
  139. (define %test-opensmtpd
  140. (system-test
  141. (name "opensmtpd")
  142. (description "Send an email to a running OpenSMTPD server.")
  143. (value (run-opensmtpd-test))))
  144. (define %exim-os
  145. (simple-operating-system
  146. (service dhcp-client-service-type)
  147. (service mail-aliases-service-type '())
  148. (service exim-service-type
  149. (exim-configuration
  150. (config-file
  151. (plain-file "exim.conf" "
  152. primary_hostname = komputilo
  153. domainlist local_domains = @
  154. domainlist relay_to_domains =
  155. hostlist relay_from_hosts = localhost
  156. never_users =
  157. acl_smtp_rcpt = acl_check_rcpt
  158. acl_smtp_data = acl_check_data
  159. begin acl
  160. acl_check_rcpt:
  161. accept
  162. acl_check_data:
  163. accept
  164. "))))))
  165. (define (run-exim-test)
  166. "Return a test of an OS running an Exim service."
  167. (define vm
  168. (virtual-machine
  169. (operating-system (marionette-operating-system
  170. %exim-os
  171. #:imported-modules '((gnu services herd))))
  172. (port-forwardings '((1025 . 25)))))
  173. (define test
  174. (with-imported-modules '((gnu build marionette)
  175. (ice-9 ftw))
  176. #~(begin
  177. (use-modules (rnrs base)
  178. (srfi srfi-64)
  179. (ice-9 ftw)
  180. (ice-9 rdelim)
  181. (ice-9 regex)
  182. (gnu build marionette))
  183. (define marionette
  184. (make-marionette '(#$vm)))
  185. (define (read-reply-code port)
  186. "Read a SMTP reply from PORT and return its reply code."
  187. (let* ((line (read-line port))
  188. (mo (string-match "([0-9]+)([ -]).*" line))
  189. (code (string->number (match:substring mo 1)))
  190. (finished? (string= " " (match:substring mo 2))))
  191. (if finished?
  192. code
  193. (read-reply-code port))))
  194. (define smtp (socket AF_INET SOCK_STREAM 0))
  195. (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
  196. (mkdir #$output)
  197. (chdir #$output)
  198. (test-begin "exim")
  199. (test-assert "service is running"
  200. (marionette-eval
  201. '(begin
  202. (use-modules (gnu services herd))
  203. (start-service 'exim))
  204. marionette))
  205. (sleep 1) ;; give the service time to start talking
  206. (connect smtp addr)
  207. ;; Be greeted.
  208. (test-eq "greeting received"
  209. 220 (read-reply-code smtp))
  210. ;; Greet the server.
  211. (write-line "EHLO somehost" smtp)
  212. (test-eq "greeting successful"
  213. 250 (read-reply-code smtp))
  214. ;; Set sender email.
  215. (write-line "MAIL FROM: test@example.com" smtp)
  216. (test-eq "sender set"
  217. 250 (read-reply-code smtp)) ;250
  218. ;; Set recipient email.
  219. (write-line "RCPT TO: root@komputilo" smtp)
  220. (test-eq "recipient set"
  221. 250 (read-reply-code smtp)) ;250
  222. ;; Send message.
  223. (write-line "DATA" smtp)
  224. (test-eq "data begun"
  225. 354 (read-reply-code smtp)) ;354
  226. (write-line "Subject: Hello" smtp)
  227. (newline smtp)
  228. (write-line "Nice to meet you!" smtp)
  229. (write-line "." smtp)
  230. (test-eq "message sent"
  231. 250 (read-reply-code smtp)) ;250
  232. ;; Say goodbye.
  233. (write-line "QUIT" smtp)
  234. (test-eq "quit successful"
  235. 221 (read-reply-code smtp)) ;221
  236. (close smtp)
  237. (test-eq "the email is received"
  238. 1
  239. (marionette-eval
  240. '(begin
  241. (use-modules (ice-9 ftw))
  242. (length (scandir "/var/spool/exim/msglog"
  243. (lambda (x) (not (string-prefix? "." x))))))
  244. marionette))
  245. (test-end)
  246. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  247. (gexp->derivation "exim-test" test))
  248. (define %test-exim
  249. (system-test
  250. (name "exim")
  251. (description "Send an email to a running an Exim server.")
  252. (value (run-exim-test))))
  253. (define %dovecot-os
  254. (simple-operating-system
  255. (service dhcp-client-service-type)
  256. (dovecot-service #:config
  257. (dovecot-configuration
  258. (disable-plaintext-auth? #f)
  259. (ssl? "no")
  260. (auth-mechanisms '("anonymous"))
  261. (auth-anonymous-username "alice")
  262. (mail-location
  263. (string-append "maildir:~/Maildir"
  264. ":INBOX=~/Maildir/INBOX"
  265. ":LAYOUT=fs"))))))
  266. (define (run-dovecot-test)
  267. "Return a test of an OS running Dovecot service."
  268. (define vm
  269. (virtual-machine
  270. (operating-system (marionette-operating-system
  271. %dovecot-os
  272. #:imported-modules '((gnu services herd))))
  273. (port-forwardings '((8143 . 143)))))
  274. (define test
  275. (with-imported-modules '((gnu build marionette))
  276. #~(begin
  277. (use-modules (gnu build marionette)
  278. (ice-9 iconv)
  279. (ice-9 rdelim)
  280. (rnrs base)
  281. (rnrs bytevectors)
  282. (srfi srfi-64))
  283. (define marionette
  284. (make-marionette '(#$vm)))
  285. (define* (message-length message #:key (encoding "iso-8859-1"))
  286. (bytevector-length (string->bytevector message encoding)))
  287. (define message "From: test@example.com\n\
  288. Subject: Hello Nice to meet you!")
  289. (mkdir #$output)
  290. (chdir #$output)
  291. (test-begin "dovecot")
  292. ;; Wait for dovecot to be up and running.
  293. (test-assert "dovecot running"
  294. (marionette-eval
  295. '(begin
  296. (use-modules (gnu services herd))
  297. (start-service 'dovecot))
  298. marionette))
  299. ;; Check Dovecot service's PID.
  300. (test-assert "service process id"
  301. (let ((pid
  302. (number->string (wait-for-file "/var/run/dovecot/master.pid"
  303. marionette))))
  304. (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
  305. marionette)))
  306. (test-assert "accept an email"
  307. (let ((imap (socket AF_INET SOCK_STREAM 0))
  308. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
  309. (connect imap addr)
  310. ;; Be greeted.
  311. (read-line imap) ;OK
  312. ;; Authenticate
  313. (write-line "a AUTHENTICATE ANONYMOUS" imap)
  314. (read-line imap) ;+
  315. (write-line "c2lyaGM=" imap)
  316. (read-line imap) ;OK
  317. ;; Create a TESTBOX mailbox
  318. (write-line "a CREATE TESTBOX" imap)
  319. (read-line imap) ;OK
  320. ;; Append a message to a TESTBOX mailbox
  321. (write-line (format #f "a APPEND TESTBOX {~a}"
  322. (number->string (message-length message)))
  323. imap)
  324. (read-line imap) ;+
  325. (write-line message imap)
  326. (read-line imap) ;OK
  327. ;; Logout
  328. (write-line "a LOGOUT" imap)
  329. (close imap)
  330. #t))
  331. (test-equal "mail arrived"
  332. message
  333. (marionette-eval
  334. '(begin
  335. (use-modules (ice-9 ftw)
  336. (ice-9 match))
  337. (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
  338. (match (scandir TESTBOX/new)
  339. (("." ".." message-file)
  340. (call-with-input-file
  341. (string-append TESTBOX/new message-file)
  342. get-string-all)))))
  343. marionette))
  344. (test-end)
  345. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  346. (gexp->derivation "dovecot-test" test))
  347. (define %test-dovecot
  348. (system-test
  349. (name "dovecot")
  350. (description "Connect to a running Dovecot server.")
  351. (value (run-dovecot-test))))
  352. (define %getmail-os
  353. (simple-operating-system
  354. (service dhcp-client-service-type)
  355. (service dovecot-service-type
  356. (dovecot-configuration
  357. (disable-plaintext-auth? #f)
  358. (ssl? "no")
  359. (auth-mechanisms '("anonymous" "plain"))
  360. (auth-anonymous-username "alice")
  361. (mail-location
  362. (string-append "maildir:~/Maildir"
  363. ":INBOX=~/Maildir/INBOX"
  364. ":LAYOUT=fs"))))
  365. (service getmail-service-type
  366. (list
  367. (getmail-configuration
  368. (name 'test)
  369. (user "alice")
  370. (directory "/var/lib/getmail/alice")
  371. (idle '("TESTBOX"))
  372. (rcfile
  373. (getmail-configuration-file
  374. (retriever
  375. (getmail-retriever-configuration
  376. (type "SimpleIMAPRetriever")
  377. (server "localhost")
  378. (username "alice")
  379. (port 143)
  380. (extra-parameters
  381. '((password . "testpass")
  382. (mailboxes . ("TESTBOX"))))))
  383. (destination
  384. (getmail-destination-configuration
  385. (type "Maildir")
  386. (path "/home/alice/TestMaildir/")))
  387. (options
  388. (getmail-options-configuration
  389. (read-all #f))))))))))
  390. (define (run-getmail-test)
  391. "Return a test of an OS running Getmail service."
  392. (define vm
  393. (virtual-machine
  394. (operating-system (marionette-operating-system
  395. %getmail-os
  396. #:imported-modules '((gnu services herd))))
  397. (port-forwardings '((8143 . 143)))))
  398. (define test
  399. (with-imported-modules '((gnu build marionette))
  400. #~(begin
  401. (use-modules (gnu build marionette)
  402. (ice-9 iconv)
  403. (ice-9 rdelim)
  404. (rnrs base)
  405. (rnrs bytevectors)
  406. (srfi srfi-64))
  407. (define marionette
  408. (make-marionette '(#$vm)))
  409. (define* (message-length message #:key (encoding "iso-8859-1"))
  410. (bytevector-length (string->bytevector message encoding)))
  411. (define message "From: test@example.com\n\
  412. Subject: Hello Nice to meet you!")
  413. (mkdir #$output)
  414. (chdir #$output)
  415. (test-begin "getmail")
  416. ;; Wait for dovecot to be up and running.
  417. (test-assert "dovecot running"
  418. (marionette-eval
  419. '(begin
  420. (use-modules (gnu services herd))
  421. (start-service 'dovecot))
  422. marionette))
  423. (test-assert "set password for alice"
  424. (marionette-eval
  425. '(system "echo -e \"testpass\ntestpass\" | passwd alice")
  426. marionette))
  427. ;; Wait for getmail to be up and running.
  428. (test-assert "getmail-test running"
  429. (marionette-eval
  430. '(let* ((pw (getpw "alice"))
  431. (uid (passwd:uid pw))
  432. (gid (passwd:gid pw)))
  433. (use-modules (gnu services herd))
  434. (for-each
  435. (lambda (dir)
  436. (mkdir dir)
  437. (chown dir uid gid))
  438. '("/home/alice/TestMaildir"
  439. "/home/alice/TestMaildir/cur"
  440. "/home/alice/TestMaildir/new"
  441. "/home/alice/TestMaildir/tmp"
  442. "/home/alice/TestMaildir/TESTBOX"
  443. "/home/alice/TestMaildir/TESTBOX/cur"
  444. "/home/alice/TestMaildir/TESTBOX/new"
  445. "/home/alice/TestMaildir/TESTBOX/tmp"))
  446. (start-service 'getmail-test))
  447. marionette))
  448. ;; Check Dovecot service's PID.
  449. (test-assert "service process id"
  450. (let ((pid
  451. (number->string (wait-for-file "/var/run/dovecot/master.pid"
  452. marionette))))
  453. (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
  454. marionette)))
  455. (test-assert "accept an email"
  456. (let ((imap (socket AF_INET SOCK_STREAM 0))
  457. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
  458. (connect imap addr)
  459. ;; Be greeted.
  460. (read-line imap) ;OK
  461. ;; Authenticate
  462. (write-line "a AUTHENTICATE ANONYMOUS" imap)
  463. (read-line imap) ;+
  464. (write-line "c2lyaGM=" imap)
  465. (read-line imap) ;OK
  466. ;; Create a TESTBOX mailbox
  467. (write-line "a CREATE TESTBOX" imap)
  468. (read-line imap) ;OK
  469. ;; Append a message to a TESTBOX mailbox
  470. (write-line (format #f "a APPEND TESTBOX {~a}"
  471. (number->string (message-length message)))
  472. imap)
  473. (read-line imap) ;+
  474. (write-line message imap)
  475. (read-line imap) ;OK
  476. ;; Logout
  477. (write-line "a LOGOUT" imap)
  478. (close imap)
  479. #t))
  480. (sleep 1)
  481. (test-assert "mail arrived"
  482. (string-contains
  483. (marionette-eval
  484. '(begin
  485. (use-modules (ice-9 ftw)
  486. (ice-9 match))
  487. (let ((TESTBOX/new "/home/alice/TestMaildir/new/"))
  488. (match (scandir TESTBOX/new)
  489. (("." ".." message-file)
  490. (call-with-input-file
  491. (string-append TESTBOX/new message-file)
  492. get-string-all)))))
  493. marionette)
  494. message))
  495. (test-end)
  496. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  497. (gexp->derivation "getmail-test" test))
  498. (define %test-getmail
  499. (system-test
  500. (name "getmail")
  501. (description "Connect to a running Getmail server.")
  502. (value (run-getmail-test))))
  503. %getmail-os