opensmtpd.scm 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445
  1. ;; sudo guix system -L ../guix-packages/ reconfigure sway.scm
  2. ;; use the above to test this code on an actual system.
  3. (define-module (gnu services opensmtpd)
  4. #:use-module (gnu services)
  5. #:use-module (gnu services base)
  6. #:use-module (gnu services configuration)
  7. #:use-module (gnu services shepherd)
  8. #:use-module (gnu system pam)
  9. #:use-module (gnu system shadow)
  10. #:use-module (gnu packages mail)
  11. #:use-module (gnu packages admin)
  12. #:use-module (gnu packages dav)
  13. #:use-module (gnu packages tls)
  14. #:use-module (guix records)
  15. #:use-module (guix packages)
  16. #:use-module (guix gexp)
  17. #:use-module (ice-9 match)
  18. #:use-module (ice-9 format)
  19. #:use-module (srfi srfi-1)
  20. #:export (opensmtpd-service-type
  21. opensmtpd-table
  22. opensmtpd-table?
  23. opensmtpd-table-name
  24. opensmtpd-table-file
  25. opensmtpd-table-file-db
  26. opensmtpd-table-values
  27. opensmtpd-ca
  28. opensmtpd-ca?
  29. opensmtpd-name
  30. opensmtpd-file
  31. opensmtpd-pki
  32. opensmtpd-pki?
  33. opensmtpd-pki-domain
  34. opensmtpd-pki-cert
  35. opensmtpd-pki-key
  36. opensmtpd-local-delivery-configuration
  37. opensmtpd-local-delivery-configuration?
  38. opensmtpd-local-delivery-configuration-method
  39. opensmtpd-local-delivery-configuration-alias
  40. opensmtpd-local-delivery-configuration-ttl
  41. opensmtpd-local-delivery-configuration-user
  42. opensmtpd-local-delivery-configuration-userbase
  43. opensmtpd-local-delivery-configuration-virtual
  44. opensmtpd-local-delivery-configuration-wrapper
  45. openmstpd-relay-configuration
  46. opensmtpd-relay-configuration?
  47. openmstpd-relay-configuration-backup
  48. openmstpd-relay-configuration-helo
  49. openmstpd-relay-configuration-domain
  50. openmstpd-relay-configuration-host
  51. openmstpd-relay-configuration-pki
  52. openmstpd-relay-configuration-srs
  53. openmstpd-relay-configuration-tls
  54. openmstpd-relay-configuration-protocols
  55. openmstpd-relay-configuration-ciphers
  56. openmstpd-relay-configuration-auth
  57. openmstpd-relay-configuration-mail
  58. openmstpd-relay-configuration-src
  59. opensmtpd-action
  60. opensmtpd-action?
  61. opensmtpd-action-name
  62. opensmtpd-action-method
  63. opensmtpd-filter-chain
  64. opensmtpd-filter-chain?
  65. opensmtpd-filter-chain-name
  66. opensmtpd-filter-chain-filter-names
  67. opensmtpd-filter-phase
  68. opensmtpd-filter-phase?
  69. opensmtpd-filter-phase-name
  70. opensmtpd-filter-phase-phase-name
  71. opensmtpd-filter-phase-conditions
  72. opensmtpd-filter-phase-decision
  73. opensmtpd-filter-phase-message
  74. opensmtpd-filter-phase-value
  75. opensmtpd-filter-proc
  76. opensmtpd-filter-proc?
  77. opensmtpd-filter-proc-name
  78. opensmtpd-filter-proc-command
  79. opensmtpd-filter-proc-exec
  80. opensmtpd-filter-proc-exec?
  81. opensmtpd-filter-proc-exec-name
  82. opensmtpd-filter-proc-exec-command
  83. opensmtpd-listen-on
  84. opensmtpd-listen-on?
  85. opensmtpd-listen-on-interface
  86. opensmtpd-listen-on-auth
  87. opensmtpd-listen-on-auth-optional
  88. opensmtpd-listen-on-filter
  89. opensmtpd-listen-on-hostname
  90. opensmtpd-listen-on-hostnames
  91. opensmtpd-listen-on-mask-src
  92. opensmtpd-listen-on-no-dsn
  93. opensmtpd-listen-on-pki
  94. opensmtpd-listen-on-port
  95. opensmtpd-listen-on-proxy-v2
  96. opensmtpd-listen-on-received-auth
  97. opensmtpd-listen-on-senders
  98. opensmtpd-listen-on-secure-connection
  99. opensmtpd-listen-on-tag
  100. opensmtpd-listen-on-protocols
  101. opensmtpd-listen-on-ciphers
  102. opensmtpd-listen-on-socket
  103. opensmtpd-listen-on-socket?
  104. opensmtpd-listen-on-socket-filter
  105. opensmtpd-listen-on-socket-mask-src
  106. opensmtpd-listen-on-socket-tag
  107. opensmtpd-match
  108. opensmtpd-match?
  109. opensmtpd-match-name
  110. opensmtpd-match-for
  111. opensmtpd-match-from
  112. opensmtpd-match-auth
  113. opensmtpd-match-helo
  114. opensmtpd-match-mail-from
  115. opensmtpd-match-rcpt-to
  116. opensmtpd-match-tag
  117. opensmtpd-match-tls
  118. opensmtpd-proc
  119. opensmtpd-proc?
  120. opensmtpd-proc-name
  121. opensmtpd-proc-command
  122. opensmtpd-smtp-configuration
  123. opensmtpd-smtp-configuration?
  124. opensmtpd-smtp-configuration-ciphers
  125. opensmtpd-smtp-configuration-limit-max-mails
  126. opensmtpd-smtp-configuration-limit-max-rcpt
  127. opensmtpd-smtp-configuration-max-message-size
  128. opensmtpd-smtp-configuration-sub-addr-delim character
  129. opensmtpd-srs-configuration
  130. opensmtpd-srs-configuration?
  131. opensmtpd-srs-configuration-key
  132. opensmtpd-srs-configuration-key-backup
  133. opensmtpd-srs-configuration-ttl-delay
  134. opensmtpd-queue-configuration
  135. opensmtpd-queue-configuration?
  136. opensmtpd-queue-configuration-compression
  137. opensmtpd-queue-configuration-encryption
  138. opensmtpd-queue-configuration-ttl-delay
  139. opensmtpd-configuration
  140. opensmtpd-configuration?
  141. opensmtpd-package
  142. opensmtpd-config-file
  143. opensmtpd-configuration-actions
  144. opensmtpd-configuration-bounce
  145. opensmtpd-configuration-filter-chains
  146. opensmtpd-configuration-filter-phases
  147. opensmtpd-configuration-filter-procs
  148. opensmtpd-configuration-filter-proc-execs
  149. opensmtpd-configuration-listen-ons
  150. opensmtpd-configuration-listen-on-socket
  151. opensmtpd-configuration-includes
  152. opensmtpd-configuration-matches
  153. opensmtpd-configuration-mda-wrappers
  154. opensmtpd-configuration-pkis
  155. opensmtpd-configuration-procs
  156. opensmtpd-configuration-tables))
  157. ;; some fieldnames have a default value of #f, which is ok. They cannot have a value of #t.
  158. ;; for example opensmtpd-table-values can be #f, BUT NOT true.
  159. ;; my/sanitize procedure tests values to see if they are of the right kind.
  160. ;; procedure false? is needed to allow fields like 'values' to be blank, (empty), or #f BUT also
  161. ;; have a value like a list of strings.
  162. (define (false? value)
  163. (eq? #f value))
  164. (define (is-value-right-type? value list-of-procedures)
  165. (let loop ([list-of-procedures list-of-procedures])
  166. (if (null? list-of-procedures)
  167. #f
  168. (begin
  169. (if ((car list-of-procedures) value)
  170. #t
  171. (loop (cdr list-of-procedures)))))))
  172. ;; converts strings like this:
  173. ;; "apple, ham, cherry" -> "apple, ham, or cherry"
  174. ;; "pineapple" -> "pinneapple".
  175. ;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam"
  176. (define (add-comma-or string)
  177. (define last-comma-location (string-rindex string #\,))
  178. (if last-comma-location
  179. (if (string-contains string ", or" last-comma-location)
  180. string
  181. (string-replace string ", or" last-comma-location (+ 1 last-comma-location)))
  182. string))
  183. ;; FIXME/TODO combine this funcion with list-of-strings->string
  184. (define (strings->string list)
  185. (add-comma-or
  186. (string-append
  187. (string-drop-right
  188. (string-append "any of these strings: "
  189. (let loop ([list list])
  190. (if (null? list)
  191. ""
  192. (string-append
  193. (car list) ", "
  194. (loop (cdr list))))))
  195. 2)
  196. " ")))
  197. ;; I could test for read-ability of a file, but then I would have to
  198. ;; test the program as root everytime instead of as a normal user...
  199. (define (file-exists? file)
  200. (access? file F_OK))
  201. (define (list-of-procedures->string procedures)
  202. (define string
  203. (let loop ([procedures procedures])
  204. (if (null? procedures)
  205. ""
  206. (begin
  207. (string-append
  208. ;; FIXME/TODO add a guess function that takes a look at the
  209. ;; function... eg: list-of-opensmtpd-proc? -> "a list of
  210. ;; <opensmtpd-proc>s, "
  211. (cond [(eq? false? (car procedures))
  212. "#f , "]
  213. [(eq? boolean? (car procedures))
  214. "boolean, "]
  215. [(eq? string? (car procedures))
  216. "string, "]
  217. [(eq? integer? (car procedures))
  218. "integer, "]
  219. [(eq? list-of-strings? (car procedures))
  220. "list of strings, "]
  221. [(eq? assoc-list? (car procedures))
  222. "an association list, "]
  223. [(eq? file-exists? (car procedures))
  224. "file, "]
  225. [(eq? list-of-opensmtpd-filter-phase? (car procedures))
  226. "a list of <opensmtpd-filter-phase>s, "]
  227. [(eq? list-of-opensmtpd-filter-chain? (car procedures))
  228. "a list of <opensmtpd-filter-chain>s, "]
  229. [(eq? list-of-opensmtpd-proc? (car procedures))
  230. "a list of <opensmtpd-proc>s, "]
  231. [else
  232. (display "You've got some procedure that you don't know about.\n")
  233. (display (car procedures))
  234. (display "\n")
  235. (throw 'bad! (car procedures))])
  236. (loop (cdr procedures)))))))
  237. (add-comma-or (string-append (string-drop-right string 2) ".\n")))
  238. ;; TODO/FIXME? write a procedure (define (string-in-list? string
  239. ;; list)) The procedure will be used to sanitize the few values that
  240. ;; whose strings can be certain contrained
  241. ;; strings. opensmtpd-listen-on would use such a procedure.
  242. (define (my/sanitize value table fieldname list-of-procedures)
  243. (if (is-value-right-type? value list-of-procedures)
  244. value
  245. (begin
  246. (display (string-append "<" table "> fieldname: '" fieldname "' is of type "
  247. (list-of-procedures->string list-of-procedures) "\n"))
  248. (throw 'bad! value))))
  249. ;; FIXME/TODO? It is possible to create a table like this (opensmtpd-table (name "table")),
  250. ;; which is a table with no values or file, which is no table at all, and will result
  251. ;; in the service not starting. This is probably not worth fixing. Only silly users
  252. ;; would try to create a table with no values or file.
  253. (define-record-type* <opensmtpd-table>
  254. opensmtpd-table make-opensmtpd-table
  255. opensmtpd-table?
  256. this-record
  257. ;; string
  258. (name opensmtpd-table-name
  259. (default #f)
  260. (sanitize (lambda (value)
  261. (my/sanitize value "opensmtpd-table" "name" (list string?)))))
  262. (file opensmtpd-table-file
  263. (default #f)
  264. (sanitize (lambda (value)
  265. (my/sanitize value "opensmtpd-table" "file" (list false? string?)))))
  266. ;; is a list of values or key values
  267. ;; eg: (list "mysite.me" "your-site.com")
  268. ;; eg: (list ("joshua" . "joshua@gnu.org") ("james" . "james@gnu.org"))
  269. ;; I am currently making these values be as assocation list of strings only.
  270. ;; FIXME should I allow a value like this?
  271. ;; (list (cons "gnucode.me" 234.949.392.23))
  272. (file-db opensmtpd-table-file-db
  273. (default #f)
  274. (sanitize (lambda (value)
  275. (my/sanitize value "opensmtpd-table" "file-db" (list boolean?)))))
  276. ;; FIXME support an aliasing table as described here:
  277. ;; https://man.openbsd.org/table.5
  278. ;; One may have to use the record file for this. I don't think tables support a table like this:
  279. ;; table "name" { joshua = joshua@gnucode.me,joshua@gnu-hurd.com,joshua@propernaming.org, root = root@gnucode.me }
  280. (values opensmtpd-table-values
  281. (default #f)
  282. (sanitize (lambda (value)
  283. (my/sanitize value "opensmtpd-table" "values" (list false? list-of-strings? assoc-list?))
  284. )))
  285. ;; can be of type: (quote list-of-strings) or (quote assoc-list)
  286. ;; (opensmtpd-table-type record) returns the values' type. The user SHOULD NEVER set the type.
  287. ;; TODO jpoiret: on irc reccomends that I just use an outside function to determine fieldname 'values', type.
  288. ;; it would be "simpler" and possibly easier for the next person working on this code to understand what is happening.
  289. (type opensmtpd-table-type
  290. (default #f)
  291. (thunked)
  292. (sanitize (lambda (value)
  293. (cond [(opensmtpd-table-values this-record)
  294. (if (list-of-strings? (opensmtpd-table-values this-record))
  295. (quote list-of-strings)
  296. (quote assoc-list))]
  297. [(opensmtpd-table-file this-record)
  298. (if (opensmtpd-table-file-db this-record)
  299. (quote db)
  300. (quote file))]
  301. [else
  302. (display "opensmtpd-table-type is broke\n")
  303. (throw 'bad! value)])))))
  304. (define-record-type* <opensmtpd-ca>
  305. opensmtpd-ca make-opensmtpd-ca
  306. opensmtpd-ca?
  307. (name opensmtpd-ca-name
  308. (default #f)
  309. (sanitize (lambda (value)
  310. (my/sanitize value "opensmtpd-ca" "name" (list string?)))))
  311. (file opensmtpd-ca-file
  312. (default #f)
  313. (sanitize (lambda (value)
  314. (my/sanitize value "opensmtpd-ca" "file" (list string? file-exists?))))))
  315. (define-record-type* <opensmtpd-pki>
  316. opensmtpd-pki make-opensmtpd-pki
  317. opensmtpd-pki?
  318. (domain opensmtpd-pki-domain
  319. (default #f)
  320. (sanitize (lambda (value)
  321. (my/sanitize value "opensmtpd-pki" "domain" (list string?)))))
  322. (cert opensmtpd-pki-cert
  323. (default #f)
  324. (sanitize (lambda (value)
  325. (my/sanitize value "opensmtpd-pki" "cert" (list string? file-exists?)))))
  326. (key opensmtpd-pki-key
  327. (default #f)
  328. (sanitize (lambda (value)
  329. (my/sanitize value "opensmtpd-pki" "key" (list string? file-exists?))))))
  330. (define-record-type* <opensmtpd-lmtp-configuration>
  331. opensmtpd-lmtp-configuration make-opensmtpd-lmtp-configuration
  332. opensmtpd-lmtp-configuration?
  333. (destination opensmtpd-lmtp-configuration-destination
  334. (default #f)
  335. (sanitize (lambda (value)
  336. (my/sanitize value "opensmtpd-lmtp-configuration" "destination" (list string?)))))
  337. (rcpt-to opensmtpd-lmtp-configuration-rcpt-to
  338. (default #f)
  339. (sanitize (lambda (value)
  340. (my/sanitize value "opensmtpd-lmtp-configuration" "rcpt-to" (list false? string?))))))
  341. (define-record-type* <opensmtpd-mda-configuration>
  342. opensmtpd-mda-configuration make-opensmtpd-mda-configuration
  343. opensmtpd-mda-configuration?
  344. (command opensmtpd-mda-configuration-command
  345. (default #f)
  346. (sanitize (lambda (value)
  347. (my/sanitize value "opensmtpd-mda-configuration" "command" (list string?))))))
  348. (define-record-type* <opensmtpd-maildir-configuration>
  349. opensmtpd-maildir-configuration make-opensmtpd-maildir-configuration
  350. opensmtpd-maildir-configuration?
  351. (pathname opensmtpd-maildir-configuration-pathname
  352. (default #f)
  353. (sanitize (lambda (value)
  354. (my/sanitize value "opensmtpd-maildir-configuration" "pathname" (list false? string?)))))
  355. (junk opensmtpd-maildir-configuration-junk
  356. (default #f)
  357. (sanitize (lambda (value)
  358. (my/sanitize value "opensmtpd-maildir-configuration" "junk" (list boolean?))))))
  359. (define-record-type* <opensmtpd-local-delivery-configuration>
  360. opensmtpd-local-delivery-configuration make-opensmtpd-local-delivery-configuration
  361. opensmtpd-local-delivery-configuration?
  362. ;; method can be "mbox", "expand-only", "forward-only", <opensmtpd-lmtp>, <opensmtpd-maildir>, <opensmtpd-mda>
  363. ;; TODO let method be a list of any of the above types...should I do this? does that make sense?
  364. (method opensmtpd-local-delivery-configuration-method
  365. (default "mbox")
  366. (sanitize (lambda (value)
  367. (cond
  368. [(or (opensmtpd-lmtp-configuration? value)
  369. (opensmtpd-maildir-configuration? value)
  370. (opensmtpd-mda-configuration? value)
  371. (string=? value "mbox")
  372. (string=? value "expand-only")
  373. (string=? value "forward-only"))
  374. value]
  375. [else
  376. (begin
  377. (display (string-append "<opensmtpd-local-delivery-configuration> fieldname 'method' must be of type "
  378. "\"mbox\", \"expand-only\", \"forward-only\" "
  379. "<opensmtpd-lmtp-configuration>, <opensmtpd-maildir-configuration>, "
  380. "or <opensmtpd-mda-configuration>.\n"))
  381. (throw 'bad! value))]))))
  382. ;; string opensmtpd-table-configuration-name
  383. (alias opensmtpd-local-delivery-configuration-alias
  384. (default #f)
  385. (sanitize (lambda (value)
  386. (my/sanitize value "opensmtpd-local-delivery-configuration" "alias" (list false? string?)))))
  387. ;; string
  388. (ttl opensmtpd-local-delivery-configuration-ttl
  389. (default #f)
  390. (sanitize (lambda (value)
  391. (my/sanitize value "opensmtpd-local-delivery-configuration" "ttl" (list false? string?)))))
  392. (user opensmtpd-local-delivery-configuration-user
  393. (default #f)
  394. (sanitize (lambda (value)
  395. (my/sanitize value "opensmtpd-local-delivery-configuration" "user" (list false? string?)))))
  396. ;; needs to be of type string
  397. (userbase opensmtpd-local-delivery-configuration-userbase
  398. (default #f)
  399. (sanitize (lambda (value)
  400. (my/sanitize value "opensmtpd-local-delivery-configuration" "userbase" (list false? string?)))))
  401. (virtual opensmtpd-local-delivery-configuration-virtual
  402. (default #f)
  403. (sanitize (lambda (value)
  404. (my/sanitize value "opensmtpd-local-delivery-configuration" "virtual" (list false? string?)))))
  405. (wrapper opensmtpd-local-delivery-configuration-wrapper
  406. (default #f)
  407. (sanitize (lambda (value)
  408. (my/sanitize value "opensmtpd-local-delivery-configuration" "wrapper" (list false? string?))))))
  409. (define-record-type* <opensmtpd-relay-configuration>
  410. opensmtpd-relay-configuration make-opensmtpd-relay-configuration
  411. opensmtpd-relay-configuration?
  412. (backup opensmtpd-relay-configuration-backup ;; boolean
  413. (default #f)
  414. (sanitize (lambda (value)
  415. (my/sanitize value "opensmtpd-relay-configuration" "backup" (list boolean?)))))
  416. (backup-mx opensmtpd-relay-configuration-backup-mx ;; string mx name
  417. (default #f)
  418. (sanitize (lambda (value)
  419. (my/sanitize value "opensmtpd-relay-configuration" "backup-mx" (list false? string?)))))
  420. ;; string or heloname or <table name>
  421. ;; this combines options helo or helo-src
  422. (helo opensmtpd-relay-configuration-helo
  423. (default #f))
  424. ;; string domain OR <domains>
  425. (domain opensmtpd-relay-configuration-domain
  426. (default #f))
  427. ;; string
  428. (host opensmtpd-relay-configuration-host
  429. (default #f))
  430. ;; string or <opensmtpd-pki-configuration> string could be "gnucode.me", which is the
  431. ;; domain fieldname of <opensmtpd-pki-configuration>.
  432. (pki opensmtpd-relay-configuration-pki
  433. (default #f)
  434. (sanitize (lambda (value)
  435. (my/sanitize value "opensmtpd-relay-configuration" "pki" (list false? string?)))))
  436. ;; boolean
  437. (srs opensmtpd-relay-configuration-srs
  438. (default #f)
  439. (lambda (value)
  440. (my/sanitize value "opensmtpd-relay-configuration" "srs" (list boolean?))))
  441. ;; boolean or no-verify
  442. (tls opensmtpd-relay-configuration-tls
  443. (default #f)
  444. (sanitize (lambda (value)
  445. (my/sanitize value "opensmtpd-relay-configuration" "tls" (list false? string?)))))
  446. ;; string
  447. (protocols opensmtpd-relay-configuration-protocols
  448. (default #f)
  449. (sanitize (lambda (value)
  450. (my/sanitize value "opensmtpd-relay-configuration" "protocols" (list false? string?)))))
  451. ;; string
  452. (ciphers opensmtpd-relay-configuration-ciphers
  453. (default #f)
  454. (sanitize (lambda (value)
  455. (my/sanitize value "opensmtpd-relay-configuration" "ciphers" (list false? string?)))))
  456. ;; string like "<table>"
  457. (auth opensmtpd-relay-configuration-auth
  458. (default #f))
  459. (mail-from opensmtpd-relay-configuration-mail-from
  460. (default #f))
  461. ;; string "sourceaddr" or "<sourceadd>"
  462. (src opensmtpd-relay-configuration-src
  463. (default #f)))
  464. (define-record-type* <opensmtpd-action>
  465. opensmtpd-action make-opensmtpd-action
  466. opensmtpd-action?
  467. this-record
  468. (name opensmtpd-action-name
  469. (default "local"))
  470. ;; TODO add support for forward-only and expand-only
  471. ;; type <opensmtpd-local-delivery-configuration> or <opensmtpd-relay-configuration>
  472. ;; ;; local-delivery has a default value so (service opensmtpd-service) will just work for
  473. ;; ;; local email delivery
  474. (method opensmtpd-action-method
  475. (default (opensmtpd-local-delivery-configuration))
  476. (sanitize (lambda (value)
  477. (my/sanitize value "opensmtpd-action" "method"
  478. (list opensmtpd-relay-configuration?
  479. opensmtpd-local-delivery-configuration?))))))
  480. ;; FIXME/TODO Perhaps it would be nice in the future to change filter names to "filters".
  481. ;; Then "filters" could either be a list of filter names, OR it could be a list of types
  482. ;; <opensmtpd-filter-phase>, <opensmtpd-filter-proc>, or <opensmtpd-filter-proc-exec>.
  483. ;; BUT then I would have to write more code...and it would get complicated. Seems like
  484. ;; to much work.
  485. ;;list of many records of type opensmtpd-filter-chain
  486. ;; FIXME/TODO? Perhaps I could make filter-chains accept a list like this:
  487. ;; (list (filter-chain (name "chain-filter")
  488. ;; (filters (list (filter-phase
  489. ;; (name "phase")
  490. ;; (conditions (list "rdns")))
  491. ;; (filter-proc-exec
  492. ;; (name "process")
  493. ;; (command "this command"))))))
  494. ;; then I could get rid of fieldnames filter-phases, filter-procs, and filter-proc-execs from
  495. ;; opensmtpd-configuration
  496. (define-record-type* <opensmtpd-filter-chain>
  497. opensmtpd-filter-chain make-opensmtpd-filter-chain
  498. opensmtpd-filter-chain?
  499. ;; string chain name
  500. (name opensmtpd-filter-chain-name
  501. (default "filter-chain")
  502. (sanitize
  503. (lambda (value)
  504. (my/sanitize value "opensmtpd-filter-chain" "chain-name" (list string?)))))
  505. ;; list of strings of filter-name
  506. ;; maybe someday this could support record types
  507. ;; <opensmtpd-filter-proc>, <opensmtpd-filter-proc-exec>, or <opensmtpd-filter-phase>
  508. (filter-names opensmtpd-filter-chain-filter-names
  509. (default #f)
  510. (sanitize
  511. (lambda (value)
  512. (my/sanitize value "opensmtpd-filter-chain" "filter-names" (list list-of-strings?))))))
  513. (define-record-type* <opensmtpd-filter-phase>
  514. opensmtpd-filter-phase make-opensmtpd-filter-phase
  515. opensmtpd-filter-phase?
  516. (name opensmtpd-filter-phase-name ;; string chain-name
  517. (default "filter-chain")
  518. (sanitize (lambda (value)
  519. (my/sanitize value "opensmtpd-filter-phase" "name" (list string?)))))
  520. (phase-name opensmtpd-filter-phase-phase-name ;; string
  521. (default #f)
  522. (sanitize (lambda (value)
  523. (my/sanitize value "opensmtpd-filter-phase" "phase-name" (list string?)))))
  524. (conditions opensmtpd-filter-phase-conditions
  525. (default #f)
  526. (sanitize (lambda (value)
  527. (my/sanitize value "opensmtpd-filter-phase" "conditions-name" (list list-of-strings?)))))
  528. (decision opensmtpd-filter-phase-decision
  529. (default #f)
  530. (sanitize (lambda (value)
  531. (my/sanitize value "opensmtpd-filter-phase" "decision"
  532. (list
  533. (lambda (value)
  534. (if (or (string=? "bypass" value)
  535. (string=? "disconnect" value)
  536. (string=? "reject" value)
  537. (string=? "rewrite" value)
  538. (string=? "junk" value))
  539. value
  540. #f)))))))
  541. (message opensmtpd-filter-phase-message
  542. (default #f)
  543. (sanitize (lambda (value)
  544. (my/sanitize value "opensmtpd-filter-phase" "message" (list false? string?)))))
  545. (value opensmtpd-filter-phase-value
  546. (default #f)
  547. (sanitize (lambda (value)
  548. (my/sanitize value "opensmtpd-filter-phase" "value" (list false? integer?))))))
  549. (define-record-type* <opensmtpd-filter-proc>
  550. opensmtpd-filter-proc make-opensmtpd-filter-proc
  551. opensmtpd-filter-proc?
  552. (name opensmtpd-filter-proc-name
  553. (default #f)
  554. (sanitize (lambda (value)
  555. (my/sanitize value "opensmtpd-filter-proc" "name" (list? string?)))))
  556. (proc-name opensmtpd-proc-proc-name
  557. (default #f)
  558. (sanitize (lambda (value)
  559. (my/sanitize value "opensmtpd-filter-proc" "name" (list? string?))))))
  560. (define-record-type* <opensmtpd-filter-proc-exec>
  561. opensmtpd-filter-proc-exec make-opensmtpd-filter-proc-exec
  562. opensmtpd-filter-proc-exec?
  563. (name opensmtpd-filter-proc-exec-name
  564. (default #f)
  565. (sanitize (lambda (value)
  566. (my/sanitize value "opensmtpd-filter-proc-exec" "name" (list string?)))))
  567. ;; FIXME/TODO how do I let this accept a list of file-like objects and strings?
  568. (command opensmtpd-filter-proc-exec-command
  569. (default #f)
  570. (sanitize (lambda (value)
  571. (my/sanitize value "opensmtpd-filter-proc-exec" "command" (list string?))))))
  572. (define-record-type* <opensmtpd-listen-on>
  573. opensmtpd-listen-on make-opensmtpd-listen-on
  574. opensmtpd-listen-on?
  575. ;; interface may be an IP address, interface group, or domain name
  576. (interface opensmtpd-listen-on-interface
  577. (default "lo"))
  578. ;;FIXME/TODO? should I convert this lambda into a (my/sanitize) invocation?
  579. ;; NO. It's not worth the effort, and the code is cleaner/easier to understand without it.
  580. ;; I would have to make a string-in-strings? procedure, which I would use twice...
  581. ;; register that function with list-of-procedures->string
  582. ;; and modify my/sanitize to accept a procedure which accepts an argument.
  583. ;; eg: (my/sanitize value "opensmtpd-match" "decision" (list (cons string-in-strings? (list "reject" "accept"))
  584. (family opensmtpd-listen-on-family
  585. (default #f)
  586. (sanitize (lambda (value)
  587. (cond
  588. [(eq? #f value) ;; value == #f
  589. value]
  590. [(and (string? value)
  591. (or (string=? "inet4" value)
  592. (string=? "inet6" value)))
  593. value]
  594. [else
  595. (begin
  596. (display "<opensmtpd-listen-on> fieldname 'family' must be string \"inet4\" or \"inet6\".\n")
  597. (throw 'bad! value))]))))
  598. ;; this is a string of <authtable>
  599. (auth opensmtpd-listen-on-auth
  600. (default #f)
  601. (sanitize (lambda (value)
  602. (my/sanitize value "opensmtpd-listen-on" "auth" (list boolean? string?)))))
  603. (auth-optional opensmtpd-listen-on-auth-optional
  604. (default #f)
  605. (sanitize (lambda (value)
  606. (my/sanitize value "opensmtpd-listen-on" "auth-optional" (list boolean? string?)))))
  607. ;; do I need a ca entry?
  608. ;; string
  609. (filter opensmtpd-listen-on-filter
  610. (default #f))
  611. ;; string
  612. (hostname opensmtpd-listen-on-hostname
  613. (default #f)
  614. (sanitize (lambda (value)
  615. (my/sanitize value "opensmtpd-listen-on" "hostname" (list false? string?)))))
  616. ;; string of type <table>
  617. (hostnames opensmtpd-listen-on-hostnames
  618. (default #f)
  619. (sanitize (lambda (value)
  620. (my/sanitize value "opensmtpd-listen-on" "hostnames" (list false? string?)))))
  621. (mask-src opensmtpd-listen-on-mask-src
  622. (default #f)
  623. (sanitize (lambda (value)
  624. (my/sanitize value "opensmtpd-listen-on" "mask-src" (list boolean?)))))
  625. (no-dsn opensmtpd-listen-on-no-dsn
  626. (default #f))
  627. ;; string or pki record
  628. (pki opensmtpd-listen-on-pki
  629. (default #f)
  630. (sanitize (lambda (value)
  631. (my/sanitize value "opensmtpd-listen-on" "pki" (list false? string?)))))
  632. (port opensmtpd-listen-on-port
  633. (default #f)
  634. (sanitize (lambda (value)
  635. (my/sanitize value "opensmtpd-listen-on" "port" (list false? integer?)))))
  636. (proxy-v2 opensmtpd-listen-on-proxy-k2
  637. (default #f))
  638. (received-auth opensmtpd-listen-on-received-auth
  639. (default #f))
  640. ;; string or <opensmtpd-senders> record
  641. (senders opensmtpd-listen-on-senders
  642. (default #f))
  643. (secure-connection opensmtpd-listen-on-secure-connection
  644. (default #f)
  645. (sanitize (lambda (value)
  646. (cond [(boolean? value)
  647. value]
  648. [(and (string? value)
  649. (or (string=? "smtps" value)
  650. (string=? "tls" value)
  651. (string=? "tls-require" value)
  652. (string=? "tls-require-verify" value)))
  653. value]
  654. [else
  655. (begin
  656. (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be "
  657. "one of the following strings: \n'smtps', 'tls', 'tls-require', "
  658. "or 'tls-require-verify'.\n"))
  659. (throw 'bad! value))]))))
  660. ;; string
  661. (tag opensmtpd-listen-on-tag
  662. (default #f))
  663. (protocols opensmtpd-listen-on-protocols
  664. (default #f))
  665. (ciphers opensmtpd-listen-on-ciphers
  666. (default #f)))
  667. (define-record-type* <opensmtpd-listen-on-socket-configuration>
  668. opensmtpd-listen-on-socket-configuration make-opensmtpd-listen-on-socket-configuration
  669. opensmtpd-listen-on-socket-configuration?
  670. ;; string or <opensmtpd-filter> record or false
  671. (filter opensmtpd-listen-on-socket-configuration-filter
  672. (default #f))
  673. (mask-src opensmtpd-listen-on-socket-configuration-mask-src
  674. (default #f))
  675. ;; string
  676. (tag opensmtpd-listen-on-socket-configuration-tag
  677. (default #f)))
  678. (define-record-type* <opensmtpd-match>
  679. opensmtpd-match make-opensmtpd-match
  680. opensmtpd-match?
  681. (name opensmtpd-match-name ;; name is a string OR it is (quote reject), in which case the match
  682. (default #f) ;; rejects the incoming connection/envelope.
  683. (sanitize (lambda (value)
  684. (if (or (string? value)
  685. (eq? (quote reject) value))
  686. value
  687. (begin
  688. (display
  689. (string-append "<opensmtpd-match> fieldname 'name' is of type string or '(quote reject).\n"
  690. "If its value is '(quote reject), then the match rejects the incoming message\n"
  691. "during the SMTP dialogue.\n"))
  692. (throw 'bad! value))))))
  693. ;;FIXME/TODO? Perhaps I should add in a reject fieldname. If reject
  694. ;;is #t, then the match record will be a reject match record. That
  695. ;;way it's less confusing for users to type in
  696. ;; (opensmtpd-match (name 'reject)).
  697. ;; BUT then I have to make fieldnames 'name' and 'reject' mutually exclusive.
  698. ;; #f (empty) or a string
  699. ;; eg: "for any" "! for local" "for domain gnucode.me" "! for domain <domains>"
  700. ;; FIXME/TODO should I properly sanitize this? any string works now...
  701. (for opensmtpd-match-for
  702. (default #f)
  703. (sanitize (lambda (value)
  704. (my/sanitize value "opensmtpd-match" "for" (list false? string?)))))
  705. ;; #f (empty) or a string
  706. ;; eg: "from any" "from auth" "from auth user" "! from auth <users>" "from local" "! from rdns"
  707. (from opensmtpd-match-from
  708. (default #f)
  709. (sanitize (lambda (value)
  710. (my/sanitize value "opensmtpd-match" "from" (list false? string?)))))
  711. (auth opensmtpd-match-auth
  712. (default #f)
  713. (sanitize (lambda (value)
  714. (my/sanitize value "opensmtpd-match" "auth" (list false? string?)))))
  715. (helo opensmtpd-match-helo
  716. (default #f)
  717. (sanitize (lambda (value)
  718. (my/sanitize value "opensmtpd-match" "helo" (list false? string?)))))
  719. (mail-from opensmtpd-match-mail-from
  720. (default #f)
  721. (sanitize (lambda (value)
  722. (my/sanitize value "opensmtpd-match" "mail-from" (list false? string?)))))
  723. (rcpt-to opensmtpd-match-rcpt-to
  724. (default #f)
  725. (sanitize (lambda (value)
  726. (my/sanitize value "opensmtpd-match" "rcpt-to" (list false? string?)))))
  727. (tag opensmtpd-match-tag
  728. (default #f)
  729. (sanitize (lambda (value)
  730. (my/sanitize value "opensmtpd-match" "tag" (list false? string?)))))
  731. ;; hmmm. How should I handle this?
  732. ;; #f would mean "! tls", #nil would mean nothing, and #t would mean "tls"
  733. ;; FIXME/TODO, this default value should be #nil or '(), but the last time I did that,
  734. ;; reconfiguring failed...
  735. (tls opensmtpd-match-tls
  736. (default '())
  737. (sanitize (lambda (value)
  738. (my/sanitize value "opensmtpd-match" "tls" (list boolean? null?))))))
  739. ;; this is for registering an already running process for opensmtpd
  740. (define-record-type* <opensmtpd-proc>
  741. opensmtpd-proc make-opensmtpd-proc
  742. opensmtpd-proc?
  743. (name opensmtpd-proc-name
  744. (default #f)
  745. (sanitize (lambda (value)
  746. (my/sanitize value "opensmtpd-proc" "name" (list false? string?)))))
  747. (command opensmtpd-proc-command
  748. (default #f)
  749. (sanitize (lambda (value)
  750. (my/sanitize value "opensmtpd-proc" "command" (list false? string?))))))
  751. (define-record-type* <opensmtpd-smtp-configuration>
  752. opensmtpd-smtp-configuration make-opensmtpd-smtp-configuration
  753. opensmtpd-smtp-configuration?
  754. (ciphers opensmtpd-configuration-ciphers
  755. (default #f)
  756. (sanitize (lambda (value)
  757. (my/sanitize value "opensmtpd-smtp-configuration" "ciphers" (list false? string?)))))
  758. (limit-max-mails opensmtpd-smtp-configuration-limit-max-mails
  759. (default #f)
  760. (sanitize (lambda (value)
  761. (my/sanitize value "opensmtpd-smtp-configuration" "limit-max-mails" (list false? integer?)))))
  762. (limit-max-rcpt opensmtpd-smtp-configuration-limit-max-rcpt
  763. (default #f)
  764. (sanitize (lambda (value)
  765. (my/sanitize value "opensmtpd-smtp-configuration" "limit-max-rcpt" (list false? integer?)))))
  766. (max-message-size opensmtpd-smtp-configuration-max-message-size
  767. (default #f)
  768. (sanitize (lambda (value)
  769. (my/sanitize value "opensmtpd-smtp-configuration" "max-message-size"
  770. (list false? integer? string?)))))
  771. ;; FIXME/TODO the sanitize function of sub-addr-delim should accept a string of length one not string?
  772. (sub-addr-delim opensmtpd-smtp-configuration-sub-addr-delim
  773. (default #f)
  774. (sanitize (lambda (value)
  775. (my/sanitize value "opensmtpd-smtp-configuration" "sub-addr-delim"
  776. (list false? integer? string?))))))
  777. (define-record-type* <opensmtpd-srs-configuration>
  778. opensmtpd-srs-configuration make-opensmtpd-srs-configuration
  779. opensmtpd-srs-configuration?
  780. (key opensmtpd-configuration-key
  781. (default #f)
  782. (sanitize (lambda (value)
  783. (my/sanitize value "opensmtpd-srs-configuration" "key" (list false? boolean? string?)))))
  784. (key-backup opensmtpd-srs-configuration-key-backup
  785. (default #f)
  786. (sanitize (lambda (value)
  787. (my/sanitize value "opensmtpd-srs-configuration" "key-backup" (list false? integer?)))))
  788. (ttl-delay opensmtpd-srs-configuration-ttl-delay
  789. (default #f)
  790. (sanitize (lambda (value)
  791. (my/sanitize value "opensmtpd-srs-configuration" "ttl-delay" (list false? string?))))))
  792. (define-record-type* <opensmtpd-queue-configuration>
  793. opensmtpd-queue-configuration make-opensmtpd-queue-configuration
  794. opensmtpd-queue-configuration?
  795. (compression opensmtpd-queue-configuration-compression
  796. (default #f)
  797. (sanitize (lambda (value)
  798. (my/sanitize value "opensmtpd-queue-configuration" "compression" (list boolean?)))))
  799. (encryption opensmtpd-configuration-encryption
  800. (default #f)
  801. (sanitize (lambda (value)
  802. (my/sanitize value "opensmtpd-queue-configuration" "encryption" (list false? file-exists? string?)))))
  803. (ttl-delay opensmtpd-queue-configuration-ttl-delay
  804. (default #f)
  805. (sanitize (lambda (value)
  806. (my/sanitize value "opensmtpd-queue-configuration" "ttl-delay" (list false? string?))))))
  807. (define-record-type* <opensmtpd-configuration>
  808. opensmtpd-configuration make-opensmtpd-configuration
  809. opensmtpd-configuration?
  810. (package opensmtpd-configuration-package
  811. (default opensmtpd))
  812. (config-file opensmtpd-configuration-config-file
  813. ;; TODO if you change this next line to
  814. ;; (default (opensmtpd-configuration)
  815. ;; you will create an infinite recursive list of <opensmtpd-configuration>.
  816. ;; bug-guix doesn't really consider this a bug, but a noobie footgun.
  817. ;; Guile will eventually refuse to compile if you use (opensmtpd-configuration)
  818. ;; BUT that error message is quite lame.
  819. (default #f))
  820. (actions opensmtpd-configuration-actions
  821. (default (list (opensmtpd-action
  822. (name "local")
  823. (method (opensmtpd-local-delivery-configuration
  824. (method "mbox"))))
  825. (opensmtpd-action
  826. (name "outbound")
  827. (method (opensmtpd-relay-configuration))))))
  828. ;; FIXME/TODO should I include a admd authservid entry?
  829. ;; string
  830. (bounce opensmtpd-configuration-bounce
  831. (default #f)
  832. (sanitize (lambda (value)
  833. (my/sanitize value "opensmtpd-configuration" "bounce"
  834. (list false? string?)))))
  835. (cas opensmtpd-configuration-cas
  836. (default #f)
  837. (sanitize (lambda (value)
  838. (my/sanitize value "opensmtpd-configuration" "cas" (list false? list-of-opensmtpd-ca?)))))
  839. ;;list of many records of type opensmtpd-filter-chain
  840. ;; FIXME/TODO? Perhaps I could make filter-chains accept a list like this:
  841. ;; (list (filter-chain (name "chain-filter")
  842. ;; (filters (list (filter-phase
  843. ;; (name "phase")
  844. ;; (conditions (list "rdns")))
  845. ;; (filter-proc-exec
  846. ;; (name "process")
  847. ;; (command "this command"))))))
  848. ;; then I could get rid of fieldnames filter-phases, filter-procs, and filter-proc-execs from
  849. ;; opensmtpd-configuration
  850. (filter-chains opensmtpd-configuration-filter-chains
  851. (sanitize (lambda (value)
  852. (my/sanitize value "opensmtpd-configuration" "filter-chain"
  853. (list false? list-of-opensmtpd-filter-chain?))))
  854. (default #f))
  855. ;; list of many records of type opensmtpd-filter-phase
  856. (filter-phases opensmtpd-configuration-filter-phases
  857. (default #f)
  858. (sanitize (lambda (value)
  859. (my/sanitize value "opensmtpd-configuration" "filter-phases"
  860. (list false? list-of-opensmtpd-filter-phase?)))))
  861. ;; list of many records of type opensmtpd-filter-proc
  862. (filter-procs opensmtpd-configuration-filter-procs
  863. (default #f))
  864. ;; list of many records of type opensmtpd-filter-proc-exec
  865. (filter-proc-execs opensmtpd-configuration-filter-proc-execs
  866. (default #f)
  867. (sanitize (lambda (value)
  868. (my/sanitize value "opensmtpd-configuration" "filter-proc-execs"
  869. (list false? list-of-opensmtpd-filter-proc-exec?)))))
  870. ;; should I have an "include" entry?
  871. ;; list of many records of type opensmtpd-listen-on
  872. (listen-ons opensmtpd-configuration-listen-ons
  873. (default (list (opensmtpd-listen-on)))
  874. (sanitize (lambda (value)
  875. (if (list-of-opensmtpd-listen-on? value)
  876. value
  877. (begin
  878. (display "<opensmtpd-configuration> fieldname 'listen-ons' expects a list of records ")
  879. (display "of one or more <opensmtpd-listen-on> records.\n")
  880. (throw 'bad! value))))))
  881. ;; accepts type <opensmtpd-listen-on-socket-configuration>
  882. (listen-on-socket opensmtpd-configuration-listen-on-socket
  883. (default (opensmtpd-listen-on-socket-configuration)))
  884. (includes opensmtpd-configuration-includes ;; list of strings of absolute path names
  885. (default #f)
  886. (sanitize (lambda (value)
  887. (my/sanitize value "opensmtpd-configuration" "includes" (list false? list-of-strings?)))))
  888. (matches opensmtpd-configuration-matches
  889. (default (list (opensmtpd-match
  890. (name "local")
  891. (for "for local"))
  892. (opensmtpd-match
  893. (name "outbound")
  894. (from "from local")
  895. (for "for any")))))
  896. ;; list of many records of type mda-wrapper
  897. ;; TODO/FIXME support using gexps here
  898. ;; eg (list "name" gexp)
  899. (mda-wrappers opensmtpd-configuration-mda-wrappers
  900. (default #f)
  901. (sanitize (lambda (value)
  902. (my/sanitize value "opensmtpd-configuration" "pkis" (list false? string?)))))
  903. (mta-max-deferred opensmtpd-configuration-mta-max-deferred
  904. (default 100)
  905. (sanitize (lambda (value)
  906. (my/sanitize value "opensmtpd-configuration" "mta-max-deferred" (list number?)))))
  907. ;; list of many records of type pki
  908. (pkis opensmtpd-configuration-pkis
  909. (default #f)
  910. (sanitize (lambda (value)
  911. (my/sanitize value "opensmtpd-configuration" "pkis" (list false? list-of-opensmtpd-pki?)))))
  912. ;; list of many records of type proc
  913. (procs opensmtpd-configuration-procs
  914. (default #f)
  915. (sanitize (lambda (value)
  916. (my/sanitize value "opensmtpd-configuration" "procs" (list false? list-of-opensmtpd-proc?)))))
  917. ;; FIXME/TODO add queue, smtp, srs directives
  918. (queue opensmtpd-configuration-queue
  919. (default #f)
  920. (sanitize (lambda (value)
  921. (my/sanitize value "opensmtpd-configuration" "queue" (list false? opensmtpd-queue-configuration?)))))
  922. (smtp opensmtpd-configuration-smtp
  923. (default #f)
  924. (sanitize (lambda (value)
  925. (my/sanitize value "opensmtpd-configuration" "smtp" (list false? opensmtpd-smtp-configuration?)))))
  926. (srs opensmtpd-configuration-srs
  927. (default #f)
  928. (sanitize (lambda (value)
  929. (my/sanitize value "opensmtpd-configuration" "srs" (list false? opensmtpd-srs-configuration?)))))
  930. ;; list of many records of type opensmtpd-table
  931. (tables opensmtpd-configuration-tables
  932. (default #f)
  933. (sanitize (lambda (value)
  934. (my/sanitize value "opensmtpd-configuration" "procs" (list false? list-of-opensmtpd-table?))))))
  935. ;; given a list and procedure, this tests that each element of list is of type
  936. ;; ie: (list-of-type? list string?) tests each list is of type string.
  937. (define (list-of-type? list proc?)
  938. (if (and (list? list)
  939. (not (null? list)))
  940. (let loop ([list list])
  941. (if (null? list)
  942. #t
  943. (if (proc? (car list))
  944. (loop (cdr list))
  945. #f)))
  946. #f))
  947. (define (list-of-strings? list)
  948. (list-of-type? list string?))
  949. ;; FIXME/TODO? make all of this end like list-of-strings? It has an "s" in it.
  950. ;; list-of-opensmtpd-ca -> <opensmtpd-ca>s? or opensmtpd-cas?
  951. (define (list-of-opensmtpd-ca? list)
  952. (list-of-type? list opensmtpd-ca?))
  953. (define (list-of-opensmtpd-pki? list)
  954. (list-of-type? list opensmtpd-pki?))
  955. (define (list-of-opensmtpd-filter-phase? list)
  956. (list-of-type? list opensmtpd-filter-phase?))
  957. (define (list-of-opensmtpd-filter-chain? list)
  958. (list-of-type? list opensmtpd-filter-chain?))
  959. (define (list-of-opensmtpd-filter-proc-exec? list)
  960. (list-of-type? list opensmtpd-filter-proc-exec?))
  961. (define (list-of-opensmtpd-listen-on? list)
  962. (list-of-type? list opensmtpd-listen-on?))
  963. (define (list-of-opensmtpd-table? list)
  964. (list-of-type? list opensmtpd-table?))
  965. (define (list-of-opensmtpd-proc? list)
  966. (list-of-type? list opensmtpd-proc?))
  967. (define* (list-of-strings->string list
  968. #:key
  969. (string-delimiter ", ")
  970. (prepend "")
  971. (append "")
  972. (drop-right-number 2))
  973. (string-drop-right
  974. (string-append prepend
  975. (let loop ([list list])
  976. (if (null? list)
  977. ""
  978. (string-append (car list)
  979. string-delimiter
  980. (loop (cdr list)))))
  981. append)
  982. drop-right-number))
  983. ;; these next few functions help me to turn <table>s
  984. ;; into strings suitable to fit into "opensmtpd.conf".
  985. ;; at the moment I cannot define this by using list-of-type?
  986. ;; the first (not (null? assoc-list)) prevents that.
  987. (define (assoc-list? assoc-list)
  988. (list-of-type? assoc-list (lambda (pair)
  989. (if (and (pair? pair)
  990. (string? (car pair))
  991. (string? (cdr pair)))
  992. #t
  993. #f))))
  994. (define (assoc-list->string assoc-list)
  995. (string-drop-right
  996. (let loop ([assoc-list assoc-list])
  997. (if (null? assoc-list)
  998. ""
  999. ;; pair is (cons "hello" "world") -> ("hello" . "world")
  1000. (let ([pair (car assoc-list)])
  1001. (string-append
  1002. (car pair)
  1003. " = "
  1004. (cdr pair)
  1005. ", "
  1006. (loop (cdr assoc-list))))))
  1007. 2))
  1008. ;; The following functions convert various records into strings.
  1009. (define* (variable->string var)
  1010. (if var
  1011. (string-append var " ")
  1012. ""))
  1013. (define (list-of-variables->string list)
  1014. (let loop [(list list)]
  1015. (if (null? list)
  1016. ""
  1017. (string-append (variable->string (car list))
  1018. (list-of-variables->string (cdr list))))))
  1019. ;; can be of type: (quote list-of-strings) or (quote assoc-list)
  1020. (define (opensmtpd-table->string table)
  1021. (string-append "table " (opensmtpd-table-name table) " "
  1022. (let ([type (opensmtpd-table-type table)])
  1023. (cond [(eq? type (quote list-of-strings))
  1024. (string-append "{ " (list-of-strings->string (opensmtpd-table-values table)) " }")]
  1025. [(eq? type (quote assoc-list))
  1026. (string-append "{ " (assoc-list->string (opensmtpd-table-values table)) " }")]
  1027. [(eq? type (quote db))
  1028. (string-append "db:" (opensmtpd-table-file table))]
  1029. [(eq? type (quote file))
  1030. (string-append "file:" (opensmtpd-table-file table))]
  1031. [else (throw 'youMessedUp table)]))
  1032. " \n"))
  1033. (define (opensmtpd-listen-on->string record)
  1034. (string-append "listen on "
  1035. (opensmtpd-listen-on-interface record) " "
  1036. (let ([hostname (opensmtpd-listen-on-hostname record)]
  1037. [hostnames (opensmtpd-listen-on-hostnames record)]
  1038. [filter (opensmtpd-listen-on-filter record)]
  1039. [mask-src (opensmtpd-listen-on-mask-src record)]
  1040. [tag (opensmtpd-listen-on-tag record)]
  1041. [secure-connection (opensmtpd-listen-on-secure-connection record)]
  1042. [port (opensmtpd-listen-on-port record)]
  1043. [pki (opensmtpd-listen-on-pki record)]
  1044. [auth (opensmtpd-listen-on-auth record)]
  1045. [auth-optional (opensmtpd-listen-on-auth-optional record)])
  1046. (string-append
  1047. (if mask-src
  1048. (string-append "mask-src ")
  1049. "")
  1050. (if hostname
  1051. (string-append "hostname " hostname " ")
  1052. "")
  1053. (if hostnames
  1054. (string-append "hostname <" hostnames "> ")
  1055. "")
  1056. (if filter
  1057. (string-append "filter \"" filter "\" ")
  1058. "")
  1059. (if tag
  1060. (string-append "tag \"" (opensmtpd-listen-on-tag record) "\" ")
  1061. "")
  1062. (if secure-connection
  1063. (cond [(string=? "smtps" secure-connection)
  1064. "smtps "]
  1065. [(string=? "tls" secure-connection)
  1066. "tls "]
  1067. [(string=? "tls-require" secure-connection)
  1068. "tls-require "]
  1069. [(string=? "tls-require-verify" secure-connection)
  1070. "tls-require verify "])
  1071. "")
  1072. (if port
  1073. (string-append "port " (number->string port) " ")
  1074. "")
  1075. (if pki
  1076. (string-append "pki " pki " ")
  1077. "")
  1078. (if auth
  1079. (string-append "auth "
  1080. (if (string? auth)
  1081. (string-append "<" auth ">")
  1082. ""))
  1083. "")
  1084. (if auth-optional
  1085. (string-append "auth-optional "
  1086. (if (string? auth-optional)
  1087. (string-append "<" auth-optional ">")
  1088. ""))
  1089. "")
  1090. "\n"))))
  1091. (define (opensmtpd-relay-configuration->string record)
  1092. (let ([host (opensmtpd-relay-configuration-host record)])
  1093. (string-append "relay "
  1094. ;; FIXME should I always quote the host fieldname? do I need to quote localhost via "localhost" ?
  1095. (if host
  1096. (string-append "host \"" host "\"")
  1097. ""))))
  1098. (define (opensmtpd-lmtp-configuration->string record)
  1099. (string-append "lmtp "
  1100. (opensmtpd-lmtp-configuration-destination record)
  1101. (if (opensmtpd-lmtp-configuration-rcpt-to record)
  1102. (begin
  1103. " " (opensmtpd-lmtp-configuration-rcpt-to record))
  1104. "")))
  1105. (define (opensmtpd-mda-configuration->string record)
  1106. (string-append "mda "
  1107. (opensmtpd-mda-configuration-command record) " "))
  1108. (define (opensmtpd-maildir-configuration->string record)
  1109. (string-append "maildir "
  1110. "\"" (opensmtpd-maildir-configuration-pathname record) "\""
  1111. (if (opensmtpd-maildir-configuration-junk record)
  1112. " junk"
  1113. " ")))
  1114. (define (opensmtpd-proc->string record)
  1115. (string-append "proc "
  1116. (opensmtpd-proc-name record) " "
  1117. "\"" (opensmtpd-proc-command record) "\"\n"))
  1118. (define (opensmtpd-local-delivery-configuration->string record)
  1119. (let ([method (opensmtpd-local-delivery-configuration-method record)])
  1120. (string-append
  1121. (cond [(string? method)
  1122. (string-append method " ")]
  1123. [(opensmtpd-mda-configuration? method)
  1124. (opensmtpd-mda-configuration->string method)]
  1125. [(opensmtpd-lmtp-configuration? method)
  1126. (opensmtpd-lmtp-configuration->string method)]
  1127. [(opensmtpd-maildir-configuration? method)
  1128. (opensmtpd-maildir-configuration->string method)])
  1129. ;; FIXME/TODO support specifying alias file:/path/to/alias-file ?
  1130. ;; I do not think that is something that I can do...
  1131. (if (string? (opensmtpd-local-delivery-configuration-alias record))
  1132. (string-append "alias <" (opensmtpd-local-delivery-configuration-alias record) "> ")
  1133. ""))))
  1134. (define (opensmtpd-action->string record)
  1135. (string-append "action "
  1136. "\"" (opensmtpd-action-name record) "\" "
  1137. (cond [(opensmtpd-local-delivery-configuration? (opensmtpd-action-method record))
  1138. (opensmtpd-local-delivery-configuration->string (opensmtpd-action-method record))]
  1139. [(opensmtpd-relay-configuration? (opensmtpd-action-method record))
  1140. (opensmtpd-relay-configuration->string (opensmtpd-action-method record))])
  1141. " \n"))
  1142. (define (opensmtpd-match->string record)
  1143. (string-append "match "
  1144. (let ([for (opensmtpd-match-for record)]
  1145. [name (opensmtpd-match-name record)]
  1146. [from (opensmtpd-match-from record)]
  1147. [auth (opensmtpd-match-auth record)]
  1148. [helo (opensmtpd-match-helo record)]
  1149. [mail-from (opensmtpd-match-mail-from record)]
  1150. [rcpt-to (opensmtpd-match-rcpt-to record)]
  1151. [tag (opensmtpd-match-tag record)]
  1152. [tls (opensmtpd-match-tls record)])
  1153. (string-append
  1154. (list-of-variables->string (list from for auth helo mail-from rcpt-to tag))
  1155. (cond [tls "tls "]
  1156. [(not tls) "! tls "]
  1157. [(null? tls) ""])
  1158. (if (string? name)
  1159. (string-append "action " "\"" name "\" ")
  1160. "reject ")
  1161. "\n"))))
  1162. (define (opensmtpd-ca->string record)
  1163. (string-append "ca " (opensmtpd-ca-name record) " "
  1164. "cert \"" (opensmtpd-ca-file record) "\"\n"))
  1165. (define (opensmtpd-pki->string record)
  1166. (string-append "pki "
  1167. (opensmtpd-pki-domain record) " "
  1168. "cert \"" (opensmtpd-pki-cert record) "\"\n"
  1169. "pki "
  1170. (opensmtpd-pki-domain record) " "
  1171. "key \"" (opensmtpd-pki-key record) "\"\n"))
  1172. (define (opensmtpd-filter-chain->string record)
  1173. (string-append "filter "
  1174. "\"" (opensmtpd-filter-chain-name record) "\" "
  1175. "chain {"
  1176. (list-of-strings->string (opensmtpd-filter-chain-filter-names record))
  1177. "}\n"))
  1178. (define (opensmtpd-filter-phase->string record)
  1179. (let ([name (opensmtpd-filter-phase-name record)]
  1180. [phase-name (opensmtpd-filter-phase-phase-name record)]
  1181. [decision (opensmtpd-filter-phase-decision record)]
  1182. [conditions (opensmtpd-filter-phase-conditions record)]
  1183. [message (opensmtpd-filter-phase-message record)]
  1184. [value (opensmtpd-filter-phase-value record)])
  1185. (string-append "filter "
  1186. "\"" name "\" "
  1187. "phase " phase-name " "
  1188. "match "
  1189. (list-of-strings->string
  1190. conditions #:string-delimiter " " #:drop-right-number 1)
  1191. " "
  1192. decision " "
  1193. (if (or (string=? "reject" decision)
  1194. (string=? "disconnect" decision))
  1195. (string-append "\"" message "\"")
  1196. "")
  1197. (if (string=? "rewrite" decision)
  1198. (string-append "rewrite " (number->string value))
  1199. "")
  1200. "\n")))
  1201. ;; FIXME/TODO should I use format here srfi-28 ?
  1202. ;; web.scm nginx does a (format #f "string" "another string")
  1203. ;; this could be a list like (list (file-append opensmtpd-dkimsign "/libexec/filter") "-d gnucode.me -s /path/to/selector.cert")
  1204. ;; Then opensmtpd-configuration->mixed-text-file could be rewritten to be something like
  1205. ;; (mixed-text-file (eval `(string-append (opensmtpd-configuration-fieldname->string ...)) (gnu services mail)))
  1206. ;; and of course (opensmtpd-filter-proc-exec->string would have to be rewritten to
  1207. ;; (string-append "filter " \"name\" proc-exec \" ,@(command) \"
  1208. (define (opensmtpd-filter-proc-exec->string record)
  1209. (string-append "filter "
  1210. "\"" (opensmtpd-filter-proc-exec-name record) "\" "
  1211. "proc-exec " "\"" (opensmtpd-filter-proc-exec-command record) "\""
  1212. "\n"))
  1213. ;; FIXME/TODO do I need this? or want this? I am using a lambda function
  1214. ;; that works fairly well below in the ";; write out the includes" line
  1215. ;; or could I just use the variable->string function?
  1216. ;; something like (variable->string string #:prepend "include \"" #:append "\"\n")
  1217. (define (opensmtpd-configuration-listen->string string)
  1218. (string-append
  1219. "include \"" string "\"\n"))
  1220. (define (opensmtpd-configuration-fieldname->string record fieldname-accessor record->string)
  1221. (if (fieldname-accessor record)
  1222. (begin
  1223. (string-append
  1224. (list-of-records->string (fieldname-accessor record) record->string) "\n"))
  1225. ""))
  1226. (define (list-of-records->string list-of-records record->string)
  1227. (string-append
  1228. (let loop ([list list-of-records])
  1229. (if (null? list)
  1230. ""
  1231. (string-append
  1232. (record->string (car list))
  1233. (loop (cdr list)))))))
  1234. (define (opensmtpd-configuration->mixed-text-file record)
  1235. ;; should I use this named let instead? or should I give this a name...
  1236. ;; (write-all-fieldnames (list (cons fieldname fieldname->string) (cons fieldname2 fieldname->string)))
  1237. ;; (let loop ([list (list (cons opensmtpd-configuration-includes (lambda (string)
  1238. ;; (string-append
  1239. ;; "include \"" string "\"\n")))
  1240. ;; (cons opensmtpd-configuration-tables opensmtpd-table->string)
  1241. ;; (cons opensmtpd-configuration-pkis opensmtpd-pki->string))])
  1242. ;; (if (null? list)
  1243. ;; ""
  1244. ;; (string-append (opensmtpd-configuration-fieldname->string record
  1245. ;; (caar list)
  1246. ;; (cdar list))
  1247. ;; (loop (cadr list)))))
  1248. (mixed-text-file
  1249. "opensmtpd.conf"
  1250. ;; write out the mta-max-deferred
  1251. ;; (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-mta-max-deferred
  1252. ;; (lambda (value)
  1253. ;; (string-append "mta max-deferred " (number->string value) "\n")))
  1254. ;; write out the includes
  1255. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-includes
  1256. opensmtpd-configuration-listen->string)
  1257. ;;write out all the tables
  1258. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-tables opensmtpd-table->string)
  1259. ;; write out all the cas
  1260. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-cas opensmtpd-ca->string)
  1261. ;; write out all the pkis
  1262. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-pkis opensmtpd-pki->string)
  1263. ;; write out all the procs
  1264. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-procs opensmtpd-proc->string)
  1265. ;; write all of the opensmtpd-configuration-filter-chains
  1266. (opensmtpd-configuration-fieldname->string record
  1267. opensmtpd-configuration-filter-chains opensmtpd-filter-chain->string)
  1268. ;; write all of the opensmtpd-configuration-filter-phases
  1269. (opensmtpd-configuration-fieldname->string record
  1270. opensmtpd-configuration-filter-phases opensmtpd-filter-phase->string)
  1271. ;; write all of the opensmtpd-filter-proc-exec
  1272. (opensmtpd-configuration-fieldname->string record
  1273. opensmtpd-configuration-filter-proc-execs opensmtpd-filter-proc-exec->string)
  1274. ;; write all of the listen-on-records
  1275. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-ons
  1276. opensmtpd-listen-on->string)
  1277. ;; write all the actions
  1278. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-actions
  1279. opensmtpd-action->string)
  1280. ;; write all of the matches
  1281. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-matches opensmtpd-match->string)
  1282. ))
  1283. (define (opensmtpd-shepherd-service config)
  1284. (list (shepherd-service
  1285. (provision '(smtpd))
  1286. (requirement '(loopback))
  1287. (documentation "Run the OpenSMTPD daemon.")
  1288. (start (let ((smtpd (file-append (opensmtpd-configuration-package config) "/sbin/smtpd")))
  1289. #~(make-forkexec-constructor
  1290. (list #$smtpd "-f" (or #$(opensmtpd-configuration-config-file config)
  1291. #$(opensmtpd-configuration->mixed-text-file config)))
  1292. #:pid-file "/var/run/smtpd.pid")))
  1293. (stop #~(make-kill-destructor)))))
  1294. (define %opensmtpd-accounts
  1295. (list (user-group
  1296. (name "smtpq")
  1297. (system? #t))
  1298. (user-account
  1299. (name "smtpd")
  1300. (group "nogroup")
  1301. (system? #t)
  1302. (comment "SMTP Daemon")
  1303. (home-directory "/var/empty")
  1304. (shell (file-append shadow "/sbin/nologin")))
  1305. (user-account
  1306. (name "smtpq")
  1307. (group "smtpq")
  1308. (system? #t)
  1309. (comment "SMTPD Queue")
  1310. (home-directory "/var/empty")
  1311. (shell (file-append shadow "/sbin/nologin")))))
  1312. (define (opensmtpd-activation config)
  1313. (let ((smtpd (file-append (opensmtpd-configuration-package config) "/sbin/smtpd"))
  1314. (config-file (opensmtpd-configuration-config-file config))
  1315. (configuration (opensmtpd-configuration->mixed-text-file config)))
  1316. #~(begin
  1317. (use-modules (guix build utils))
  1318. ;; Create mbox and spool directories.
  1319. (mkdir-p "/var/mail")
  1320. (mkdir-p "/var/spool/smtpd")
  1321. (chmod "/var/spool/smtpd" #o711)
  1322. (mkdir-p "/var/spool/mail")
  1323. (chmod "/var/spool/mail" #o711)
  1324. (display (string-append "smtpd: checking syntax of "
  1325. (or
  1326. #$config-file
  1327. #$configuration)
  1328. "\n"))
  1329. (system* #$smtpd "-nf"
  1330. (or
  1331. #$config-file
  1332. #$configuration)))))
  1333. (define %opensmtpd-pam-services
  1334. (list (unix-pam-service "smtpd")))
  1335. (define opensmtpd-service-type
  1336. (service-type
  1337. (name 'opensmtpd)
  1338. (extensions
  1339. (list (service-extension account-service-type
  1340. (const %opensmtpd-accounts))
  1341. (service-extension activation-service-type
  1342. opensmtpd-activation)
  1343. (service-extension pam-root-service-type
  1344. (const %opensmtpd-pam-services))
  1345. (service-extension profile-service-type
  1346. (compose list opensmtpd-configuration-package))
  1347. (service-extension shepherd-root-service-type
  1348. opensmtpd-shepherd-service)))
  1349. (default-value (opensmtpd-configuration))
  1350. (description "Run the Opensmtpd email server.")))