mail.scm 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291
  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>
  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 accounts)
  29. #:use-module (gnu system shadow)
  30. #:use-module (gnu system vm)
  31. #:use-module (gnu services)
  32. #:use-module (gnu services base)
  33. #:use-module (gnu services getmail)
  34. #:use-module (gnu services mail)
  35. #:use-module (gnu services networking)
  36. #:use-module (guix gexp)
  37. #:use-module (guix store)
  38. #:use-module (ice-9 ftw)
  39. #:use-module (srfi srfi-64)
  40. #:export (%test-opensmtpd
  41. %test-exim
  42. %test-dovecot
  43. %test-getmail))
  44. (define %opensmtpd-os
  45. (simple-operating-system
  46. (service dhcp-client-service-type)
  47. (service opensmtpd-service-type
  48. (opensmtpd-configuration
  49. (config-file
  50. (plain-file "smtpd.conf" "
  51. listen on 0.0.0.0
  52. action inbound mbox
  53. match from any for local action inbound
  54. "))))))
  55. (define (run-opensmtpd-test)
  56. "Return a test of an OS running OpenSMTPD service."
  57. (define vm
  58. (virtual-machine
  59. (operating-system (marionette-operating-system
  60. %opensmtpd-os
  61. #:imported-modules '((gnu services herd))))
  62. (port-forwardings '((1025 . 25)))))
  63. (define test
  64. (with-imported-modules '((gnu build marionette))
  65. #~(begin
  66. (use-modules (rnrs base)
  67. (srfi srfi-64)
  68. (ice-9 rdelim)
  69. (ice-9 regex)
  70. (gnu build marionette))
  71. (define marionette
  72. (make-marionette '(#$vm)))
  73. (define (read-reply-code port)
  74. "Read a SMTP reply from PORT and return its reply code."
  75. (let* ((line (read-line port))
  76. (mo (string-match "([0-9]+)([ -]).*" line))
  77. (code (string->number (match:substring mo 1)))
  78. (finished? (string= " " (match:substring mo 2))))
  79. (if finished?
  80. code
  81. (read-reply-code port))))
  82. (test-runner-current (system-test-runner #$output))
  83. (test-begin "opensmptd")
  84. (test-assert "service is running"
  85. (marionette-eval
  86. '(begin
  87. (use-modules (gnu services herd))
  88. (start-service 'smtpd))
  89. marionette))
  90. (test-assert "mbox is empty"
  91. (marionette-eval
  92. '(and (file-exists? "/var/spool/mail")
  93. (not (file-exists? "/var/spool/mail/root")))
  94. marionette))
  95. (test-eq "accept an email"
  96. #t
  97. (let* ((smtp (socket AF_INET SOCK_STREAM 0))
  98. (addr (make-socket-address AF_INET INADDR_LOOPBACK 1025)))
  99. (connect smtp addr)
  100. ;; Be greeted.
  101. (read-reply-code smtp) ;220
  102. ;; Greet the server.
  103. (write-line "EHLO somehost" smtp)
  104. (read-reply-code smtp) ;250
  105. ;; Set sender email.
  106. (write-line "MAIL FROM: <someone>" smtp)
  107. (read-reply-code smtp) ;250
  108. ;; Set recipient email.
  109. (write-line "RCPT TO: <root>" smtp)
  110. (read-reply-code smtp) ;250
  111. ;; Send message.
  112. (write-line "DATA" smtp)
  113. (read-reply-code smtp) ;354
  114. (write-line "Subject: Hello" smtp)
  115. (newline smtp)
  116. (write-line "Nice to meet you!" smtp)
  117. (write-line "." smtp)
  118. (read-reply-code smtp) ;250
  119. ;; Say goodbye.
  120. (write-line "QUIT" smtp)
  121. (read-reply-code smtp) ;221
  122. (close smtp)
  123. #t))
  124. (test-assert "mail arrived"
  125. (marionette-eval
  126. '(begin
  127. (use-modules (ice-9 popen)
  128. (ice-9 rdelim))
  129. (define (queue-empty?)
  130. (let* ((pipe (open-pipe* OPEN_READ
  131. #$(file-append opensmtpd
  132. "/sbin/smtpctl")
  133. "show" "queue"))
  134. (line (read-line pipe)))
  135. (close-pipe pipe)
  136. (eof-object? line)))
  137. (let wait ((n 20))
  138. (cond ((queue-empty?)
  139. (file-exists? "/var/spool/mail/root"))
  140. ((zero? n)
  141. (error "root mailbox didn't show up"))
  142. (else
  143. (sleep 1) (wait (- n 1))))))
  144. marionette))
  145. (test-end))))
  146. (gexp->derivation "opensmtpd-test" test))
  147. (define %test-opensmtpd
  148. (system-test
  149. (name "opensmtpd")
  150. (description "Send an email to a running OpenSMTPD server.")
  151. (value (run-opensmtpd-test))))
  152. ;; trying to create a bad record, should result in an error.
  153. ;; this function should not be able return, instead it should throw an error
  154. ;(define (create-bad-record record)
  155. ;; TODO why is this not working
  156. ; (with-output-to-port (%make-void-port "w")
  157. ; (lambda () (when record #f))))
  158. ;; if this caller function is reached, then trying to create the bad record
  159. ;; resulted in an error. So return true.
  160. (define (return-true error arg)
  161. #t)
  162. (define (return-false error arg)
  163. #f)
  164. (define (test-good-record func)
  165. (catch #t func return-false))
  166. (define (run-opensmtpd-record-sanitation-test)
  167. ;;(with-output-to-port (%make-void-port "w")
  168. ;; (lambda ()
  169. (test-begin "run-opensmtpd-record-sanitation-test")
  170. (test-error "(interface (filters ...)) has two filters with the same name."
  171. #t
  172. (let ((dkimsign
  173. (opensmtpd-filter
  174. (name "dkimsign")
  175. (exec #t)
  176. (proc
  177. (list
  178. (file-append
  179. opensmtpd-filter-dkimsign
  180. "/libexec/opensmtpd/filter-dkimsign")
  181. " -d gnucode.me -s 2021-09-22 -c "
  182. "relaxed/relaxed -k "
  183. "rando string"
  184. "/etc/dkim/private.key "
  185. "user nobody group nogroup")))))
  186. (opensmtpd-interface
  187. (interface "lo")
  188. (filters (list
  189. dkimsign
  190. dkimsign)))))
  191. ;; duplicate filter names. The filters are different, the fix
  192. ;; is to change one filter's 'name' field.
  193. (test-error "Test <interface> cannot have two filters with the same name."
  194. #t
  195. (opensmtpd-interface
  196. (filters (list
  197. (opensmtpd-filter-phase
  198. (name "src")
  199. (phase "connect")
  200. (options
  201. (list
  202. (opensmtpd-option
  203. (option "fcrdns")
  204. (bool #f))))
  205. (decision "junk"))
  206. (opensmtpd-filter-phase
  207. (name "src")
  208. (phase "helo")
  209. (options
  210. (list
  211. (opensmtpd-option
  212. (option "rdns")
  213. (bool #f))))
  214. (decision "reject")
  215. (message "433 no rdns."))))))
  216. (test-error "(filter (proc ...)) is a list of strings or gexps, NOT numbers."
  217. #t
  218. (opensmtpd-filter
  219. (name "dkimsign")
  220. (proc
  221. (list
  222. (file-append opensmtpd-filter-dkimsign
  223. "/libexec/opensmtpd/filter-dkimsign")
  224. " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
  225. 5
  226. "/etc/dkim/private.key "
  227. "user nobody group nogroup"))
  228. (exec #t)))
  229. (test-error "Test <filter-phase> fieldname 'phase' has an wrong phase name."
  230. #t
  231. (opensmtpd-filter-phase
  232. (name "filter")
  233. (phase "wrongString")
  234. (decision "bypass")
  235. (options
  236. (list
  237. (opensmtpd-option
  238. (option "auth"))))))
  239. (test-error
  240. "(filter-phase (decision \"reject\")) requires a 'message'."
  241. #t
  242. (opensmtpd-interface
  243. (filters (list
  244. (opensmtpd-filter-phase
  245. (name "src")
  246. (phase "connect")
  247. (options
  248. (list
  249. (opensmtpd-option
  250. (option "src")
  251. (data (opensmtpd-table
  252. (name "src-table")
  253. (data (list "cat" "hat")))))))
  254. (decision "reject"))))))
  255. (test-error (string-append
  256. "Test <filter-phase> fieldname 'decision' "
  257. "w/ value \"reject\" and \"disconnect\" requires a 'message'."
  258. " The message must begin with 4xx or 5xx.")
  259. #t
  260. (opensmtpd-interface
  261. (filters (list
  262. (opensmtpd-filter-phase
  263. (name "src")
  264. (phase "connect")
  265. (options
  266. (list
  267. (opensmtpd-option
  268. (option "src")
  269. (data (opensmtpd-table
  270. (name "src-table")
  271. (data (list "cat" "hat")))))))
  272. (decision "reject")
  273. (message "322 Bad data!"))))))
  274. (test-error
  275. "(filter-phase \"rewrite\") requires fieldname 'value' to have a number."
  276. #t
  277. (opensmtpd-interface
  278. (filters
  279. (list
  280. (opensmtpd-filter-phase
  281. (name "noFRDNS")
  282. (phase "commit")
  283. (options (list (opensmtpd-option
  284. (option "fcrdns")
  285. (bool #f))))
  286. (decision "rewrite"))))))
  287. (define option-string1/2 (list-ref (list "junk" "bypass")
  288. (random 2)))
  289. (test-error
  290. (string-append "(filter-phase \"decision\" with values 'junk' or 'bypass',"
  291. " then fieldname 'message' and 'value' must be blank.")
  292. #t
  293. (opensmtpd-interface
  294. (filters
  295. (list
  296. (opensmtpd-filter-phase
  297. (name "noFRDNS")
  298. (phase "commit")
  299. (options (list (opensmtpd-option
  300. (option "fcrdns")
  301. )))
  302. (decision option-string1/2)
  303. (message "This is not a good email."))))))
  304. (test-error "You cannot junk an email on phase commit."
  305. #t
  306. (opensmtpd-interface
  307. (filters
  308. (list
  309. (opensmtpd-filter-phase
  310. (name "junk-after-commit")
  311. (options (list (opensmtpd-option
  312. (option "fcrdns"))))
  313. (phase "commit")
  314. (decision "junk"))))))
  315. (define (good-interface1)
  316. (opensmtpd-interface
  317. (senders
  318. (opensmtpd-table
  319. (name "senders")
  320. (data '(("joshua" . "joshua@dismail.de")))))
  321. (masquerade #t)))
  322. (test-assert "good interface" (test-good-record good-interface1))
  323. (test-error "Test <filter-phase> has 2 duplicate options."
  324. #t
  325. (opensmtpd-filter-phase
  326. (name "invalid-fcrdns")
  327. (phase "connect")
  328. (options
  329. (list (opensmtpd-option
  330. (option "fcrdns"))
  331. (opensmtpd-option
  332. (option "fcrdns"))))
  333. (decision "reject")
  334. (message "422 No valid fcrdns.")))
  335. (test-error "Test <filter-phase> option 'src' requires a table."
  336. #t
  337. (opensmtpd-filter-phase
  338. (name "filter")
  339. (phase "helo")
  340. (decision "bypass")
  341. (options
  342. (list
  343. (opensmtpd-option
  344. (option "src"))))))
  345. (test-error "Test <filter-phase> option 'fcrdns' cannot have a table."
  346. #t
  347. (opensmtpd-filter-phase
  348. (name "filter")
  349. (phase "helo")
  350. (decision "bypass")
  351. (options
  352. (list
  353. (opensmtpd-option
  354. (option "fcrdns")
  355. (data (opensmtpd-table
  356. (name "table")
  357. (data (list "hello" "cat")))))))))
  358. (test-error "Test <filter-phase> must have at least one option."
  359. #t
  360. (opensmtpd-filter-phase
  361. (name "filter")
  362. (phase "helo")
  363. (decision "bypass")))
  364. (test-error
  365. "(filter-phase (options ...)) must be a list of <opensmtpd-option>s."
  366. #t
  367. (opensmtpd-filter-phase
  368. (name "rdns")
  369. (decision "junk")
  370. (phase "helo")
  371. (options
  372. (list 5))))
  373. (test-error "Test (decision \"junks\") is invalid."
  374. #t
  375. (opensmtpd-filter-phase
  376. (name "this")
  377. (options (list
  378. (opensmtpd-option
  379. (option "auth"))))
  380. (phase "helo")
  381. (decision "junks")))
  382. (test-error
  383. (string-append
  384. "(local-delivery (virtual ...) must be an <opensmtp-table> "
  385. "whose 'data' is an alist.")
  386. #t
  387. (opensmtpd-local-delivery
  388. (name "receive")
  389. (method (opensmtpd-maildir
  390. (pathname "/home/%{rcpt.user}/Maildir")))
  391. (virtual (opensmtpd-table
  392. (name "virt")
  393. (data (list "jbranso@dismail.de"))))))
  394. (test-error "(opensmtpd-match (options ...)) should not be quoted."
  395. #t
  396. (opensmtpd-match
  397. (action (opensmtpd-relay
  398. (name "relay")))
  399. (options
  400. '((opensmtpd-option
  401. (option "for any"))))))
  402. (test-error "Test <opensmtpd-match> has duplicate 'for' options."
  403. #t
  404. (opensmtpd-match
  405. (action (opensmtpd-relay
  406. (name "relay")))
  407. (options (list
  408. (opensmtpd-option
  409. (option "for any"))
  410. (opensmtpd-option
  411. (option "for local"))))))
  412. (test-error "Test <opensmtpd-match> has duplicate 'from' options."
  413. #t
  414. (opensmtpd-match
  415. (action (opensmtpd-relay
  416. (name "relay")))
  417. (options (list
  418. (opensmtpd-option
  419. (option "from any"))
  420. (opensmtpd-option
  421. (option "from auth"))))))
  422. (define option-string1
  423. (list-ref (list "helo" "rcpt-to" "mail-from")
  424. (random 3)))
  425. ;; rcpt-to, mail-from, and helo must have a data field.
  426. (test-error (string-append "Test (opensmtpd-option (option \""
  427. option-string1
  428. "\")) must also define fieldname 'data'.")
  429. #t
  430. (opensmtpd-match
  431. (action (opensmtpd-relay
  432. (name "relay")))
  433. (options (list
  434. (opensmtpd-option
  435. (option option-string1))))))
  436. (define option-string2
  437. (list-ref (list "for local" "for any" "from any"
  438. "from local" "from socket" "tls")
  439. (random 6)))
  440. ;; "for local" "for any" "from any" "from local" "from socket" "tls"
  441. ;; cannot have data or regex defined
  442. (test-error (string-append "Test (opensmtpd-option (option \""
  443. option-string2
  444. "\")) cannot have fieldname 'data' defined.")
  445. #t
  446. (opensmtpd-match
  447. (action (opensmtpd-relay
  448. (name "relay")))
  449. (options (list
  450. (opensmtpd-option
  451. (option option-string2)
  452. (regex #t))))))
  453. (test-error "(opensmtpd-match (action ...)) needs to be defined."
  454. #t
  455. (opensmtpd-match
  456. (options (list
  457. (opensmtpd-option
  458. (option "from auth"))))))
  459. (test-error "(opensmtpd-match (options ...)) has duplicate 'helo's."
  460. #t
  461. (opensmtpd-match
  462. (action (opensmtpd-relay
  463. (name "relay")))
  464. (options (list
  465. (opensmtpd-option
  466. (option "helo")
  467. (bool #f))
  468. (opensmtpd-option
  469. (option "helo"))))))
  470. (test-error "(opensmtpd-match (options ...)) has duplicate 'mail-from's."
  471. #t
  472. (opensmtpd-match
  473. (action (opensmtpd-relay
  474. (name "relay")))
  475. (options (list
  476. (opensmtpd-option
  477. (option "mail-from")
  478. (data "hello"))
  479. (opensmtpd-option
  480. (option "mail-from")
  481. (data "world"))))))
  482. (test-error
  483. "(opensmtpd-match (options ...)) has an invalid option name: fcrdns."
  484. #t
  485. (opensmtpd-match
  486. (options (list
  487. (opensmtpd-option
  488. (option "fcrdns"))))
  489. (action (opensmtpd-relay
  490. (name "relay")))))
  491. (test-error
  492. "(opensmtpd-match (options ...)) has an invalid option name: rdns."
  493. #t
  494. (opensmtpd-match
  495. (options (list
  496. (opensmtpd-option
  497. (option "rdns"))))
  498. (action (opensmtpd-relay
  499. (name "relay")))))
  500. (test-error
  501. (string-append
  502. "(opensmtpd-match (options ...)) option-name 'tag' must "
  503. "also have 'data' be a string.")
  504. #t
  505. (opensmtpd-match
  506. (options (list
  507. (opensmtpd-option
  508. (option "tag"))))
  509. (action (opensmtpd-relay
  510. (name "relay")))))
  511. (define option-string3
  512. (list-ref (list "for domain" "for rcpt-to"
  513. "from mail-from" "from src")
  514. (random 4)))
  515. ;; the options in this list:
  516. ;; (list "for domain" "for rcpt-to" "from mail-from" "from src")
  517. ;; must have a data field.
  518. (test-error
  519. (string-append "Test (opensmtpd-option (option \""
  520. option-string3 "\"))"
  521. " must define fieldname 'data'.")
  522. #t
  523. (opensmtpd-match
  524. (options (list
  525. (opensmtpd-option
  526. (option option-string3))))
  527. (action (opensmtpd-relay
  528. (name "relay")))))
  529. (define option-string4
  530. (list-ref (list "for local" "for any" "from any"
  531. "from local" "from socket" "tls")
  532. (random 6)))
  533. ;; the options in this list cannot have a data or regex field defined.
  534. ;; (list "for local" "for any" "from any" "from local" "from socket" "tls")
  535. (test-error (string-append "Test (opensmtpd-option (option \""
  536. option-string4 "\"))"
  537. " cannot define fieldname 'data'.")
  538. #t
  539. (opensmtpd-match
  540. (options (list
  541. (opensmtpd-option
  542. (regex #t)
  543. (option option-string4))))
  544. (action (opensmtpd-relay
  545. (name "relay")))))
  546. (define option-string5
  547. (list-ref (list "for domain" "for rcpt-to"
  548. "from mail-from" "from src")
  549. (random 4)))
  550. (test-error (string-append "Test (opensmtpd-option (option \""
  551. option-string5 "\"))"
  552. " must define fieldname 'data' as a string or "
  553. "an <opensmtpd-table>, whose 'data' \n"
  554. "is a list of strings.")
  555. #t
  556. (opensmtpd-match
  557. (options
  558. (list (opensmtpd-option
  559. (option option-string5)
  560. (data (opensmtpd-table
  561. (name "src-table")
  562. (data '(("127.0.0.1" . "374.394.405.23"))))))))
  563. (action (opensmtpd-relay
  564. (name "relay")))))
  565. ;; match must have at least one option.
  566. (test-error
  567. "(opensmtpd-match (options ...)) must have at least one <opensmtpd-option>."
  568. #t
  569. (opensmtpd-match
  570. (action
  571. (opensmtpd-local-delivery
  572. (name "mail")))))
  573. ;; you cannot have strings of length 0.
  574. (test-error
  575. (string-append
  576. "(opensmtpd-table (name \"table\") (data '((\"james\" . \"\")))) "
  577. "has an empty string.")
  578. #t
  579. (opensmtpd-table
  580. (name "mytable")
  581. (data '(("hello" . "")))))
  582. (define good-match1
  583. (opensmtpd-match
  584. (action (opensmtpd-relay
  585. (name "relay")))
  586. (options (list (opensmtpd-option
  587. (option "for any"))
  588. (opensmtpd-option
  589. (option "from any"))
  590. (opensmtpd-option
  591. (option "auth"))))))
  592. (test-assert "good match" (test-good-record good-match1))
  593. ;;(test-assert "Test <opensmtpd-match> is valid.")
  594. ;; I used to have some code that assumed if you have
  595. ;; an "<interface>" and an "<socket>" using the same filter, then that
  596. ;; was a misconfiguration. It's not.
  597. (define (good-opensmtpd-configuration1)
  598. (let ([interface "lo"]
  599. [filter-dkimsign
  600. (opensmtpd-filter
  601. (name "dkimsign")
  602. (exec #t)
  603. (proc (list (file-append opensmtpd-filter-dkimsign
  604. "/libexec/opensmtpd/filter-dkimsign")
  605. " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k "
  606. "/etc/dkim/private.key "
  607. "user nobody group nogroup")))])
  608. (opensmtpd-configuration
  609. (interfaces
  610. (list
  611. ;; send out emails and be sure to dkimsign them.
  612. (opensmtpd-interface
  613. (interface interface)
  614. (filters (list filter-dkimsign)))))
  615. (socket
  616. (opensmtpd-socket
  617. (filters (list filter-dkimsign))))
  618. (matches (list
  619. (opensmtpd-match
  620. (action (opensmtpd-relay
  621. (name "relay")))
  622. (options (list (opensmtpd-option
  623. (option "for any"))
  624. (opensmtpd-option
  625. (option "from any"))
  626. (opensmtpd-option
  627. (option "auth"))))))))))
  628. (test-assert
  629. (string-append "opensmtpd-configuration may use the same dkimsign "
  630. "filter on <opensmtpd-socket> and <opensmtpd-interface>.")
  631. (test-good-record good-opensmtpd-configuration1))
  632. ;; this is just the largest configuration that I can test.
  633. (define (good-opensmtpd-configuration2)
  634. (let ([interface "lo"]
  635. [creds-table
  636. (opensmtpd-table
  637. (name "creds")
  638. (data
  639. (list
  640. (cons "joshua"
  641. "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))]
  642. [receive-action
  643. (opensmtpd-local-delivery
  644. (name "receive")
  645. (method (opensmtpd-maildir
  646. (pathname "/home/%{rcpt.user}/Maildir")
  647. (junk #t)))
  648. (alias (opensmtpd-table
  649. (name "aliases")
  650. (data '(("joshua@gnucode.me" . "joshua")))))
  651. (virtual (opensmtpd-table
  652. (name "virt")
  653. (data '(("josh"
  654. . "jbranso@dismail.de"))))))]
  655. ;; as of 7-24-22 this proc fieldname does not actually work, but
  656. ;; is proper syntax.
  657. [filter-dkimsign (opensmtpd-filter
  658. (name "dkimsign")
  659. (exec #t)
  660. (proc (list
  661. (file-append
  662. opensmtpd-filter-dkimsign
  663. "/libexec/opensmtpd/filter-dkimsign")
  664. " -d gnucode.me -s 2021-09-22 -c "
  665. "relaxed/relaxed -k "
  666. "/etc/dkim/private.key "
  667. "user nobody group nogroup")))]
  668. [filter-invalid-fcrdns (opensmtpd-filter-phase
  669. (name "invalid-fcrdns")
  670. (phase "connect")
  671. (options
  672. (list (opensmtpd-option
  673. (option "fcrdns")
  674. (bool #f))))
  675. (decision "reject")
  676. (message "422 No valid fcrdns."))]
  677. [filter-invalid-rdns (opensmtpd-filter-phase
  678. (name "invalid-rdns")
  679. (phase "connect")
  680. (options
  681. (list (opensmtpd-option
  682. (option "rdns")
  683. (bool #f))))
  684. (decision "junk"))]
  685. [smtp.gnucode.me (opensmtpd-pki
  686. (domain "smtp.gnucode.me")
  687. (cert "guix.scm")
  688. (key "guix.scm"))])
  689. (opensmtpd-configuration
  690. (mta-max-deferred 50)
  691. (queue
  692. (opensmtpd-queue
  693. (compression #t)))
  694. (smtp
  695. (opensmtpd-smtp
  696. (max-message-size "10M")))
  697. (srs
  698. (opensmtpd-srs
  699. (ttl-delay "5d")))
  700. (interfaces
  701. (list
  702. (opensmtpd-interface
  703. (interface interface)
  704. (port 25)
  705. (secure-connection "tls")
  706. (filters (list filter-invalid-fcrdns
  707. filter-invalid-rdns))
  708. (pki smtp.gnucode.me))
  709. ;; this lets local users logged into the system via ssh send email
  710. ;; be sure to dkimsign them.
  711. (opensmtpd-interface
  712. (interface interface)
  713. (port 465)
  714. (secure-connection "smtps")
  715. (pki smtp.gnucode.me)
  716. (auth creds-table)
  717. (filters (list filter-dkimsign)))
  718. ;; if you uncomment this next line, then you get issues.
  719. ;;(opensmtpd-socket
  720. ;; (filters (list filter-dkimsign)))
  721. ;; send out emails and be sure to dkimsign them.
  722. (opensmtpd-interface
  723. (interface interface)
  724. (port 587)
  725. (secure-connection "tls-require")
  726. (pki smtp.gnucode.me)
  727. (auth creds-table)
  728. (filters (list filter-dkimsign)))))
  729. (socket
  730. (opensmtpd-socket
  731. (filters (list filter-dkimsign))
  732. (tag "socket")))
  733. (matches (list
  734. (opensmtpd-match
  735. (action (opensmtpd-relay
  736. (name "relay")))
  737. (options (list (opensmtpd-option
  738. (option "for any"))
  739. (opensmtpd-option
  740. (option "from any"))
  741. (opensmtpd-option
  742. (option "auth")))))
  743. (opensmtpd-match
  744. (action receive-action)
  745. (options (list (opensmtpd-option
  746. (option "from any"))
  747. (opensmtpd-option
  748. (option "for domain")
  749. (data (opensmtpd-table
  750. (name "domain-table")
  751. (data (list "gnucode.me"
  752. "gnu-hurd.com"))))))))
  753. (opensmtpd-match
  754. (action receive-action)
  755. (options (list (opensmtpd-option
  756. (option "for local"))))))))))
  757. (test-assert "Test my largish example <opensmtpd-configuration>."
  758. (test-good-record good-opensmtpd-configuration2))
  759. ;; the matches have two actions with the same name,
  760. ;; but are different actions.
  761. (test-error
  762. (string-append "Test <opensmtpd-configuration> fieldname 'matches' has "
  763. "two actions with the same name, but the actions are "
  764. "different.")
  765. #t
  766. (opensmtpd-configuration
  767. (matches
  768. (list (opensmtpd-match
  769. (options
  770. (list
  771. (opensmtpd-option
  772. (option "auth"))))
  773. (action
  774. (opensmtpd-local-delivery
  775. (name "my-local-delivery")
  776. (ttl "50m"))))
  777. (opensmtpd-match
  778. (options
  779. (list
  780. (opensmtpd-option
  781. (option "auth"))))
  782. (action
  783. (opensmtpd-local-delivery
  784. (name "my-local-delivery")
  785. (ttl "50h"))))))))
  786. ;; you can only have 1 opensmtpd-socket.
  787. (test-error
  788. (string-append
  789. "(opensmtpd-configuration> (listen-on ...)) may only have "
  790. "one <opensmtpd-socket>.")
  791. #t
  792. (let ([interface "lo"])
  793. (opensmtpd-configuration
  794. (socket
  795. (list
  796. (opensmtpd-socket)
  797. (opensmtpd-socket)))
  798. (matches (list
  799. (opensmtpd-match
  800. (options
  801. (list
  802. (opensmtpd-option
  803. (option "auth"))))
  804. (action (opensmtpd-relay
  805. (name "relay")))))))))
  806. (test-end "run-opensmtpd-record-sanitation-test"))
  807. (define %test-opensmtpd-record-sanitation
  808. (system-test
  809. (name "opensmtpdRecordSanitation")
  810. (description
  811. (string-append "<opensmtpd> has numerous sanity checks.\n"
  812. "This checks that invalid configurations, return an\n"
  813. "appropriate error.\n"))
  814. (value (run-opensmtpd-record-sanitation-test))))
  815. (define %exim-os
  816. (simple-operating-system
  817. (service dhcp-client-service-type)
  818. (service mail-aliases-service-type '())
  819. (service exim-service-type
  820. (exim-configuration
  821. (config-file
  822. (plain-file "exim.conf" "
  823. primary_hostname = komputilo
  824. domainlist local_domains = @
  825. domainlist relay_to_domains =
  826. hostlist relay_from_hosts = localhost
  827. never_users =
  828. acl_smtp_rcpt = acl_check_rcpt
  829. acl_smtp_data = acl_check_data
  830. begin acl
  831. acl_check_rcpt:
  832. accept
  833. acl_check_data:
  834. accept
  835. "))))))
  836. (define (run-exim-test)
  837. "Return a test of an OS running an Exim service."
  838. (define vm
  839. (virtual-machine
  840. (operating-system (marionette-operating-system
  841. %exim-os
  842. #:imported-modules '((gnu services herd))))
  843. (port-forwardings '((1025 . 25)))))
  844. (define test
  845. (with-imported-modules '((gnu build marionette))
  846. #~(begin
  847. (use-modules (rnrs base)
  848. (srfi srfi-64)
  849. (ice-9 ftw)
  850. (ice-9 rdelim)
  851. (ice-9 regex)
  852. (gnu build marionette))
  853. (define marionette
  854. (make-marionette '(#$vm)))
  855. (define (read-reply-code port)
  856. "Read a SMTP reply from PORT and return its reply code."
  857. (let* ((line (read-line port))
  858. (mo (string-match "([0-9]+)([ -]).*" line))
  859. (code (string->number (match:substring mo 1)))
  860. (finished? (string= " " (match:substring mo 2))))
  861. (if finished?
  862. code
  863. (read-reply-code port))))
  864. (define smtp (socket AF_INET SOCK_STREAM 0))
  865. (define addr (make-socket-address AF_INET INADDR_LOOPBACK 1025))
  866. (test-runner-current (system-test-runner #$output))
  867. (test-begin "exim")
  868. (test-assert "service is running"
  869. (marionette-eval
  870. '(begin
  871. (use-modules (gnu services herd))
  872. (start-service 'exim))
  873. marionette))
  874. (sleep 1) ;; give the service time to start talking
  875. (connect smtp addr)
  876. ;; Be greeted.
  877. (test-eq "greeting received"
  878. 220 (read-reply-code smtp))
  879. ;; Greet the server.
  880. (write-line "EHLO somehost" smtp)
  881. (test-eq "greeting successful"
  882. 250 (read-reply-code smtp))
  883. ;; Set sender email.
  884. (write-line "MAIL FROM: test@example.com" smtp)
  885. (test-eq "sender set"
  886. 250 (read-reply-code smtp)) ;250
  887. ;; Set recipient email.
  888. (write-line "RCPT TO: root@komputilo" smtp)
  889. (test-eq "recipient set"
  890. 250 (read-reply-code smtp)) ;250
  891. ;; Send message.
  892. (write-line "DATA" smtp)
  893. (test-eq "data begun"
  894. 354 (read-reply-code smtp)) ;354
  895. (write-line "Subject: Hello" smtp)
  896. (newline smtp)
  897. (write-line "Nice to meet you!" smtp)
  898. (write-line "." smtp)
  899. (test-eq "message sent"
  900. 250 (read-reply-code smtp)) ;250
  901. ;; Say goodbye.
  902. (write-line "QUIT" smtp)
  903. (test-eq "quit successful"
  904. 221 (read-reply-code smtp)) ;221
  905. (close smtp)
  906. (test-eq "the email is received"
  907. 1
  908. (marionette-eval
  909. '(begin
  910. (use-modules (ice-9 ftw))
  911. (length (scandir "/var/spool/exim/msglog"
  912. (lambda (x) (not (string-prefix? "." x))))))
  913. marionette))
  914. (test-end))))
  915. (gexp->derivation "exim-test" test))
  916. (define %test-exim
  917. (system-test
  918. (name "exim")
  919. (description "Send an email to a running an Exim server.")
  920. (value (run-exim-test))))
  921. (define %dovecot-os
  922. (simple-operating-system
  923. (service dhcp-client-service-type)
  924. (dovecot-service #:config
  925. (dovecot-configuration
  926. (disable-plaintext-auth? #f)
  927. (ssl? "no")
  928. (auth-mechanisms '("anonymous"))
  929. (auth-anonymous-username "alice")
  930. (mail-location
  931. (string-append "maildir:~/Maildir"
  932. ":INBOX=~/Maildir/INBOX"
  933. ":LAYOUT=fs"))))))
  934. (define (run-dovecot-test)
  935. "Return a test of an OS running Dovecot service."
  936. (define vm
  937. (virtual-machine
  938. (operating-system (marionette-operating-system
  939. %dovecot-os
  940. #:imported-modules '((gnu services herd))))
  941. (port-forwardings '((8143 . 143)))))
  942. (define test
  943. (with-imported-modules '((gnu build marionette))
  944. #~(begin
  945. (use-modules (gnu build marionette)
  946. (ice-9 iconv)
  947. (ice-9 rdelim)
  948. (rnrs base)
  949. (rnrs bytevectors)
  950. (srfi srfi-64))
  951. (define marionette
  952. (make-marionette '(#$vm)))
  953. (define* (message-length message #:key (encoding "iso-8859-1"))
  954. (bytevector-length (string->bytevector message encoding)))
  955. (define message "From: test@example.com\n\
  956. Subject: Hello Nice to meet you!")
  957. (test-runner-current (system-test-runner #$output))
  958. (test-begin "dovecot")
  959. ;; Wait for dovecot to be up and running.
  960. (test-assert "dovecot running"
  961. (marionette-eval
  962. '(begin
  963. (use-modules (gnu services herd))
  964. (start-service 'dovecot))
  965. marionette))
  966. ;; Check Dovecot service's PID.
  967. (test-assert "service process id"
  968. (let ((pid
  969. (number->string (wait-for-file "/var/run/dovecot/master.pid"
  970. marionette))))
  971. (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
  972. marionette)))
  973. (test-assert "accept an email"
  974. (let ((imap (socket AF_INET SOCK_STREAM 0))
  975. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
  976. (connect imap addr)
  977. ;; Be greeted.
  978. (read-line imap) ;OK
  979. ;; Authenticate
  980. (write-line "a AUTHENTICATE ANONYMOUS" imap)
  981. (read-line imap) ;+
  982. (write-line "c2lyaGM=" imap)
  983. (read-line imap) ;OK
  984. ;; Create a TESTBOX mailbox
  985. (write-line "a CREATE TESTBOX" imap)
  986. (read-line imap) ;OK
  987. ;; Append a message to a TESTBOX mailbox
  988. (write-line (format #f "a APPEND TESTBOX {~a}"
  989. (number->string (message-length message)))
  990. imap)
  991. (read-line imap) ;+
  992. (write-line message imap)
  993. (read-line imap) ;OK
  994. ;; Logout
  995. (write-line "a LOGOUT" imap)
  996. (close imap)
  997. #t))
  998. (test-equal "mail arrived"
  999. message
  1000. (marionette-eval
  1001. '(begin
  1002. (use-modules (ice-9 ftw)
  1003. (ice-9 match))
  1004. (let ((TESTBOX/new "/home/alice/Maildir/TESTBOX/new/"))
  1005. (match (scandir TESTBOX/new)
  1006. (("." ".." message-file)
  1007. (call-with-input-file
  1008. (string-append TESTBOX/new message-file)
  1009. get-string-all)))))
  1010. marionette))
  1011. (test-end))))
  1012. (gexp->derivation "dovecot-test" test))
  1013. (define %test-dovecot
  1014. (system-test
  1015. (name "dovecot")
  1016. (description "Connect to a running Dovecot server.")
  1017. (value (run-dovecot-test))))
  1018. (define %getmail-os
  1019. (operating-system
  1020. (inherit (simple-operating-system))
  1021. ;; Set a password for the user account; the test needs it.
  1022. (users (cons (user-account
  1023. (name "alice")
  1024. (password (crypt "testpass" "$6$abc"))
  1025. (comment "Bob's sister")
  1026. (group "users")
  1027. (supplementary-groups '("wheel" "audio" "video")))
  1028. %base-user-accounts))
  1029. (services (cons* (service dhcp-client-service-type)
  1030. (service dovecot-service-type
  1031. (dovecot-configuration
  1032. (disable-plaintext-auth? #f)
  1033. (ssl? "no")
  1034. (auth-mechanisms '("anonymous" "plain"))
  1035. (auth-anonymous-username "alice")
  1036. (mail-location
  1037. (string-append "maildir:~/Maildir"
  1038. ":INBOX=~/Maildir/INBOX"
  1039. ":LAYOUT=fs"))))
  1040. (service getmail-service-type
  1041. (list
  1042. (getmail-configuration
  1043. (name 'test)
  1044. (user "alice")
  1045. (directory "/var/lib/getmail/alice")
  1046. (idle '("TESTBOX"))
  1047. (rcfile
  1048. (getmail-configuration-file
  1049. (retriever
  1050. (getmail-retriever-configuration
  1051. (type "SimpleIMAPRetriever")
  1052. (server "localhost")
  1053. (username "alice")
  1054. (port 143)
  1055. (extra-parameters
  1056. '((password . "testpass")
  1057. (mailboxes . ("TESTBOX"))))))
  1058. (destination
  1059. (getmail-destination-configuration
  1060. (type "Maildir")
  1061. (path "/home/alice/TestMaildir/")))
  1062. (options
  1063. (getmail-options-configuration
  1064. (read-all #f))))))))
  1065. %base-services))))
  1066. (define (run-getmail-test)
  1067. "Return a test of an OS running Getmail service."
  1068. (define vm
  1069. (virtual-machine
  1070. (operating-system (marionette-operating-system
  1071. %getmail-os
  1072. #:imported-modules '((gnu services herd))))
  1073. (port-forwardings '((8143 . 143)))))
  1074. (define test
  1075. (with-imported-modules '((gnu build marionette))
  1076. #~(begin
  1077. (use-modules (gnu build marionette)
  1078. (ice-9 iconv)
  1079. (ice-9 rdelim)
  1080. (rnrs base)
  1081. (rnrs bytevectors)
  1082. (srfi srfi-64))
  1083. (define marionette
  1084. (make-marionette '(#$vm)))
  1085. (define* (message-length message #:key (encoding "iso-8859-1"))
  1086. (bytevector-length (string->bytevector message encoding)))
  1087. (define message "From: test@example.com\n\
  1088. Subject: Hello Nice to meet you!")
  1089. (test-runner-current (system-test-runner #$output))
  1090. (test-begin "getmail")
  1091. ;; Wait for dovecot to be up and running.
  1092. (test-assert "dovecot running"
  1093. (marionette-eval
  1094. '(begin
  1095. (use-modules (gnu services herd))
  1096. (start-service 'dovecot))
  1097. marionette))
  1098. ;; Wait for getmail to be up and running.
  1099. (test-assert "getmail-test running"
  1100. (marionette-eval
  1101. '(let* ((pw (getpw "alice"))
  1102. (uid (passwd:uid pw))
  1103. (gid (passwd:gid pw)))
  1104. (use-modules (gnu services herd))
  1105. (for-each
  1106. (lambda (dir)
  1107. (mkdir dir)
  1108. (chown dir uid gid))
  1109. '("/home/alice/TestMaildir"
  1110. "/home/alice/TestMaildir/cur"
  1111. "/home/alice/TestMaildir/new"
  1112. "/home/alice/TestMaildir/tmp"
  1113. "/home/alice/TestMaildir/TESTBOX"
  1114. "/home/alice/TestMaildir/TESTBOX/cur"
  1115. "/home/alice/TestMaildir/TESTBOX/new"
  1116. "/home/alice/TestMaildir/TESTBOX/tmp"))
  1117. (start-service 'getmail-test))
  1118. marionette))
  1119. ;; Check Dovecot service's PID.
  1120. (test-assert "service process id"
  1121. (let ((pid
  1122. (number->string (wait-for-file "/var/run/dovecot/master.pid"
  1123. marionette))))
  1124. (marionette-eval `(file-exists? (string-append "/proc/" ,pid))
  1125. marionette)))
  1126. (test-assert "accept an email"
  1127. (let ((imap (socket AF_INET SOCK_STREAM 0))
  1128. (addr (make-socket-address AF_INET INADDR_LOOPBACK 8143)))
  1129. (connect imap addr)
  1130. ;; Be greeted.
  1131. (read-line imap) ;OK
  1132. ;; Authenticate
  1133. (write-line "a AUTHENTICATE ANONYMOUS" imap)
  1134. (read-line imap) ;+
  1135. (write-line "c2lyaGM=" imap)
  1136. (read-line imap) ;OK
  1137. ;; Create a TESTBOX mailbox
  1138. (write-line "a CREATE TESTBOX" imap)
  1139. (read-line imap) ;OK
  1140. ;; Append a message to a TESTBOX mailbox
  1141. (write-line (format #f "a APPEND TESTBOX {~a}"
  1142. (number->string (message-length message)))
  1143. imap)
  1144. (read-line imap) ;+
  1145. (write-line message imap)
  1146. (read-line imap) ;OK
  1147. ;; Logout
  1148. (write-line "a LOGOUT" imap)
  1149. (close imap)
  1150. #t))
  1151. (sleep 1)
  1152. (test-assert "mail arrived"
  1153. (string-contains
  1154. (marionette-eval
  1155. '(begin
  1156. (use-modules (ice-9 ftw)
  1157. (ice-9 match))
  1158. (let ((TESTBOX/new "/home/alice/TestMaildir/new/"))
  1159. (match (scandir TESTBOX/new)
  1160. (("." ".." message-file)
  1161. (call-with-input-file
  1162. (string-append TESTBOX/new message-file)
  1163. get-string-all)))))
  1164. marionette)
  1165. message))
  1166. (test-end))))
  1167. (gexp->derivation "getmail-test" test))
  1168. (define %test-getmail
  1169. (system-test
  1170. (name "getmail")
  1171. (description "Connect to a running Getmail server.")
  1172. (value (run-getmail-test))))