mail.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (gnu tests mail)
  23. #:use-module (gnu tests)
  24. #:use-module (gnu system)
  25. #:use-module (gnu system vm)
  26. #:use-module (gnu services)
  27. #:use-module (gnu services mail)
  28. #:use-module (gnu services networking)
  29. #:use-module (guix gexp)
  30. #:use-module (guix store)
  31. #:use-module (ice-9 ftw)
  32. #:export (%test-opensmtpd
  33. %test-exim
  34. %test-dovecot))
  35. (define %opensmtpd-os
  36. (simple-operating-system
  37. (service dhcp-client-service-type)
  38. (service opensmtpd-service-type
  39. (opensmtpd-configuration
  40. (config-file
  41. (plain-file "smtpd.conf" "
  42. listen on 0.0.0.0
  43. accept from any for local deliver to mbox
  44. "))))))
  45. (define (run-opensmtpd-test)
  46. "Return a test of an OS running OpenSMTPD service."
  47. (define vm
  48. (virtual-machine
  49. (operating-system (marionette-operating-system
  50. %opensmtpd-os
  51. #:imported-modules '((gnu services herd))))
  52. (port-forwardings '((1025 . 25)))))
  53. (define test
  54. (with-imported-modules '((gnu build marionette))
  55. #~(begin
  56. (use-modules (rnrs base)
  57. (srfi srfi-64)
  58. (ice-9 rdelim)
  59. (ice-9 regex)
  60. (gnu build marionette))
  61. (define marionette
  62. (make-marionette '(#$vm)))
  63. (define (read-reply-code port)
  64. "Read a SMTP reply from PORT and return its reply code."
  65. (let* ((line (read-line port))
  66. (mo (string-match "([0-9]+)([ -]).*" line))
  67. (code (string->number (match:substring mo 1)))
  68. (finished? (string= " " (match:substring mo 2))))
  69. (if finished?
  70. code
  71. (read-reply-code port))))
  72. (mkdir #$output)
  73. (chdir #$output)
  74. (test-begin "opensmptd")
  75. (test-assert "service is running"
  76. (marionette-eval
  77. '(begin
  78. (use-modules (gnu services herd))
  79. (start-service 'smtpd))
  80. marionette))
  81. (test-assert "mbox is empty"
  82. (marionette-eval
  83. '(and (file-exists? "/var/mail")
  84. (not (file-exists? "/var/mail/root")))
  85. marionette))
  86. (test-eq "accept an email"
  87. #t
  88. (let* ((smtp (socket AF_INET SOCK_STREAM 0))
  89. (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
  90. (connect smtp addr)
  91. ;; Be greeted.
  92. (read-reply-code smtp) ;220
  93. ;; Greet the server.
  94. (write-line "EHLO somehost" smtp)
  95. (read-reply-code smtp) ;250
  96. ;; Set sender email.
  97. (write-line "MAIL FROM: <someone>" smtp)
  98. (read-reply-code smtp) ;250
  99. ;; Set recipient email.
  100. (write-line "RCPT TO: <root>" smtp)
  101. (read-reply-code smtp) ;250
  102. ;; Send message.
  103. (write-line "DATA" smtp)
  104. (read-reply-code smtp) ;354
  105. (write-line "Subject: Hello" smtp)
  106. (newline smtp)
  107. (write-line "Nice to meet you!" smtp)
  108. (write-line "." smtp)
  109. (read-reply-code smtp) ;250
  110. ;; Say goodbye.
  111. (write-line "QUIT" smtp)
  112. (read-reply-code smtp) ;221
  113. (close smtp)
  114. #t))
  115. (test-assert "mail arrived"
  116. (marionette-eval
  117. '(begin
  118. (use-modules (ice-9 popen)
  119. (ice-9 rdelim))
  120. (define (queue-empty?)
  121. (eof-object?
  122. (read-line
  123. (open-input-pipe "smtpctl show queue"))))
  124. (let wait ()
  125. (if (queue-empty?)
  126. (file-exists? "/var/mail/root")
  127. (begin (sleep 1) (wait)))))
  128. marionette))
  129. (test-end)
  130. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  131. (gexp->derivation "opensmtpd-test" test))
  132. (define %test-opensmtpd
  133. (system-test
  134. (name "opensmtpd")
  135. (description "Send an email to a running OpenSMTPD server.")
  136. (value (run-opensmtpd-test))))
  137. (define %exim-os
  138. (simple-operating-system
  139. (service dhcp-client-service-type)
  140. (service mail-aliases-service-type '())
  141. (service exim-service-type
  142. (exim-configuration
  143. (config-file
  144. (plain-file "exim.conf" "
  145. primary_hostname = komputilo
  146. domainlist local_domains = @
  147. domainlist relay_to_domains =
  148. hostlist relay_from_hosts = localhost
  149. never_users =
  150. acl_smtp_rcpt = acl_check_rcpt
  151. acl_smtp_data = acl_check_data
  152. begin acl
  153. acl_check_rcpt:
  154. accept
  155. acl_check_data:
  156. accept
  157. "))))))
  158. (define (run-exim-test)
  159. "Return a test of an OS running an Exim service."
  160. (define vm
  161. (virtual-machine
  162. (operating-system (marionette-operating-system
  163. %exim-os
  164. #:imported-modules '((gnu services herd))))
  165. (port-forwardings '((1025 . 25)))))
  166. (define test
  167. (with-imported-modules '((gnu build marionette)
  168. (ice-9 ftw))
  169. #~(begin
  170. (use-modules (rnrs base)
  171. (srfi srfi-64)
  172. (ice-9 ftw)
  173. (ice-9 rdelim)
  174. (ice-9 regex)
  175. (gnu build marionette))
  176. (define marionette
  177. (make-marionette '(#$vm)))
  178. (define (read-reply-code port)
  179. "Read a SMTP reply from PORT and return its reply code."
  180. (let* ((line (read-line port))
  181. (mo (string-match "([0-9]+)([ -]).*" line))
  182. (code (string->number (match:substring mo 1)))
  183. (finished? (string= " " (match:substring mo 2))))
  184. (if finished?
  185. code
  186. (read-reply-code port))))
  187. (define smtp (socket AF_INET SOCK_STREAM 0))
  188. (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
  189. (mkdir #$output)
  190. (chdir #$output)
  191. (test-begin "exim")
  192. (test-assert "service is running"
  193. (marionette-eval
  194. '(begin
  195. (use-modules (gnu services herd))
  196. (start-service 'exim))
  197. marionette))
  198. (sleep 1) ;; give the service time to start talking
  199. (connect smtp addr)
  200. ;; Be greeted.
  201. (test-eq "greeting received"
  202. 220 (read-reply-code smtp))
  203. ;; Greet the server.
  204. (write-line "EHLO somehost" smtp)
  205. (test-eq "greeting successful"
  206. 250 (read-reply-code smtp))
  207. ;; Set sender email.
  208. (write-line "MAIL FROM: test@example.com" smtp)
  209. (test-eq "sender set"
  210. 250 (read-reply-code smtp)) ;250
  211. ;; Set recipient email.
  212. (write-line "RCPT TO: root@komputilo" smtp)
  213. (test-eq "recipient set"
  214. 250 (read-reply-code smtp)) ;250
  215. ;; Send message.
  216. (write-line "DATA" smtp)
  217. (test-eq "data begun"
  218. 354 (read-reply-code smtp)) ;354
  219. (write-line "Subject: Hello" smtp)
  220. (newline smtp)
  221. (write-line "Nice to meet you!" smtp)
  222. (write-line "." smtp)
  223. (test-eq "message sent"
  224. 250 (read-reply-code smtp)) ;250
  225. ;; Say goodbye.
  226. (write-line "QUIT" smtp)
  227. (test-eq "quit successful"
  228. 221 (read-reply-code smtp)) ;221
  229. (close smtp)
  230. (test-eq "the email is received"
  231. 1
  232. (marionette-eval
  233. '(begin
  234. (use-modules (ice-9 ftw))
  235. (length (scandir "/var/spool/exim/msglog"
  236. (lambda (x) (not (string-prefix? "." x))))))
  237. marionette))
  238. (test-end)
  239. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  240. (gexp->derivation "exim-test" test))
  241. (define %test-exim
  242. (system-test
  243. (name "exim")
  244. (description "Send an email to a running an Exim server.")
  245. (value (run-exim-test))))
  246. (define %dovecot-os
  247. (simple-operating-system
  248. (service dhcp-client-service-type)
  249. (dovecot-service #:config
  250. (dovecot-configuration
  251. (disable-plaintext-auth? #f)
  252. (ssl? "no")
  253. (auth-mechanisms '("anonymous"))
  254. (auth-anonymous-username "alice")
  255. (mail-location
  256. (string-append "maildir:~/Maildir"
  257. ":INBOX=~/Maildir/INBOX"
  258. ":LAYOUT=fs"))))))
  259. (define (run-dovecot-test)
  260. "Return a test of an OS running Dovecot service."
  261. (define vm
  262. (virtual-machine
  263. (operating-system (marionette-operating-system
  264. %dovecot-os
  265. #:imported-modules '((gnu services herd))))
  266. (port-forwardings '((8143 . 143)))))
  267. (define test
  268. (with-imported-modules '((gnu build marionette))
  269. #~(begin
  270. (use-modules (gnu build marionette)
  271. (ice-9 iconv)
  272. (ice-9 rdelim)
  273. (rnrs base)
  274. (rnrs bytevectors)
  275. (srfi srfi-64))
  276. (define marionette
  277. (make-marionette '(#$vm)))
  278. (define* (message-length message #:key (encoding "iso-8859-1"))
  279. (bytevector-length (string->bytevector message encoding)))
  280. (define message "From: test@example.com\n\
  281. Subject: Hello Nice to meet you!")
  282. (mkdir #$output)
  283. (chdir #$output)
  284. (test-begin "dovecot")
  285. ;; Wait for dovecot to be up and running.
  286. (test-assert "dovecot running"
  287. (marionette-eval
  288. '(begin
  289. (use-modules (gnu services herd))
  290. (start-service 'dovecot))
  291. marionette))
  292. ;; Check Dovecot service's PID.
  293. (test-assert "service process id"
  294. (let ((pid
  295. (number->string (wait-for-file "/var/run/dovecot/master.pid"
  296. marionette))))
  297. (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
  298. marionette)))
  299. (test-assert "accept an email"
  300. (let ((imap (socket AF_INET SOCK_STREAM 0))
  301. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
  302. (connect imap addr)
  303. ;; Be greeted.
  304. (read-line imap) ;OK
  305. ;; Authenticate
  306. (write-line "a AUTHENTICATE ANONYMOUS" imap)
  307. (read-line imap) ;+
  308. (write-line "c2lyaGM=" imap)
  309. (read-line imap) ;OK
  310. ;; Create a TESTBOX mailbox
  311. (write-line "a CREATE TESTBOX" imap)
  312. (read-line imap) ;OK
  313. ;; Append a message to a TESTBOX mailbox
  314. (write-line (format #f "a APPEND TESTBOX {~a}"
  315. (number->string (message-length message)))
  316. imap)
  317. (read-line imap) ;+
  318. (write-line message imap)
  319. (read-line imap) ;OK
  320. ;; Logout
  321. (write-line "a LOGOUT" imap)
  322. (close imap)
  323. #t))
  324. (test-equal "mail arrived"
  325. message
  326. (marionette-eval
  327. '(begin
  328. (use-modules (ice-9 ftw)
  329. (ice-9 match))
  330. (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
  331. (match (scandir TESTBOX/new)
  332. (("." ".." message-file)
  333. (call-with-input-file
  334. (string-append TESTBOX/new message-file)
  335. get-string-all)))))
  336. marionette))
  337. (test-end)
  338. (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
  339. (gexp->derivation "dovecot-test" test))
  340. (define %test-dovecot
  341. (system-test
  342. (name "dovecot")
  343. (description "Connect to a running Dovecot server.")
  344. (value (run-dovecot-test))))