opensmtpd-records.scm 116 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168
  1. ;; sudo guix system -L ../linode/ reconfigure sway.scm
  2. ;; use the above to test this code on an actual system.
  3. ;; An example configuration can usually be found in in the below link,
  4. ;; near the top of the file: https://notabug.org/jbranso/guix-config/src/master/opensmtpd-records.scm
  5. ;;
  6. ;; You can test various non-exported functions in the repl like so:
  7. ;; Try this -> ((@@ (opensmtpd-records) opensmtpd-configuration->mixed-text-file) (opensmtpd-configuration))
  8. ;; (@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string) (opensmtpd-listen-on))
  9. (define-module (opensmtpd-records)
  10. #:use-module (gnu services)
  11. #:use-module (gnu services base)
  12. #:use-module (gnu services configuration)
  13. #:use-module (gnu services shepherd)
  14. #:use-module (gnu system pam)
  15. #:use-module (gnu system shadow)
  16. #:use-module (gnu packages mail)
  17. #:use-module (gnu packages admin)
  18. #:use-module (gnu packages dav)
  19. #:use-module (gnu packages tls)
  20. #:use-module (guix records)
  21. #:use-module (guix packages)
  22. #:use-module (guix gexp)
  23. #:use-module (ice-9 match)
  24. #:use-module (ice-9 receive)
  25. #:use-module (ice-9 format)
  26. #:use-module (srfi srfi-1)
  27. #:export (opensmtpd-service-type
  28. opensmtpd-table-configuration
  29. opensmtpd-table-configuration?
  30. opensmtpd-table-configuration-name
  31. opensmtpd-table-configuration-file-db
  32. opensmtpd-table-configuration-data
  33. opensmtpd-ca-configuration
  34. opensmtpd-ca-configuration?
  35. opensmtpd-ca-configuration-name
  36. opensmtpd-ca-configuration-file
  37. opensmtpd-pki-configuration
  38. opensmtpd-pki-configuration?
  39. opensmtpd-pki-configuration-domain
  40. opensmtpd-pki-configuration-cert
  41. opensmtpd-pki-configuration-key
  42. opensmtpd-pki-configuration-dhe
  43. opensmtpd-action-local-delivery-configuration
  44. opensmtpd-action-local-delivery-configuration?
  45. opensmtpd-action-local-delivery-configuration-method
  46. opensmtpd-action-local-delivery-configuration-alias
  47. opensmtpd-action-local-delivery-configuration-ttl
  48. opensmtpd-action-local-delivery-configuration-user
  49. opensmtpd-action-local-delivery-configuration-userbase
  50. opensmtpd-action-local-delivery-configuration-virtual
  51. opensmtpd-action-local-delivery-configuration-wrapper
  52. opensmtpd-maildir-configuration
  53. opensmtpd-maildir-configuration?
  54. opensmtpd-maildir-configuration-pathname
  55. opensmtpd-maildir-configuration-junk
  56. opensmtpd-mda-configuration
  57. opensmtpd-mda-configuration-name
  58. opensmtpd-mda-configuration-command
  59. opensmtpd-action-relay-configuration
  60. opensmtpd-action-relay-configuration?
  61. opensmtpd-action-relay-configuration-backup
  62. opensmtpd-action-relay-configuration-backup-mx
  63. opensmtpd-action-relay-configuration-helo
  64. opensmtpd-action-relay-configuration-domain
  65. opensmtpd-action-relay-configuration-host
  66. opensmtpd-action-relay-configuration-pki
  67. opensmtpd-action-relay-configuration-srs
  68. opensmtpd-action-relay-configuration-tls
  69. opensmtpd-action-relay-configuration-auth
  70. opensmtpd-action-relay-configuration-mail-from
  71. opensmtpd-action-relay-configuration-src
  72. opensmtpd-option-configuration
  73. opensmtpd-option-configuration?
  74. opensmtpd-option-configuration-option
  75. opensmtpd-option-configuration-not
  76. opensmtpd-option-configuration-regex
  77. opensmtpd-option-configuration-data
  78. opensmtpd-filter-phase-configuration
  79. opensmtpd-filter-phase-configuration?
  80. opensmtpd-filter-phase-configuration-name
  81. opensmtpd-filter-phase-configuration-phase-name
  82. opensmtpd-filter-phase-configuration-options
  83. opensmtpd-filter-phase-configuration-decision
  84. opensmtpd-filter-phase-configuration-message
  85. opensmtpd-filter-phase-configuration-value
  86. opensmtpd-filter-configuration
  87. opensmtpd-filter-configuration?
  88. opensmtpd-filter-configuration-name
  89. opensmtpd-filter-configuration-proc
  90. opensmtpd-listen-on-configuration
  91. opensmtpd-listen-on-configuration?
  92. opensmtpd-listen-on-configuration-interface
  93. opensmtpd-listen-on-configuration-family
  94. opensmtpd-listen-on-configuration-auth
  95. opensmtpd-listen-on-configuration-auth-optional
  96. opensmtpd-listen-on-configuration-filters
  97. opensmtpd-listen-on-configuration-hostname
  98. opensmtpd-listen-on-configuration-hostnames
  99. opensmtpd-listen-on-configuration-mask-src
  100. opensmtpd-listen-on-configuration-disable-dsn
  101. opensmtpd-listen-on-configuration-pki
  102. opensmtpd-listen-on-configuration-port
  103. opensmtpd-listen-on-configuration-proxy-v2
  104. opensmtpd-listen-on-configuration-received-auth
  105. opensmtpd-listen-on-configuration-senders
  106. opensmtpd-listen-on-configuration-secure-connection
  107. opensmtpd-listen-on-configuration-tag
  108. opensmtpd-listen-on-socket-configuration
  109. opensmtpd-listen-on-socket-configuration?
  110. opensmtpd-listen-on-socket-configuration-filters
  111. opensmtpd-listen-on-socket-configuration-mask-src
  112. opensmtpd-listen-on-socket-configuration-tag
  113. opensmtpd-match-configuration
  114. opensmtpd-match-configuration?
  115. opensmtpd-match-configuration-action
  116. opensmtpd-match-configuration-options
  117. opensmtpd-smtp-configuration
  118. opensmtpd-smtp-configuration?
  119. opensmtpd-smtp-configuration-ciphers
  120. opensmtpd-smtp-configuration-limit-max-mails
  121. opensmtpd-smtp-configuration-limit-max-rcpt
  122. opensmtpd-smtp-configuration-max-message-size
  123. opensmtpd-smtp-configuration-sub-addr-delim character
  124. opensmtpd-srs-configuration
  125. opensmtpd-srs-configuration?
  126. opensmtpd-srs-configuration-key
  127. opensmtpd-srs-configuration-backup-key
  128. opensmtpd-srs-configuration-ttl-delay
  129. opensmtpd-queue-configuration
  130. opensmtpd-queue-configuration?
  131. opensmtpd-queue-configuration-compression
  132. opensmtpd-queue-configuration-encryption
  133. opensmtpd-queue-configuration-ttl-delay
  134. opensmtpd-configuration
  135. opensmtpd-configuration?
  136. opensmtpd-package
  137. opensmtpd-config-file
  138. opensmtpd-configuration-bounce
  139. opensmtpd-configuration-listen-ons
  140. opensmtpd-configuration-listen-on-socket
  141. opensmtpd-configuration-includes
  142. opensmtpd-configuration-matches
  143. opensmtpd-configuration-mda-wrappers
  144. opensmtpd-configuration-mta-max-deferred
  145. opensmtpd-configuration-srs
  146. opensmtpd-configuration-smtp
  147. opensmtpd-configuration-queue
  148. ))
  149. ;; some fieldnames have a default value of #f, which is ok. They cannot have a value of #t.
  150. ;; for example opensmtpd-table-configuration-data can be #f, BUT NOT true.
  151. ;; my/sanitize procedure tests values to see if they are of the right kind.
  152. ;; procedure false? is needed to allow fields like 'values' to be blank, (empty), or #f BUT also
  153. ;; have a value like a list of strings.
  154. (define (false? var)
  155. (eq? #f var))
  156. ;; this procedure takes in a var and a list of procedures. It loops through list of procedures passing in var to each.
  157. ;; if one procedure returns #t, the function returns true. Otherwise #f.
  158. ;; TODO for fun rewrite this using map
  159. ;; If I rewrote it in map, then it may help with sanitizing.
  160. ;; eg: I could then potentially easily sanitize vars with lambda procedures.
  161. (define (is-value-right-type? var list-of-procedures)
  162. (if (null? list-of-procedures)
  163. #f
  164. (if ((car list-of-procedures) var)
  165. #t
  166. (is-value-right-type? var (cdr list-of-procedures)))))
  167. ;; converts strings like this:
  168. ;; "apple, ham, cherry" -> "apple, ham, or cherry"
  169. ;; "pineapple" -> "pinneapple".
  170. ;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam"
  171. (define (add-comma-or string)
  172. (define last-comma-location (string-rindex string #\,))
  173. (if last-comma-location
  174. (if (string-contains string ", or" last-comma-location)
  175. string
  176. (string-replace string ", or" last-comma-location
  177. (+ 1 last-comma-location)))
  178. string))
  179. ;; I could test for read-ability of a file, but then I would have to
  180. ;; test the program as root everytime instead of as a normal user...
  181. (define (file-exists? file)
  182. (if (string? file)
  183. (access? file F_OK)
  184. #f))
  185. (define (list-of-procedures->string procedures)
  186. (define string
  187. (let loop ([procedures procedures])
  188. (if (null? procedures)
  189. ""
  190. (begin
  191. (string-append
  192. (cond [(eq? false? (car procedures))
  193. "#f , "]
  194. [(eq? boolean? (car procedures))
  195. "boolean, "]
  196. [(eq? string? (car procedures))
  197. "string, "]
  198. [(eq? integer? (car procedures))
  199. "integer, "]
  200. [(eq? list-of-strings? (car procedures))
  201. "list of strings, "]
  202. [(eq? assoc-list? (car procedures))
  203. "an association list, "]
  204. [(eq? opensmtpd-pki-configuration? (car procedures))
  205. "an <opensmtpd-pki-configuration> record, "]
  206. [(eq? opensmtpd-table-configuration? (car procedures))
  207. "an <opensmtpd-table-configuration> record, "]
  208. [(eq? list-of-unique-opensmtpd-match-configuration? (car procedures))
  209. "a list of unique <opensmtpd-match-configuration> records, "]
  210. [(eq? table-whose-data-are-assoc-list? (car procedures))
  211. (string-append
  212. "an <opensmtpd-table-configuration> record whose fieldname 'values' are an assoc-list \n"
  213. "(eg: (opensmtpd-table-configuration (name \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")]
  214. [(eq? file-exists? (car procedures))
  215. "file, "]
  216. [else "has an incorrect value, "])
  217. (loop (cdr procedures)))))))
  218. (add-comma-or (string-append (string-drop-right string 2) ".\n")))
  219. ;; TODO can I M-x raise-sexp (string=? string var) in this procedure? and get rid of checking
  220. ;; if the var is a string? The previous string-in-list? had that check.
  221. ;; (string-in-list? '("hello" 5 "cat")) currently works. If I M-x raise-sexp (string=? string var)
  222. ;; then it will no longer work.
  223. (define (string-in-list? string list)
  224. (primitive-eval (cons 'or (map (lambda (var) (and (string? var) (string=? string var))) list))))
  225. (define (my/sanitize var record fieldname list-of-procedures)
  226. (if (is-value-right-type? var list-of-procedures record fieldname)
  227. var
  228. (begin
  229. (display (string-append "<" record "> fieldname: '" fieldname "' is of type "
  230. (list-of-procedures->string list-of-procedures) "\n"))
  231. (throw 'bad! var))))
  232. ;; Some example opensmtpd-table-configurations:
  233. ;;
  234. ;; (opensmtpd-table-configuration (name "root accounts") (data '(("joshua" . "root@dismail.de") ("joshua" . "postmaster@dismail.de"))))
  235. ;; (opensmtpd-table-configuration (name "root accounts") (data (list "mysite.me" "your-site.com")))
  236. ;; TODO should <opensmtpd-table-configuration> support have a fieldname 'file'?
  237. ;; Or should I change name to name-or-file ?
  238. (define-record-type* <opensmtpd-table-configuration>
  239. opensmtpd-table-configuration make-opensmtpd-table-configuration
  240. opensmtpd-table-configuration?
  241. this-record
  242. (name opensmtpd-table-configuration-name ;; string
  243. (default #f)
  244. (sanitize (lambda (var)
  245. (my/sanitize var "opensmtpd-table-configuration" "name" (list string?)))))
  246. (file-db opensmtpd-table-configuration-file-db
  247. (default #f)
  248. (sanitize (lambda (var)
  249. (my/sanitize var "opensmtpd-table-configuration" "file-db"
  250. (list boolean?)))))
  251. ;; FIXME support an aliasing table as described here:
  252. ;; https://man.openbsd.org/table.5
  253. ;; One may have to use the record file for this. I don't think tables support a table like this:
  254. ;; table "name" { joshua = joshua@gnucode.me,joshua@gnu-hurd.com,joshua@propernaming.org, root = root@gnucode.me }
  255. ;; If values is an absolute filename, then it will use said filename to house the table info.
  256. ;; filename must be an absolute filename.
  257. (data opensmtpd-table-configuration-data
  258. (default #f)
  259. (sanitize (lambda (var)
  260. (my/sanitize var "opensmtpd-table-configuration" "values"
  261. (list file-exists? list-of-strings? assoc-list?)))))
  262. ;; is a list of values or key values
  263. ;; eg: (list "mysite.me" "your-site.com")
  264. ;; eg: (list ("joshua" . "joshua@gnu.org") ("james" . "james@gnu.org"))
  265. ;; I am currently making these values be as assocation list of strings only.
  266. ;; FIXME should I allow a var like this?
  267. ;; (list (cons "gnucode.me" 234.949.392.23))
  268. ;; can be of type: (quote list-of-strings) or (quote assoc-list)
  269. ;; (opensmtpd-table-configuration-type record) returns the values' type. The user SHOULD NEVER set the type.
  270. ;; TODO jpoiret: on irc reccomends that I just use an outside function to determine fieldname 'values', type.
  271. ;; it would be "simpler" and possibly easier for the next person working on this code to understand what is happening.
  272. (type opensmtpd-table-configuration-type
  273. (default #f)
  274. (thunked)
  275. (sanitize (lambda (var)
  276. (cond [(opensmtpd-table-configuration-data this-record)
  277. (if (list-of-strings? (opensmtpd-table-configuration-data this-record))
  278. (quote list-of-strings)
  279. (quote assoc-list))]
  280. [(file-exists? (opensmtpd-table-configuration-data this-record))
  281. (if (opensmtpd-table-configuration-file-db this-record)
  282. (quote db)
  283. (quote file))]
  284. [else
  285. (display "opensmtpd-table-configuration-type is broke\n")
  286. (throw 'bad! var)])))))
  287. (define-record-type* <opensmtpd-ca-configuration>
  288. opensmtpd-ca-configuration make-opensmtpd-ca-configuration
  289. opensmtpd-ca-configuration?
  290. (name opensmtpd-ca-configuration-name
  291. (default #f)
  292. (sanitize (lambda (var)
  293. (my/sanitize var "opensmtpd-ca-configuration" "name" (list string?)))))
  294. (file opensmtpd-ca-configuration-file
  295. (default #f)
  296. (sanitize (lambda (var)
  297. (my/sanitize var "opensmtpd-ca-configuration" "file" (list file-exists?))))))
  298. (define-record-type* <opensmtpd-pki-configuration>
  299. opensmtpd-pki-configuration make-opensmtpd-pki-configuration
  300. opensmtpd-pki-configuration?
  301. (domain opensmtpd-pki-configuration-domain
  302. (default #f)
  303. (sanitize (lambda (var)
  304. (my/sanitize var "opensmtpd-pki-configuration" "domain" (list string?)))))
  305. ;; TODO/FIXME this should probably be a list of files. The opensmtpd documentation says
  306. ;; that you could have a list of files:
  307. ;;
  308. ;; pki pkiname cert certfile
  309. ;; Associate certificate file certfile with host pkiname, and use that file to prove
  310. ;; the identity of the mail server to clients. pkiname is the server's name, de‐
  311. ;; rived from the default hostname or set using either
  312. ;; /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/mailname or us‐
  313. ;; ing the hostname directive. If a fallback certificate or SNI is wanted, the ‘*’
  314. ;; wildcard may be used as pkiname.
  315. ;; A certificate chain may be created by appending one or many certificates, includ‐
  316. ;; ing a Certificate Authority certificate, to certfile. The creation of certifi‐
  317. ;; cates is documented in starttls(8).
  318. (cert opensmtpd-pki-configuration-cert
  319. (default #f)
  320. (sanitize (lambda (var)
  321. (my/sanitize var "opensmtpd-pki-configuration" "cert" (list file-exists?)))))
  322. (key opensmtpd-pki-configuration-key
  323. (default #f)
  324. (sanitize (lambda (var)
  325. (my/sanitize var "opensmtpd-pki-configuration" "key" (list file-exists?)))))
  326. ; todo sanitize this. valid parameters are "none", "legacy", or "auto".
  327. (dhe opensmtpd-pki-configuration-dhe
  328. (default #f)
  329. (sanitize (lambda (var)
  330. (my/sanitize var "opensmtpd-dhe" "dhe" (list false? string?))))))
  331. (define-record-type* <opensmtpd-lmtp-configuration>
  332. opensmtpd-lmtp-configuration make-opensmtpd-lmtp-configuration
  333. opensmtpd-lmtp-configuration?
  334. (destination opensmtpd-lmtp-configuration-destination
  335. (default #f)
  336. (sanitize (lambda (var)
  337. (my/sanitize var "opensmtpd-lmtp-configuration" "destination"
  338. (list string?)))))
  339. (rcpt-to opensmtpd-lmtp-configuration-rcpt-to
  340. (default #f)
  341. (sanitize (lambda (var)
  342. (my/sanitize var "opensmtpd-lmtp-configuration" "rcpt-to"
  343. (list false? string?))))))
  344. (define-record-type* <opensmtpd-mda-configuration>
  345. opensmtpd-mda-configuration make-opensmtpd-mda-configuration
  346. opensmtpd-mda-configuration?
  347. (name opensmtpd-mda-configuration-name
  348. (default #f)
  349. (sanitize (lambda (var)
  350. (my/sanitize var "opensmtpd-mda-configuration" "name"
  351. (list string?)))))
  352. ;; TODO should I allow this command to be a gexp?
  353. (command opensmtpd-mda-configuration-command
  354. (default #f)
  355. (sanitize (lambda (var)
  356. (my/sanitize var "opensmtpd-mda-configuration" "command"
  357. (list string?))))))
  358. (define-record-type* <opensmtpd-maildir-configuration>
  359. opensmtpd-maildir-configuration make-opensmtpd-maildir-configuration
  360. opensmtpd-maildir-configuration?
  361. (pathname opensmtpd-maildir-configuration-pathname
  362. (default #f)
  363. (sanitize (lambda (var)
  364. (my/sanitize var "opensmtpd-maildir-configuration" "pathname"
  365. (list false? string?)))))
  366. (junk opensmtpd-maildir-configuration-junk
  367. (default #f)
  368. (sanitize (lambda (var)
  369. (my/sanitize var "opensmtpd-maildir-configuration" "junk"
  370. (list boolean?))))))
  371. (define-record-type* <opensmtpd-action-local-delivery-configuration>
  372. opensmtpd-action-local-delivery-configuration make-opensmtpd-action-local-delivery-configuration
  373. opensmtpd-action-local-delivery-configuration?
  374. (name opensmtpd-action-local-delivery-configuration-name
  375. (default #f)
  376. (sanitize (lambda (var)
  377. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "name"
  378. (list string?)))))
  379. (method opensmtpd-action-local-delivery-configuration-method
  380. (default "mbox")
  381. (sanitize (lambda (var)
  382. (cond
  383. [(or (opensmtpd-lmtp-configuration? var)
  384. (opensmtpd-maildir-configuration? var)
  385. (opensmtpd-mda-configuration? var)
  386. (string=? var "mbox")
  387. (string=? var "expand-only")
  388. (string=? var "forward-only"))
  389. var]
  390. [else
  391. (begin
  392. (display (string-append "<opensmtpd-action-local-delivery-configuration> fieldname 'method' must be of type \n"
  393. "\"mbox\", \"expand-only\", \"forward-only\" \n"
  394. "<opensmtpd-lmtp-configuration>, <opensmtpd-maildir-configuration>, \n"
  395. "or <opensmtpd-mda-configuration>.\n"))
  396. (throw 'bad! var))]))))
  397. (alias opensmtpd-action-local-delivery-configuration-alias
  398. (default #f)
  399. (sanitize (lambda (var)
  400. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "alias"
  401. (list false? opensmtpd-table-configuration?)))))
  402. (ttl opensmtpd-action-local-delivery-configuration-ttl
  403. (default #f)
  404. (sanitize (lambda (var)
  405. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "ttl"
  406. (list false? string?)))))
  407. (user opensmtpd-action-local-delivery-configuration-user
  408. (default #f)
  409. (sanitize (lambda (var)
  410. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "user"
  411. (list false? string?)))))
  412. (userbase opensmtpd-action-local-delivery-configuration-userbase
  413. (default #f)
  414. (sanitize (lambda (var)
  415. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "userbase"
  416. (list false? opensmtpd-table-configuration?)))))
  417. (virtual opensmtpd-action-local-delivery-configuration-virtual
  418. (default #f)
  419. (sanitize (lambda (var)
  420. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "virtual"
  421. (list false? opensmtpd-table-configuration?)))))
  422. (wrapper opensmtpd-action-local-delivery-configuration-wrapper
  423. (default #f)
  424. (sanitize (lambda (var)
  425. (my/sanitize var "opensmtpd-action-local-delivery-configuration" "wrapper"
  426. (list false? string?))))))
  427. ;; FIXME/TODO this is a valid opensmtpd-relay record
  428. ;; (opensmtpd-action-relay-configuration
  429. ;; (pki (opensmtpd-pki-configuration
  430. ;; (domain "gnucode.me")
  431. ;; (cert "opensmtpd.scm")
  432. ;; (key "opensmtpd.scm"))))
  433. ;; BUT how does it relay the email? What host does it use?
  434. ;; I think opensmtpd-relay-configuration needs "method" field.
  435. ;; the method field might need to be another record...BUT basically the relay has to have a 'backup', 'backup-mx',
  436. ;; or 'domain', or 'host' defined.
  437. (define-record-type* <opensmtpd-action-relay-configuration>
  438. opensmtpd-action-relay-configuration make-opensmtpd-action-relay-configuration
  439. opensmtpd-action-relay-configuration?
  440. (name opensmtpd-action-relay-configuration-name
  441. (sanitize (lambda (var)
  442. (my/sanitize var "opensmtpd-action-relay-configuration" "name"
  443. (list string?))))
  444. (default #f))
  445. (backup opensmtpd-action-relay-configuration-backup ;; boolean
  446. (default #f)
  447. (sanitize (lambda (var)
  448. (my/sanitize var "opensmtpd-action-relay-configuration" "backup"
  449. (list boolean?)))))
  450. (backup-mx opensmtpd-action-relay-configuration-backup-mx ;; string mx name
  451. (default #f)
  452. (sanitize (lambda (var)
  453. (my/sanitize var "opensmtpd-action-relay-configuration" "backup-mx"
  454. (list false? string?)))))
  455. (helo opensmtpd-action-relay-configuration-helo
  456. (sanitize (lambda (var)
  457. (my/sanitize var "opensmtpd-action-relay-configuration" "helo"
  458. (list false? string? opensmtpd-table-configuration?))))
  459. (default #f))
  460. (helo-src opensmtpd-action-relay-configuration-helo-src
  461. (sanitize (lambda (var)
  462. (my/sanitize var "opensmtpd-action-relay-configuration" "helo-src"
  463. (list false? string? opensmtpd-table-configuration?))))
  464. (default #f))
  465. (domain opensmtpd-action-relay-configuration-domain
  466. (sanitize (lambda (var)
  467. (my/sanitize var "opensmtpd-action-relay-configuration" "domain"
  468. (list false? opensmtpd-table-configuration?))))
  469. (default #f))
  470. (host opensmtpd-action-relay-configuration-host
  471. (sanitize (lambda (var)
  472. (my/sanitize var "opensmtpd-action-relay-configuration" "host"
  473. (list false? string?))))
  474. (default #f))
  475. (pki opensmtpd-action-relay-configuration-pki
  476. (default #f)
  477. (sanitize (lambda (var)
  478. (my/sanitize var "opensmtpd-action-relay-configuration" "pki"
  479. (list false? opensmtpd-pki-configuration?)))))
  480. (srs opensmtpd-action-relay-configuration-srs
  481. (default #f)
  482. (lambda (var)
  483. (my/sanitize var "opensmtpd-action-relay-configuration" "srs"
  484. (list boolean?))))
  485. (tls opensmtpd-action-relay-configuration-tls
  486. (default #f)
  487. (sanitize (lambda (var)
  488. (my/sanitize var "opensmtpd-action-relay-configuration" "tls"
  489. (list false? string?)))))
  490. (auth opensmtpd-action-relay-configuration-auth
  491. (sanitize (lambda (var)
  492. (my/sanitize var "opensmtpd-action-relay-configuration" "auth"
  493. (list false? opensmtpd-table-configuration?))))
  494. (default #f))
  495. (mail-from opensmtpd-action-relay-configuration-mail-from
  496. (default #f))
  497. ;; string "127.0.0.1" or "<interface>" or "<table of IP addresses>"
  498. ;; TODO should I do some sanitizing to make sure that the string? here is actually an IP address or a valid interface?
  499. (src opensmtpd-action-relay-configuration-src
  500. (sanitize (lambda (var)
  501. (my/sanitize var "opensmtpd-action-relay-configuration" "src"
  502. (list false? string? opensmtpd-table-configuration?))))
  503. (default #f)))
  504. ;; this record is used by <opensmtpd-filter-phase-configuration> &
  505. ;; <opensmtpd-match-configuration>
  506. (define-record-type* <opensmtpd-option-configuration>
  507. opensmtpd-option-configuration make-opensmtpd-option-configuration
  508. opensmtpd-option-configuration?
  509. (option opensmtpd-option-configuration-option
  510. (default #f)
  511. (sanitize (lambda (var)
  512. (if (and (string? var)
  513. (or (string-in-list? var (list "fcrdns" "rdns"
  514. "src" "helo"
  515. "auth" "mail-from"
  516. "rcpt-to"
  517. "for"
  518. "for any" "for local"
  519. "for domain" "for rcpt-to"
  520. "from any" "from auth"
  521. "from local" "from mail-from"
  522. "from rdns" "from socket"
  523. "from src" "auth"
  524. "helo" "mail-from"
  525. "rcpt-to" "tag" "tls"
  526. ))))
  527. var
  528. (begin
  529. (display (string-append "<opensmtpd-option-configuration> fieldname: 'option' is of type \n"
  530. "string. The string can be either 'fcrdns', \n"
  531. " 'rdns', 'src', 'helo', 'auth', 'mail-from', or 'rcpt-to', \n"
  532. "'for', 'for any', 'for local', 'for domain', 'for rcpt-to', \n"
  533. "'from any', 'from auth', 'from local', 'from mail-from', 'from rdns', 'from socket', \n"
  534. "'from src', 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n"
  535. ))
  536. (throw 'bad! var))))))
  537. (not opensmtpd-option-configuration-not
  538. (default #f)
  539. (sanitize (lambda (var)
  540. (my/sanitize var "opensmtpd-option-configuration" "not"
  541. (list boolean?)))))
  542. (regex opensmtpd-option-configuration-regex
  543. (default #f)
  544. (sanitize (lambda (var)
  545. (my/sanitize var "opensmtpd-option-configuration" "regex"
  546. (list boolean?)))))
  547. (data opensmtpd-option-configuration-data
  548. (default #f)
  549. (sanitize (lambda (var)
  550. (my/sanitize var "opensmtpd-option-configuration" "data"
  551. (list false? string? opensmtpd-table-configuration?))))))
  552. (define-record-type* <opensmtpd-filter-phase-configuration>
  553. opensmtpd-filter-phase-configuration make-opensmtpd-filter-phase-configuration
  554. opensmtpd-filter-phase-configuration?
  555. (name opensmtpd-filter-phase-configuration-name ;; string chain-name
  556. (default #f)
  557. (sanitize (lambda (var)
  558. (my/sanitize var "opensmtpd-filter-phase-configuration" "name"
  559. (list string?)))))
  560. (phase opensmtpd-filter-phase-configuration-phase ;; string
  561. (default #f)
  562. (sanitize (lambda (var)
  563. ;;(my/sanitize var "opensmtpd-filter-phase-configuration" "phase"
  564. ;; (list (sanitize-configuration
  565. ;; (proc (lambda (value)
  566. ;; (and (string? var)
  567. ;; (string-in-list? var (list "connect"
  568. ;; "helo"
  569. ;; "mail-from"
  570. ;; "rcpt-to"
  571. ;; "data"
  572. ;; "commit")))))
  573. ;; (error-message (list
  574. ;; "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n"
  575. ;; "string. The string can be either 'connect',"
  576. ;; " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n ")))))
  577. (if (and (string? var)
  578. (string-in-list? var (list "connect"
  579. "helo"
  580. "mail-from"
  581. "rcpt-to"
  582. "data"
  583. "commit")))
  584. var
  585. (begin
  586. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n"
  587. "string. The string can be either 'connect',"
  588. " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n "
  589. ))
  590. (throw 'bad! var)))
  591. )))
  592. (options opensmtpd-filter-phase-configuration-options
  593. (default #f)
  594. (sanitize (lambda (var)
  595. ;; returns #t if list is a unique list of <opensmtpd-option-configuration>
  596. (define (list-of-opensmtpd-option-configuration? list)
  597. (and (list-of-type? list opensmtpd-option-configuration?)
  598. (not (contains-duplicate? list))))
  599. (define (list-has-duplicates-or-non-opensmtpd-option-configuration list)
  600. (not (list-of-opensmtpd-option-configuration? list)))
  601. ;; input <opensmtpd-option-configuration>
  602. ;; return #t if <opensmtpd-option-configuration> fieldname 'option'
  603. ;; that needs a corresponding table has one. Otherwise #f
  604. (define (opensmtpd-option-configuration-has-table? record)
  605. (define decision (opensmtpd-option-configuration-option record))
  606. (and (string? decision)
  607. ;; if option needs a table, check for a table
  608. (if (string-in-list? decision (list "src"
  609. "helo"
  610. "mail-from"
  611. "rcpt-to"))
  612. (opensmtpd-table-configuration? (opensmtpd-option-configuration-data record))
  613. #t)))
  614. (define (list-of-opensmtpd-option-configuration-has-table? list)
  615. (list-of-type? list opensmtpd-option-configuration-has-table?))
  616. (define (some-opensmtpd-option-configuration-in-list-lack-table? list)
  617. (not (list-of-opensmtpd-option-configuration-has-table? list)))
  618. ;;each element in list is of type <opensmtpd-option-configuration>
  619. (cond [(list-has-duplicates-or-non-opensmtpd-option-configuration var)
  620. (begin
  621. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'options' is a list of unique \n"
  622. "<opensmtpd-option-configuration> records.\n"))
  623. (throw 'bad! var))]
  624. ;; if fieldname 'option' is of string 'src', 'helo', 'mail-from', 'rcpt-to', then there should be a table
  625. [(some-opensmtpd-option-configuration-in-list-lack-table? var)
  626. (begin
  627. (display (string-append "<opensmtpd-option-configuration>'s fieldname 'option' values of \n"
  628. "'src', 'helo', 'mail-from', or 'rcpt-to' need a corresponding 'table' \n"
  629. " of type <opensmtpd-table-configuration>. eg: \n"
  630. "(opensmtpd-option-configuration \n"
  631. " (option \"src\")\n"
  632. " (table (opensmtpd-table-configuration \n"
  633. " (name \"src-table\")\n"
  634. " (data (list \"hello\" \"cat\")))))\n"))
  635. ;; TODO it would be nice if the var this error message throws in the bad
  636. ;; <opensmtpd-option-configuration>, instead of the list of records.
  637. (throw 'bad! var))]
  638. [else var]))))
  639. (decision opensmtpd-filter-phase-configuration-decision
  640. (default #f)
  641. (sanitize (lambda (var)
  642. (if (and (string? var)
  643. (string-in-list? var (list "bypass" "disconnect"
  644. "reject" "rewrite" "junk")))
  645. var
  646. (begin
  647. (display (string-append "<opensmtpd-filter-decision> fieldname: 'decision' is of type \n"
  648. "string. The string can be either 'bypass',"
  649. " 'disconnect', 'reject', 'rewrite', or 'junk'.\n"))
  650. (throw 'bad! var))))))
  651. (message opensmtpd-filter-phase-configuration-message
  652. (default #f)
  653. (sanitize (lambda (var)
  654. (my/sanitize var "opensmtpd-filter-phase-configuration" "message"
  655. (list false? string?)))))
  656. (value opensmtpd-filter-phase-configuration-value
  657. (default #f)
  658. (sanitize (lambda (var)
  659. (my/sanitize var "opensmtpd-filter-phase-configuration" "value"
  660. (list false? number?))))))
  661. (define-record-type* <opensmtpd-filter-configuration>
  662. opensmtpd-filter-configuration make-opensmtpd-filter-configuration
  663. opensmtpd-filter-configuration?
  664. (name opensmtpd-filter-configuration-name
  665. (default #f)
  666. (sanitize (lambda (var)
  667. (my/sanitize var "opensmtpd-filter" "name"
  668. (list string?)))))
  669. (exec opensmtpd-filter-exec
  670. (default #f)
  671. (sanitize (lambda (var)
  672. (my/sanitize var "opensmtpd-filter" "exec"
  673. (list boolean?)))))
  674. (proc opensmtpd-filter-configuration-proc ; a string like "rspamd" or the command to start it like "/path/to/rspamd --option=arg --2nd-option=arg2"
  675. (default #f)
  676. (sanitize (lambda (var)
  677. (my/sanitize var "opensmtpd-filter" "proc"
  678. (list string?))))))
  679. ;; There is another type of filter that opensmtpd supports, which is a filter chain.
  680. ;; A filter chain is a list of <opensmtpd-filter-phase-configuration> and <opensmtpd-filter-configuration>.
  681. ;; This lets you apply several filters under one filter name. I could have defined
  682. ;; a record type for it, but the record would only have had two fields: name and list-of-filters.
  683. ;; Why write that as a record? That's too simple.
  684. ;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>
  685. ;; returns # otherwise
  686. (define (opensmtpd-filter-chain? %filters)
  687. (and (list-of-unique-filter-or-filter-phase? %filters)
  688. (< 1 (length %filters))))
  689. (define-record-type* <opensmtpd-listen-on-configuration>
  690. opensmtpd-listen-on-configuration make-opensmtpd-listen-on-configuration
  691. opensmtpd-listen-on-configuration?
  692. ;; interface may be an IP address, interface group, or domain name
  693. (interface opensmtpd-listen-on-configuration-interface
  694. (default "lo"))
  695. (family opensmtpd-listen-on-configuration-family
  696. (default #f)
  697. (sanitize (lambda (var)
  698. (cond
  699. [(eq? #f var) ;; var == #f
  700. var]
  701. [(and (string? var)
  702. (string-in-list? var (list "inet4" "inet6")))
  703. var]
  704. [else
  705. (begin
  706. (display "<opensmtpd-listen-on-configuration> fieldname 'family' must be string \"inet4\" or \"inet6\".\n")
  707. (throw 'bad! var))]))))
  708. (auth opensmtpd-listen-on-configuration-auth
  709. (default #f)
  710. (sanitize (lambda (var)
  711. (my/sanitize var "opensmtpd-listen-on-configuration" "auth"
  712. (list boolean? table-whose-data-are-assoc-list?)))))
  713. (auth-optional opensmtpd-listen-on-configuration-auth-optional
  714. (default #f)
  715. (sanitize (lambda (var)
  716. (my/sanitize var "opensmtpd-listen-on-configuration" "auth-optional"
  717. (list boolean?
  718. table-whose-data-are-assoc-list?)))))
  719. ;; TODO add a ca entry?
  720. ;; string FIXME/TODO sanitize this to support a gexp. That way way the
  721. ;; includes directive can include my hacky scheme code that I use for opensmtpd-dkimsign.
  722. (filters opensmtpd-listen-on-configuration-filters
  723. (default #f)
  724. (sanitize (lambda (var)
  725. (sanitize-filters var))))
  726. (hostname opensmtpd-listen-on-configuration-hostname
  727. (default #f)
  728. (sanitize (lambda (var)
  729. (my/sanitize var "opensmtpd-listen-on-configuration" "hostname"
  730. (list false? string?)))))
  731. (hostnames opensmtpd-listen-on-configuration-hostnames
  732. (default #f)
  733. (sanitize (lambda (var)
  734. (my/sanitize var "opensmtpd-listen-on-configuration" "hostnames"
  735. (list false? table-whose-data-are-assoc-list?)))))
  736. (mask-src opensmtpd-listen-on-configuration-mask-src
  737. (default #f)
  738. (sanitize (lambda (var)
  739. (my/sanitize var "opensmtpd-listen-on-configuration" "mask-src"
  740. (list boolean?)))))
  741. (disable-dsn opensmtpd-listen-on-configuration-disable-dsn
  742. (default #f))
  743. (pki opensmtpd-listen-on-configuration-pki
  744. (default #f)
  745. (sanitize (lambda (var)
  746. (my/sanitize var "opensmtpd-listen-on-configuration" "pki"
  747. (list false? opensmtpd-pki-configuration?)))))
  748. (port opensmtpd-listen-on-configuration-port
  749. (default #f)
  750. (sanitize (lambda (var)
  751. (my/sanitize var "opensmtpd-listen-on-configuration" "port"
  752. (list false? integer?)))))
  753. (proxy-v2 opensmtpd-listen-on-configuration-proxy-k2
  754. (default #f))
  755. (received-auth opensmtpd-listen-on-configuration-received-auth
  756. (default #f))
  757. ;; TODO add in a senders option!
  758. ;; string or <opensmtpd-senders> record
  759. ;; (senders opensmtpd-listen-on-configuration-senders
  760. ;; (sanitize (lambda (var)
  761. ;; (my/sanitize var "opensmtpd-listen-on-configuration" "port" (list false? integer?))))
  762. ;; (default #f))
  763. (secure-connection opensmtpd-listen-on-configuration-secure-connection
  764. (default #f)
  765. (sanitize (lambda (var)
  766. (cond [(boolean? var)
  767. var]
  768. [(and (string? var)
  769. (string-in-list? var
  770. (list "smtps" "tls"
  771. "tls-require"
  772. "tls-require-verify")))
  773. var]
  774. [else
  775. (begin
  776. (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be \n"
  777. "one of the following strings: \n'smtps', 'tls', 'tls-require', \n"
  778. "or 'tls-require-verify'.\n"))
  779. (throw 'bad! var))]))))
  780. (tag opensmtpd-listen-on-configuration-tag
  781. (sanitize (lambda (var)
  782. (my/sanitize var "opensmtpd-listen-on-configuration" "tag"
  783. (list false? string?))))
  784. (default #f)))
  785. (define-record-type* <opensmtpd-listen-on-socket-configuration-configuration>
  786. opensmtpd-listen-on-socket-configuration-configuration make-opensmtpd-listen-on-socket-configuration-configuration
  787. opensmtpd-listen-on-socket-configuration-configuration?
  788. ;; false or <opensmtpd-filter-configuration> or list of <opensmtpd-filter-configuration>
  789. (filters opensmtpd-listen-on-socket-configuration-configuration-filters
  790. (sanitize (lambda (var)
  791. (sanitize-filters var)))
  792. (default #f))
  793. (mask-src opensmtpd-listen-on-socket-configuration-configuration-mask-src
  794. (default #f))
  795. (tag opensmtpd-listen-on-socket-configuration-configuration-tag
  796. (sanitize (lambda (var)
  797. (my/sanitize var "opensmtpd-listen-on-configuration" "tag"
  798. (list false? string?))))
  799. (default #f)))
  800. (define-record-type* <opensmtpd-match-configuration>
  801. opensmtpd-match-configuration make-opensmtpd-match-configuration
  802. opensmtpd-match-configuration?
  803. ;;TODO? Perhaps I should add in a reject fieldname. If reject
  804. ;;is #t, then the match record will be a reject match record.
  805. ;; (opensmtpd-match (reject #t)) vs. (opensmtpd-match (action 'reject))
  806. ;; To do this, I will also have to 'reject' mutually exclusive. AND an match with 'reject' can have no action defined.
  807. (action opensmtpd-match-configuration-action
  808. (default #f)
  809. (sanitize (lambda (var)
  810. (if (or (opensmtpd-action-relay-configuration? var)
  811. (opensmtpd-action-local-delivery-configuration? var)
  812. (eq? (quote reject) var))
  813. var
  814. (begin
  815. (display
  816. (string-append "<opensmtpd-match-configuration> fieldname 'action' is of type <opensmtpd-action-relay-configuration>, \n"
  817. "<opensmtpd-action-local-delivery-configuration>, or (quote reject).\n"
  818. "If its var is (quote reject), then the match rejects the incoming message\n"
  819. "during the SMTP dialogue.\n"))
  820. (throw 'bad! var))))))
  821. (options opensmtpd-match-configuration-options
  822. (default #f)
  823. (sanitize (lambda (var)
  824. (cond ((not var)
  825. #f)
  826. ((not (list-of-unique-opensmtpd-option-configuration? var))
  827. (throw-error var '("<opensmtpd-match-configuration> fieldname 'options' is a list of unique \n"
  828. "<opensmtpd-option-configuration> records. \n")))
  829. (else (sanitize-list-of-options-for-match-configuration var)))))))
  830. (define-record-type* <opensmtpd-smtp-configuration>
  831. opensmtpd-smtp-configuration make-opensmtpd-smtp-configuration
  832. opensmtpd-smtp-configuration?
  833. (ciphers opensmtpd-smtp-configuration-ciphers
  834. (default #f)
  835. (sanitize (lambda (var)
  836. (my/sanitize var "opensmtpd-smtp-configuration" "ciphers"
  837. (list false? string?)))))
  838. (limit-max-mails opensmtpd-smtp-configuration-limit-max-mails
  839. (default #f)
  840. (sanitize (lambda (var)
  841. (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-mails"
  842. (list false? integer?)))))
  843. (limit-max-rcpt opensmtpd-smtp-configuration-limit-max-rcpt
  844. (default #f)
  845. (sanitize (lambda (var)
  846. (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-rcpt"
  847. (list false? integer?)))))
  848. (max-message-size opensmtpd-smtp-configuration-max-message-size
  849. (default #f)
  850. (sanitize (lambda (var)
  851. (my/sanitize var "opensmtpd-smtp-configuration" "max-message-size"
  852. (list false? integer? string?)))))
  853. ;; FIXME/TODO the sanitize function of sub-addr-delim should accept a string of length one not string?
  854. (sub-addr-delim opensmtpd-smtp-configuration-sub-addr-delim
  855. (default #f)
  856. (sanitize (lambda (var)
  857. (my/sanitize var "opensmtpd-smtp-configuration" "sub-addr-delim"
  858. (list false? integer? string?))))))
  859. (define-record-type* <opensmtpd-srs-configuration>
  860. opensmtpd-srs-configuration make-opensmtpd-srs-configuration
  861. opensmtpd-srs-configuration?
  862. ;; TODO should this be a file?
  863. (key opensmtpd-srs-configuration-key
  864. (default #f)
  865. (sanitize (lambda (var)
  866. (my/sanitize var "opensmtpd-srs-configuration" "key"
  867. (list false? boolean? string?)))))
  868. ;; TODO should this also be a file?
  869. (backup-key opensmtpd-srs-configuration-backup-key
  870. (default #f)
  871. (sanitize (lambda (var)
  872. (my/sanitize var "opensmtpd-srs-configuration" "backup-key"
  873. (list false? integer?)))))
  874. (ttl-delay opensmtpd-srs-configuration-ttl-delay
  875. (default #f)
  876. (sanitize (lambda (var)
  877. (my/sanitize var "opensmtpd-srs-configuration" "ttl-delay"
  878. (list false? string?))))))
  879. (define-record-type* <opensmtpd-queue-configuration>
  880. opensmtpd-queue-configuration make-opensmtpd-queue-configuration
  881. opensmtpd-queue-configuration?
  882. (compression opensmtpd-queue-configuration-compression
  883. (default #f)
  884. (sanitize (lambda (var)
  885. (my/sanitize var "opensmtpd-queue-configuration" "compression"
  886. (list boolean?)))))
  887. (encryption opensmtpd-queue-configuration-encryption
  888. (default #f)
  889. (sanitize (lambda (var)
  890. (my/sanitize var "opensmtpd-queue-configuration" "encryption"
  891. (list boolean? file-exists? string?)))))
  892. (ttl-delay opensmtpd-queue-configuration-ttl-delay
  893. (default #f)
  894. (sanitize (lambda (var)
  895. (my/sanitize var "opensmtpd-queue-configuration" "ttl-delay"
  896. (list false? string?))))))
  897. (define-record-type* <opensmtpd-configuration>
  898. opensmtpd-configuration make-opensmtpd-configuration
  899. opensmtpd-configuration?
  900. (package opensmtpd-configuration-package
  901. (default opensmtpd))
  902. (config-file opensmtpd-configuration-config-file
  903. (default #f))
  904. ;; FIXME/TODO should I include a admd authservid entry?
  905. ;; TODO sanitize this properly with perhaps a <sanitize-configuration>.
  906. (bounce opensmtpd-configuration-bounce
  907. (default #f)
  908. (sanitize (lambda (var)
  909. (my/sanitize var "opensmtpd-configuration" "bounce"
  910. (list false? list?)))))
  911. (cas opensmtpd-configuration-cas
  912. (default #f)
  913. (sanitize (lambda (var)
  914. (my/sanitize var "opensmtpd-configuration" "cas"
  915. (list false? list-of-opensmtpd-ca-configuration?)))))
  916. ;; list of many records of type opensmtpd-listen-on-configuration
  917. (listen-ons opensmtpd-configuration-listen-ons
  918. (default (list (opensmtpd-listen-on-configuration)))
  919. (sanitize (lambda (var)
  920. (if (list-of-opensmtpd-listen-on-configuration? var)
  921. var
  922. (begin
  923. (display "<opensmtpd-configuration> fieldname 'listen-ons' expects a list of records ")
  924. (display "of one or more unique <opensmtpd-listen-on-configuration> records.\n")
  925. (throw 'bad! var))))))
  926. ;; accepts type <opensmtpd-listen-on-socket-configuration-configuration>
  927. (listen-on-socket opensmtpd-configuration-listen-on-socket
  928. (default (opensmtpd-listen-on-socket-configuration-configuration)))
  929. (includes opensmtpd-configuration-includes ;; list of strings of absolute path names
  930. (default #f)
  931. (sanitize (lambda (var)
  932. (my/sanitize var "opensmtpd-configuration" "includes"
  933. (list false? list-of-strings?)))))
  934. (matches opensmtpd-configuration-matches
  935. (default (list (opensmtpd-match-configuration
  936. (action (opensmtpd-action-local-delivery-configuration
  937. (name "local")
  938. (method "mbox")))
  939. (options (list
  940. (opensmtpd-option-configuration
  941. (option "for local")))))
  942. (opensmtpd-match-configuration
  943. (action (opensmtpd-action-relay-configuration
  944. (name "outbound")))
  945. (options (list
  946. (opensmtpd-option-configuration
  947. (option "from local"))
  948. (opensmtpd-option-configuration
  949. (option "for any")))))))
  950. ;; TODO perhaps I should sanitize this function like I sanitized the 'filters'.
  951. ;; I definitely should sanitize this function a bit more. For example, you could have two different
  952. ;; actions, one for local delivery and one for remote, with the same name. I should make sure that
  953. ;; I have no two different actions with the same name.
  954. (sanitize (lambda (var)
  955. ;; Should we do more sanitizing here? eg: "from socket" should NOT have a table or value
  956. var
  957. (my/sanitize var "opensmtpd-configuration" "matches"
  958. (list list-of-unique-opensmtpd-match-configuration?)))))
  959. ;; list of many records of type mda-wrapper
  960. ;; TODO/FIXME support using gexps here
  961. ;; eg (list "name" gexp)
  962. (mda-wrappers opensmtpd-configuration-mda-wrappers
  963. (default #f)
  964. (sanitize (lambda (var)
  965. (my/sanitize var
  966. "opensmtpd-configuration"
  967. "mda-wrappers"
  968. (list false? string?)))))
  969. (mta-max-deferred opensmtpd-configuration-mta-max-deferred
  970. (default 100)
  971. (sanitize (lambda (var)
  972. (my/sanitize var "opensmtpd-configuration" "mta-max-deferred"
  973. (list number?)))))
  974. ;; TODO should I add a fieldname proc _proc-name_ _command_ as found in the man 5 smtpd.conf ?
  975. (queue opensmtpd-configuration-queue
  976. (default #f)
  977. (sanitize (lambda (var)
  978. (my/sanitize var "opensmtpd-configuration" "queue"
  979. (list false? opensmtpd-queue-configuration?)))))
  980. (smtp opensmtpd-configuration-smtp
  981. (default #f)
  982. (sanitize (lambda (var)
  983. (my/sanitize var "opensmtpd-configuration" "smtp"
  984. (list false? opensmtpd-smtp-configuration?)))))
  985. (srs opensmtpd-configuration-srs
  986. (default #f)
  987. (sanitize (lambda (var)
  988. (my/sanitize var "opensmtpd-configuration" "srs"
  989. (list false? opensmtpd-srs-configuration?))))))
  990. ;; this procedure sanitizes the fieldname opensmtpd-match-configuration-options
  991. (define* (sanitize-list-of-options-for-match-configuration %options
  992. #:key
  993. (for #f)
  994. (from #f)
  995. (auth #f)
  996. (helo #f)
  997. (mail-from #f)
  998. (rcpt-to #f)
  999. (tag #f)
  1000. (tls #f))
  1001. (if (null? %options)
  1002. (remove false?
  1003. (list for from auth helo mail-from rcpt-to tag tls))
  1004. (let* ((option-record (car %options))
  1005. (option-string (opensmtpd-option-configuration-option option-record)))
  1006. (cond ((string=? "auth" option-string)
  1007. (if auth
  1008. (throw-error %options
  1009. '("<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1010. "<opensmtpd-option-configuration> with fieldname 'option' with value 'auth'. \n"
  1011. "You can only have one option with value 'auth' in the options list.\n"))
  1012. (sanitize-list-of-options-for-match-configuration (cdr %options)
  1013. #:for for
  1014. #:from from
  1015. #:auth option-record
  1016. #:helo helo
  1017. #:mail-from mail-from
  1018. #:rcpt-to rcpt-to
  1019. #:tag tag
  1020. #:tls tls)))
  1021. ((string=? "helo" option-string)
  1022. (cond (helo
  1023. (throw-error %options
  1024. (list "<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1025. "<opensmtpd-option-configuration> with fieldname 'option' with value 'helo'. \n"
  1026. "You can only have one option with value 'helo' in the options list.\n")))
  1027. ((not (opensmtpd-option-configuration-data option-record))
  1028. (throw-error option-record
  1029. (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'helo' \n"
  1030. "must have a 'data' of type string or <opensmtpd-table-configuration>.\n")))
  1031. (else (sanitize-list-of-options-for-match-configuration (cdr %options)
  1032. #:for for
  1033. #:from from
  1034. #:auth auth
  1035. #:helo option-record
  1036. #:mail-from mail-from
  1037. #:rcpt-to rcpt-to
  1038. #:tag tag
  1039. #:tls tls))))
  1040. ((string=? "tag" option-string)
  1041. (cond (tag
  1042. (throw-error %options
  1043. (list
  1044. "<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1045. "<opensmtpd-option-configuration> with fieldname 'option' with value 'tag'. \n"
  1046. "You can only have one option with value 'tag' in the options list.\n")))
  1047. ((not (string? (opensmtpd-option-configuration-data option-record)))
  1048. (throw-error option-record
  1049. (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tag' \n"
  1050. "must have a 'data' of type string.\n")))
  1051. (else (sanitize-list-of-options-for-match-configuration (cdr %options)
  1052. #:for for
  1053. #:from from
  1054. #:auth auth
  1055. #:helo helo
  1056. #:mail-from mail-from
  1057. #:rcpt-to rcpt-to
  1058. #:tag option-record
  1059. #:tls tls))))
  1060. ((string=? "tls" option-string)
  1061. (if tls
  1062. (begin
  1063. (display (string-append
  1064. "<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1065. "<opensmtpd-option-configuration> with fieldname 'option' with value 'tls'. \n"
  1066. "You can only have one option with value 'tls' in the options list.\n"))
  1067. (throw 'bad! %options))
  1068. (sanitize-list-of-options-for-match-configuration (cdr %options)
  1069. #:for for
  1070. #:from from
  1071. #:auth auth
  1072. #:helo helo
  1073. #:mail-from mail-from
  1074. #:rcpt-to rcpt-to
  1075. #:tag tag
  1076. #:tls option-record)))
  1077. ((string=? "mail-from" option-string)
  1078. (cond (mail-from
  1079. (begin
  1080. (display (string-append
  1081. "<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1082. "<opensmtpd-option-configuration> with fieldname 'option' with value 'mail-from'. \n"
  1083. "You can only have one option with value 'mail-from' in the options list.\n"))
  1084. (throw 'bad! %options)))
  1085. ((not (opensmtpd-option-configuration-data option-record))
  1086. (throw-error option-record
  1087. (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'mail-from' \n"
  1088. "must have a 'data' of type string or <opensmtpd-table-configuration>.\n")))
  1089. (else (sanitize-list-of-options-for-match-configuration (cdr %options)
  1090. #:for for
  1091. #:from from
  1092. #:auth auth
  1093. #:helo helo
  1094. #:mail-from option-record
  1095. #:rcpt-to rcpt-to
  1096. #:tag tag
  1097. #:tls tls))))
  1098. ((string=? "rcpt-to" option-string)
  1099. (if rcpt-to
  1100. (begin
  1101. (display (string-append
  1102. "<opensmtpd-match-configuration>'s fieldname 'options' has two records of type \n"
  1103. "<opensmtpd-option-configuration> with fieldname 'option' with value 'rcpt-to'. \n"
  1104. "You can only have one option with value 'rcpt-to' in the options list.\n"))
  1105. (throw 'bad! %options))
  1106. (sanitize-list-of-options-for-match-configuration (cdr %options)
  1107. #:for for
  1108. #:from from
  1109. #:auth auth
  1110. #:helo helo
  1111. #:mail-from mail-from
  1112. #:rcpt-to option-record
  1113. #:tag tag
  1114. #:tls tls)))
  1115. ((string=? "for" (substring option-string 0 3))
  1116. (cond (for
  1117. (throw-error %options
  1118. `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'for' option. \n"
  1119. "But '" ,option-string "' and '" ,(opensmtpd-option-configuration-option for) "' are present.\n")))
  1120. ((and (string-in-list? option-string (list "for any" "for local")) ; for any cannot have a data field.
  1121. (or (opensmtpd-option-configuration-data option-record)
  1122. (opensmtpd-option-configuration-regex option-record)))
  1123. (throw-error option-record
  1124. (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for any' \n"
  1125. "or 'for local', then its 'data' and 'regex' field must be #f. \n")))
  1126. ((and (string-in-list? option-string (list "for domain" "for rcpt-to")) ; for domain must have a data field.
  1127. (not (opensmtpd-option-configuration-data option-record)))
  1128. (throw-error option-record
  1129. (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for domain' \n"
  1130. "or 'for rcpt-to', then its 'data' field must be a string or an \n"
  1131. "<opensmtpd-table-configuration> record.\n")))
  1132. (else (sanitize-list-of-options-for-match-configuration (cdr %options)
  1133. #:for option-record
  1134. #:from from
  1135. #:auth auth
  1136. #:helo helo
  1137. #:mail-from mail-from
  1138. #:rcpt-to rcpt-to
  1139. #:tag tag
  1140. #:tls tls))))
  1141. ((string=? "from" (substring option-string 0 4))
  1142. (cond (from
  1143. (throw-error %options
  1144. `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'from' option. \n"
  1145. "But '" ,option-string "' and '" ,(opensmtpd-option-configuration-option from) "' are present.\n")))
  1146. ((and (string-in-list? option-string (list "from any" "from local" "from socket")) ; for any cannot have a data field.
  1147. (or (opensmtpd-option-configuration-data option-record)
  1148. (opensmtpd-option-configuration-regex option-record)))
  1149. (throw-error option-record
  1150. (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from any', \n"
  1151. " 'from local', or 'from socket', then its 'data' and 'regex' field must be #f. \n")))
  1152. ((and (string-in-list? option-string (list "from mail-from" "from src")) ; for domain must have a data field.
  1153. (not (opensmtpd-option-configuration-data option-record)))
  1154. (throw-error option-record
  1155. (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from mail-from' \n"
  1156. "or 'from src', then its 'data' field must be a string or an \n"
  1157. "<opensmtpd-table-configuration> record.\n")))
  1158. (else (sanitize-list-of-options-for-match-configuration (cdr %options)
  1159. #:for for
  1160. #:from option-record
  1161. #:auth auth
  1162. #:helo helo
  1163. #:mail-from mail-from
  1164. #:rcpt-to rcpt-to
  1165. #:tag tag
  1166. #:tls tls))))))))
  1167. ;; some procedures for <opensmtpd-listen-on-configuration> and
  1168. ;; <opensmtpd-listen-on-socket-configuration-configuration>.
  1169. (define (sanitize-filters %list)
  1170. ;; the order of the first two tests in this cond is important.
  1171. ;; (false?) has to be 1st and (list-has-duplicates-or-non-filters?) has to be second.
  1172. ;; You may optionally re-order the other alternates in the cond.
  1173. (cond [(false? %list)
  1174. #f]
  1175. [(list-has-duplicates-or-non-filters? %list)
  1176. (begin
  1177. (display (string-append "<opensmtpd-listen-on-configuration> fieldname: 'filters' is a list, in which each unique element \n"
  1178. "is of type <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>.\n"))
  1179. (throw 'bad! %list))]
  1180. [else
  1181. (let loop ([%traversing-list %list]
  1182. [%original-list %list])
  1183. (if (null? %traversing-list)
  1184. %original-list
  1185. (cond
  1186. [(opensmtpd-filter-configuration? (car %traversing-list))
  1187. (loop (cdr %traversing-list) %original-list)]
  1188. [(filter-phase-has-message-and-value? (car %traversing-list))
  1189. (begin
  1190. (display (string-append "<opensmtpd-filter-phase-configuration> cannot have defined fieldnames 'value' \n"
  1191. "and 'message'.\n"))
  1192. (throw 'bad! (car %traversing-list)))]
  1193. [(filter-phase-decision-lacks-proper-message? (car %traversing-list))
  1194. (begin
  1195. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' options \n"
  1196. "\"disconnect\" and \"reject\" require fieldname 'message' to have a string.\n"
  1197. "The 'message' string must be RFC commpliant, which means that the string \n"
  1198. "must begin with a 4xx or 5xx status code.\n"))
  1199. (throw 'bad! (car %traversing-list)))]
  1200. [(filter-phase-lacks-proper-value? (car %traversing-list))
  1201. (begin
  1202. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' option \n"
  1203. "\"rewrite\" requires fieldname 'value' to have a number.\n"))
  1204. (throw 'bad! (car %traversing-list)))]
  1205. [(filter-phase-has-incorrect-junk-or-bypass? (car %traversing-list))
  1206. (begin
  1207. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n"
  1208. "\"junk\" or 'bypass' cannot have a defined fieldnames 'message' or 'value'.\n"))
  1209. (throw 'bad! (car %traversing-list)))]
  1210. [(filter-phase-junks-after-commit? (car %traversing-list))
  1211. (begin
  1212. (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n"
  1213. "\"junk\" cannot junk an email during 'phase' \"commit\".\n"))
  1214. (throw 'bad! (car %traversing-list)))]
  1215. [else (loop (cdr %traversing-list) %original-list)])))]))
  1216. (define (list-has-duplicates-or-non-filters? list)
  1217. (not (list-of-unique-filter-or-filter-phase? list)))
  1218. (define (filter-phase-has-message-and-value? record)
  1219. (and (opensmtpd-filter-phase-configuration-message record)
  1220. (opensmtpd-filter-phase-configuration-value record)))
  1221. ;; return #t if phase needs a message. Or if the message did not start with a 4xx or 5xx status code.
  1222. ;; otherwise #f
  1223. (define (filter-phase-decision-lacks-proper-message? record)
  1224. (define decision (opensmtpd-filter-phase-configuration-decision record))
  1225. (if (string-in-list? decision (list "disconnect" "reject"))
  1226. ;; this message needs to be RFC compliant, meaning
  1227. ;; that it need to start with 4xx or 5xx status code
  1228. (cond [(eq? #f (opensmtpd-filter-phase-configuration-message record))
  1229. #t]
  1230. [(string? (opensmtpd-filter-phase-configuration-message record))
  1231. (let ((number (string->number
  1232. (substring
  1233. (opensmtpd-filter-phase-configuration-message record) 0 3))))
  1234. (if (and (number? number)
  1235. (and (< number 600) (> number 399)))
  1236. #f
  1237. #t))])
  1238. #f))
  1239. ;; 'decision' "rewrite" requires 'value' to be a number.
  1240. (define (filter-phase-lacks-proper-value? record)
  1241. (define decision (opensmtpd-filter-phase-configuration-decision record))
  1242. (if (string=? "rewrite" decision)
  1243. (if (and (number? (opensmtpd-filter-phase-configuration-value record))
  1244. (eq? #f (opensmtpd-filter-phase-configuration-message record)))
  1245. #f
  1246. #t)
  1247. #f))
  1248. ;; 'decision' "junk" or "bypass" cannot have a message or a value.
  1249. (define (filter-phase-has-incorrect-junk-or-bypass? record)
  1250. (and
  1251. (string-in-list?
  1252. (opensmtpd-filter-phase-configuration-decision record)
  1253. (list "junk" "bypass"))
  1254. (or
  1255. (opensmtpd-filter-phase-configuration-value record)
  1256. (opensmtpd-filter-phase-configuration-message record))))
  1257. (define (filter-phase-junks-after-commit? record)
  1258. (and (string=? (opensmtpd-filter-phase-configuration-decision record) "junk")
  1259. (string=? (opensmtpd-filter-phase-configuration-phase record) "commit")))
  1260. ;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>
  1261. ;; returns # otherwise
  1262. (define (list-of-unique-filter-or-filter-phase? %filters)
  1263. (and (list? %filters)
  1264. (not (null? %filters))
  1265. ;; this list is made up of only <opensmtpd-filter-phase-configuration> or <opensmtpd-filter-configuration>
  1266. (primitive-eval
  1267. (cons 'and (map (lambda (filter)
  1268. (or (opensmtpd-filter-configuration? filter)
  1269. (opensmtpd-filter-phase-configuration? filter)))
  1270. %filters)))
  1271. (not (contains-duplicate? %filters))))
  1272. (define (throw-error var %strings)
  1273. (display (apply string-append %strings))
  1274. (throw 'bad! var))
  1275. ;; this is used for sanitizing <opensmtpd-filter-phase-configuration> fieldname 'options'
  1276. (define (contains-duplicate? list)
  1277. (if (null? list)
  1278. #f
  1279. (or
  1280. ;; check if (car list) is in (cdr list)
  1281. (primitive-eval (cons 'or
  1282. (map (lambda (var) (equal? var (car list)))
  1283. (cdr list))))
  1284. ;; check if (cdr list) contains duplicate
  1285. (contains-duplicate? (cdr list)))))
  1286. ;; given a list and procedure, this tests that each element of list is of type
  1287. ;; ie: (list-of-type? list string?) tests each list is of type string.
  1288. (define (list-of-type? list proc?)
  1289. (if (and (list? list)
  1290. (not (null? list)))
  1291. (let loop ([list list])
  1292. (if (null? list)
  1293. #t
  1294. (if (proc? (car list))
  1295. (loop (cdr list))
  1296. #f)))
  1297. #f))
  1298. (define (list-of-strings? list)
  1299. (list-of-type? list string?))
  1300. (define (list-of-unique-opensmtpd-option-configuration? list)
  1301. (and (list-of-type?
  1302. list opensmtpd-option-configuration?)
  1303. (not (contains-duplicate? list))))
  1304. (define (list-of-opensmtpd-ca-configuration? list)
  1305. (list-of-type? list opensmtpd-ca-configuration?))
  1306. (define (list-of-opensmtpd-pki-configuration? list)
  1307. (list-of-type? list opensmtpd-pki-configuration?))
  1308. (define (list-of-opensmtpd-listen-on-configuration? list)
  1309. (and (list-of-type? list opensmtpd-listen-on-configuration?)
  1310. (not (contains-duplicate? list))))
  1311. (define (list-of-unique-opensmtpd-match-configuration? list)
  1312. (and (list-of-type? list opensmtpd-match-configuration?)
  1313. (not (contains-duplicate? list))))
  1314. (define* (list-of-strings->string list
  1315. #:key
  1316. (string-delimiter ", ")
  1317. (postpend "")
  1318. (append "")
  1319. (drop-right-number 2))
  1320. (string-drop-right
  1321. (string-append (let loop ([list list])
  1322. (if (null? list)
  1323. ""
  1324. (string-append append (car list) postpend
  1325. string-delimiter
  1326. (loop (cdr list)))))
  1327. append)
  1328. drop-right-number))
  1329. ;; at the moment I cannot define this by using list-of-type?
  1330. ;; the first (not (null? assoc-list)) prevents that.
  1331. (define (assoc-list? assoc-list)
  1332. (list-of-type? assoc-list (lambda (pair)
  1333. (if (and (pair? pair)
  1334. (string? (car pair))
  1335. (string? (cdr pair)))
  1336. #t
  1337. #f))))
  1338. (define* (variable->string var #:key (append "") (postpend " "))
  1339. (let ([var (if (number? var)
  1340. (number->string var)
  1341. var)])
  1342. (if var
  1343. (string-append append var postpend)
  1344. "")))
  1345. ;; this procedure takes in one argument.
  1346. ;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns
  1347. ;; #t, #f if otherwise.
  1348. ;; TODO should I remove these two functions? And instead use the (opensmtpd-table-configuration-type) procedure?
  1349. (define (table-whose-data-are-assoc-list? table)
  1350. (if (not (opensmtpd-table-configuration? table))
  1351. #f
  1352. (assoc-list? (opensmtpd-table-configuration-data table))))
  1353. ;; this procedure takes in one argument
  1354. ;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns
  1355. ;; #t, #f if otherwise.
  1356. (define (table-whose-data-are-a-list-of-strings? table)
  1357. (if (not (opensmtpd-table-configuration? table))
  1358. #f
  1359. (list-of-strings? (opensmtpd-table-configuration-data table))))
  1360. ;; these next few functions help me to turn <table>s
  1361. ;; into strings suitable to fit into "opensmtpd.conf".
  1362. (define (assoc-list->string assoc-list)
  1363. (string-drop-right
  1364. (let loop ([assoc-list assoc-list])
  1365. (if (null? assoc-list)
  1366. ""
  1367. ;; pair is (cons "hello" "world") -> ("hello" . "world")
  1368. (let ([pair (car assoc-list)])
  1369. (string-append
  1370. "\"" (car pair) "\""
  1371. " = "
  1372. "\"" (cdr pair) "\""
  1373. ", "
  1374. (loop (cdr assoc-list))))))
  1375. 2))
  1376. ;; can be of type: (quote list-of-strings) or (quote assoc-list)
  1377. (define (opensmtpd-table-configuration->string table)
  1378. (string-append "table " (opensmtpd-table-configuration-name table) " "
  1379. (let ([type (opensmtpd-table-configuration-type table)])
  1380. (cond [(eq? type (quote list-of-strings))
  1381. (string-append "{ " (list-of-strings->string (opensmtpd-table-configuration-data table)
  1382. #:append "\""
  1383. #:drop-right-number 3
  1384. #:postpend "\"") " }")]
  1385. [(eq? type (quote assoc-list))
  1386. (string-append "{ " (assoc-list->string (opensmtpd-table-configuration-data table)) " }")]
  1387. [(eq? type (quote db))
  1388. (string-append "db:" (opensmtpd-table-configuration-data table))]
  1389. [(eq? type (quote file))
  1390. (string-append "file:" (opensmtpd-table-configuration-data table))]
  1391. [else (throw 'youMessedUp table)]))
  1392. " \n"))
  1393. ;; The following functions convert various records into strings.
  1394. (define (opensmtpd-listen-on-configuration->string record)
  1395. (string-append "listen on "
  1396. (opensmtpd-listen-on-configuration-interface record) " "
  1397. (let* ([hostname (opensmtpd-listen-on-configuration-hostname record)]
  1398. [hostnames (if (opensmtpd-listen-on-configuration-hostnames record)
  1399. (opensmtpd-table-configuration-name (opensmtpd-listen-on-configuration-hostnames record))
  1400. #f)]
  1401. [filters (opensmtpd-listen-on-configuration-filters record)]
  1402. [filter-name (if filters
  1403. (if (< 1 (length filters))
  1404. (generate-filter-chain-name filters)
  1405. (if (opensmtpd-filter-configuration? (car filters))
  1406. (opensmtpd-filter-configuration-name (car filters))
  1407. (opensmtpd-filter-phase-configuration-name (car filters))))
  1408. #f)]
  1409. [mask-src (opensmtpd-listen-on-configuration-mask-src record)]
  1410. [tag (opensmtpd-listen-on-configuration-tag record)]
  1411. [secure-connection (opensmtpd-listen-on-configuration-secure-connection record)]
  1412. [port (opensmtpd-listen-on-configuration-port record)]
  1413. [pki (opensmtpd-listen-on-configuration-pki record)]
  1414. [auth (opensmtpd-listen-on-configuration-auth record)]
  1415. [auth-optional (opensmtpd-listen-on-configuration-auth-optional record)])
  1416. (string-append
  1417. (if mask-src
  1418. (string-append "mask-src ")
  1419. "")
  1420. (variable->string hostname #:append "hostname ")
  1421. (variable->string hostnames #:append "hostnames <" #:postpend "> ")
  1422. (variable->string filter-name #:append "filter \"" #:postpend "\" ")
  1423. (variable->string tag #:append "tag \"" #:postpend "\" ")
  1424. (if secure-connection
  1425. (cond [(string=? "smtps" secure-connection)
  1426. "smtps "]
  1427. [(string=? "tls" secure-connection)
  1428. "tls "]
  1429. [(string=? "tls-require" secure-connection)
  1430. "tls-require "]
  1431. [(string=? "tls-require-verify" secure-connection)
  1432. "tls-require verify "])
  1433. "")
  1434. (variable->string port #:append "port " #:postpend " ")
  1435. (if pki
  1436. (variable->string (opensmtpd-pki-configuration-domain pki) #:append "pki ")
  1437. "")
  1438. (if auth
  1439. (string-append "auth "
  1440. (if (opensmtpd-table-configuration? auth)
  1441. (string-append "<" (opensmtpd-table-configuration-name auth) "> ")
  1442. ""))
  1443. "")
  1444. (if auth-optional
  1445. (string-append "auth-optional "
  1446. (if (opensmtpd-table-configuration? auth-optional)
  1447. (string-append "<" (opensmtpd-table-configuration-name auth-optional) "> ")
  1448. ""))
  1449. "")
  1450. "\n"))))
  1451. (define (opensmtpd-listen-on-socket-configuration->string record)
  1452. (string-append "listen on socket "
  1453. (let* ([filters (opensmtpd-listen-on-socket-configuration-configuration-filters record)]
  1454. [filter-name (if filters
  1455. (if (< 1 (length filters))
  1456. (generate-filter-chain-name filters)
  1457. (if (opensmtpd-filter-configuration? (car filters))
  1458. (opensmtpd-filter-configuration-name (car filters))
  1459. (opensmtpd-filter-phase-configuration-name (car filters))))
  1460. #f)]
  1461. [mask-src (opensmtpd-listen-on-socket-configuration-configuration-mask-src record)]
  1462. [tag (opensmtpd-listen-on-socket-configuration-configuration-tag record)])
  1463. (string-append
  1464. (if mask-src
  1465. (string-append "mask-src ")
  1466. "")
  1467. (variable->string filter-name #:append "filter \"" #:postpend "\" ")
  1468. (variable->string tag #:append "tag \"" #:postpend "\" ")
  1469. "\n"))))
  1470. (define (opensmtpd-action-relay-configuration->string record)
  1471. (let ([backup (opensmtpd-action-relay-configuration-backup record)]
  1472. [backup-mx (opensmtpd-action-relay-configuration-backup-mx record)]
  1473. [helo (opensmtpd-action-relay-configuration-helo record)]
  1474. ;; helo-src can either be a string IP address or an <opensmtpd-table-configuration>
  1475. [helo-src (if (opensmtpd-action-relay-configuration-helo-src record)
  1476. (if (string? (opensmtpd-action-relay-configuration-helo-src record))
  1477. (opensmtpd-action-relay-configuration-helo-src record)
  1478. (string-append "<\""
  1479. (opensmtpd-table-configuration-name
  1480. (opensmtpd-action-relay-configuration-src record))
  1481. "\">"))
  1482. #f)]
  1483. [domain (if (opensmtpd-action-relay-configuration-domain record)
  1484. (opensmtpd-table-configuration-name
  1485. (opensmtpd-action-relay-configuration-domain record))
  1486. #f)]
  1487. [host (opensmtpd-action-relay-configuration-host record)]
  1488. [name (opensmtpd-action-relay-configuration-name record)]
  1489. [pki (if (opensmtpd-action-relay-configuration-pki record)
  1490. (opensmtpd-pki-configuration-domain (opensmtpd-action-relay-configuration-pki record))
  1491. #f)]
  1492. [srs (opensmtpd-action-relay-configuration-srs record)]
  1493. [tls (opensmtpd-action-relay-configuration-tls record)]
  1494. [auth (if (opensmtpd-action-relay-configuration-auth record)
  1495. (opensmtpd-table-configuration-name
  1496. (opensmtpd-action-relay-configuration-auth record))
  1497. #f)]
  1498. [mail-from (opensmtpd-action-relay-configuration-mail-from record)]
  1499. ;; src can either be a string IP address or an <opensmtpd-table-configuration>
  1500. [src (if (opensmtpd-action-relay-configuration-src record)
  1501. (if (string? (opensmtpd-action-relay-configuration-src record))
  1502. (opensmtpd-action-relay-configuration-src record)
  1503. (string-append "<\""
  1504. (opensmtpd-table-configuration-name
  1505. (opensmtpd-action-relay-configuration-src record))
  1506. "\">"))
  1507. #f)]
  1508. )
  1509. (string-append
  1510. "\""
  1511. name
  1512. "\" " "relay "
  1513. ;;FIXME should I always quote the host fieldname? do I need to quote localhost via "localhost" ?
  1514. (variable->string host #:append "host \"" #:postpend "\" ")
  1515. (variable->string backup)
  1516. (variable->string backup-mx #:append "backup mx ")
  1517. (variable->string helo #:append "helo ")
  1518. (variable->string helo-src #:append "helo-src ")
  1519. (variable->string domain #:append "domain <\"" #:postpend "\"> ")
  1520. (variable->string host #:append "host ")
  1521. (variable->string pki #:append "pki ")
  1522. (variable->string srs)
  1523. (variable->string tls #:append "tls ")
  1524. (variable->string auth #:append "auth <" #:postpend "> ")
  1525. (variable->string mail-from #:append "mail-from ")
  1526. (variable->string src #:append "src ")
  1527. "\n")))
  1528. (define (opensmtpd-lmtp-configuration->string record)
  1529. (string-append "lmtp "
  1530. (opensmtpd-lmtp-configuration-destination record)
  1531. (if (opensmtpd-lmtp-configuration-rcpt-to record)
  1532. (begin
  1533. " " (opensmtpd-lmtp-configuration-rcpt-to record))
  1534. "")))
  1535. (define (opensmtpd-mda-configuration->string record)
  1536. (string-append "mda "
  1537. (opensmtpd-mda-configuration-command record) " "))
  1538. (define (opensmtpd-maildir-configuration->string record)
  1539. (string-append "maildir "
  1540. "\""
  1541. (if (opensmtpd-maildir-configuration-pathname record)
  1542. (opensmtpd-maildir-configuration-pathname record)
  1543. "~/Maildir")
  1544. "\""
  1545. (if (opensmtpd-maildir-configuration-junk record)
  1546. " junk "
  1547. " ")))
  1548. (define (opensmtpd-action-local-delivery-configuration->string record)
  1549. (let ([name (opensmtpd-action-local-delivery-configuration-name record)]
  1550. [method (opensmtpd-action-local-delivery-configuration-method record)]
  1551. [alias (if (opensmtpd-action-local-delivery-configuration-alias record)
  1552. (opensmtpd-table-configuration-name
  1553. (opensmtpd-action-local-delivery-configuration-alias record))
  1554. #f)]
  1555. [ttl (opensmtpd-action-local-delivery-configuration-ttl record)]
  1556. [user (opensmtpd-action-local-delivery-configuration-user record)]
  1557. [userbase (if (opensmtpd-action-local-delivery-configuration-userbase record)
  1558. (opensmtpd-table-configuration-name
  1559. (opensmtpd-action-local-delivery-configuration-userbase record))
  1560. #f)]
  1561. [virtual (if (opensmtpd-action-local-delivery-configuration-virtual record)
  1562. (opensmtpd-table-configuration-name
  1563. (opensmtpd-action-local-delivery-configuration-virtual record))
  1564. #f)]
  1565. [wrapper (opensmtpd-action-local-delivery-configuration-wrapper record)])
  1566. (string-append
  1567. "\"" name "\" "
  1568. (cond [(string? method)
  1569. (string-append method " ")]
  1570. [(opensmtpd-mda-configuration? method)
  1571. (opensmtpd-mda-configuration->string method)]
  1572. [(opensmtpd-lmtp-configuration? method)
  1573. (opensmtpd-lmtp-configuration->string method)]
  1574. [(opensmtpd-maildir-configuration? method)
  1575. (opensmtpd-maildir-configuration->string method)])
  1576. ;; FIXME/TODO support specifying alias file:/path/to/alias-file ?
  1577. ;; I do not think that is something that I can do...
  1578. (variable->string alias #:append "alias <\"" #:postpend "\"> ")
  1579. (variable->string ttl #:append "ttl ")
  1580. (variable->string user #:append "user ")
  1581. (variable->string userbase #:append "userbase <\"" #:postpend "\"> ")
  1582. (variable->string virtual #:append "virtual <" #:postpend "> ")
  1583. (variable->string wrapper #:append "wrapper "))))
  1584. ;; this function turns both opensmtpd-action-local-delivery-configuration and
  1585. ;; opensmtpd-action-relay-configuration into strings.
  1586. (define (opensmtpd-action->string record)
  1587. (string-append "action "
  1588. (cond [(opensmtpd-action-local-delivery-configuration? record)
  1589. (opensmtpd-action-local-delivery-configuration->string record)]
  1590. [(opensmtpd-action-relay-configuration? record)
  1591. (opensmtpd-action-relay-configuration->string record)])
  1592. " \n"))
  1593. ;; this turns option records found in <opensmtpd-match-configuration> into strings.
  1594. (define* (opensmtpd-option-configuration->string record
  1595. #:key
  1596. (space-after-! #f))
  1597. (let ([not (opensmtpd-option-configuration-not record)]
  1598. [option (opensmtpd-option-configuration-option record)]
  1599. [regex (opensmtpd-option-configuration-regex record)]
  1600. [data (opensmtpd-option-configuration-data record)])
  1601. (string-append
  1602. (if not
  1603. (if space-after-!
  1604. "! "
  1605. "!")
  1606. "")
  1607. option " "
  1608. (if regex
  1609. "regex "
  1610. "")
  1611. (if data
  1612. (if (opensmtpd-table-configuration? data)
  1613. (string-append "<" (opensmtpd-table-configuration-name data) "> ")
  1614. (string-append data " "))
  1615. ""))))
  1616. (define (opensmtpd-match-configuration->string record)
  1617. (string-append "match "
  1618. (let* ([action (opensmtpd-match-configuration-action record)]
  1619. [name (cond [(opensmtpd-action-relay-configuration? action)
  1620. (opensmtpd-action-relay-configuration-name action)]
  1621. [(opensmtpd-action-local-delivery-configuration? action)
  1622. (opensmtpd-action-local-delivery-configuration-name action)]
  1623. [else 'reject])]
  1624. [options (opensmtpd-match-configuration-options record)])
  1625. (string-append
  1626. (if options
  1627. (apply string-append
  1628. (map opensmtpd-option-configuration->string options))
  1629. "")
  1630. (if (string? name)
  1631. (string-append "action " "\"" name "\" ")
  1632. "reject ")
  1633. "\n"))))
  1634. (define (opensmtpd-ca-configuration->string record)
  1635. (string-append "ca " (opensmtpd-ca-configuration-name record) " "
  1636. "cert \"" (opensmtpd-ca-configuration-file record) "\"\n"))
  1637. (define (opensmtpd-pki-configuration->string record)
  1638. (let ([domain (opensmtpd-pki-configuration-domain record)]
  1639. [cert (opensmtpd-pki-configuration-cert record)]
  1640. [key (opensmtpd-pki-configuration-key record)]
  1641. [dhe (opensmtpd-pki-configuration-dhe record)])
  1642. (string-append "pki " domain " " "cert \"" cert "\" \n"
  1643. "pki " domain " " "key \"" key "\" \n"
  1644. (if dhe
  1645. (string-append
  1646. "pki " domain " " "dhe " dhe "\n")
  1647. ""))))
  1648. (define (generate-filter-chain-name list-of-filters)
  1649. (string-drop-right (apply string-append
  1650. (flatten
  1651. (map (lambda (filter)
  1652. (list
  1653. (if (opensmtpd-filter-configuration? filter)
  1654. (opensmtpd-filter-configuration-name filter)
  1655. (opensmtpd-filter-phase-configuration-name filter))
  1656. "-"))
  1657. list-of-filters)))
  1658. 1))
  1659. ;; this procedure takes in a list of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>,
  1660. ;; returns a string of the form:
  1661. ;; filter "uniquelyGeneratedName" chain chain { "filter-name", "filter-name2" [, ...]}
  1662. (define (opensmtpd-filter-chain->string list-of-filters)
  1663. (string-append "filter \""
  1664. (generate-filter-chain-name list-of-filters)
  1665. "\" "
  1666. "chain {"
  1667. (string-drop-right
  1668. (apply string-append
  1669. (flatten
  1670. (map (lambda (filter)
  1671. (list
  1672. "\""
  1673. (if (opensmtpd-filter-configuration? filter)
  1674. (opensmtpd-filter-configuration-name filter)
  1675. (opensmtpd-filter-phase-configuration-name filter))
  1676. "\", "))
  1677. list-of-filters))
  1678. ) 2)
  1679. "}\n"))
  1680. (define (opensmtpd-filter-phase-configuration->string record)
  1681. (let ([name (opensmtpd-filter-phase-configuration-name record)]
  1682. [phase (opensmtpd-filter-phase-configuration-phase record)]
  1683. [decision (opensmtpd-filter-phase-configuration-decision record)]
  1684. [options (opensmtpd-filter-phase-configuration-options record)]
  1685. [message (opensmtpd-filter-phase-configuration-message record)]
  1686. [value (opensmtpd-filter-phase-configuration-value record)])
  1687. (string-append "filter "
  1688. "\"" name "\" "
  1689. "phase " phase " "
  1690. "match "
  1691. (apply string-append ; turn the options into a string
  1692. (flatten
  1693. (map (lambda (option)
  1694. (opensmtpd-option-configuration->string option #:space-after-! #f))
  1695. options)))
  1696. " "
  1697. decision " "
  1698. (if (string-in-list? decision (list "reject" "disconnect"))
  1699. (string-append "\"" message "\"")
  1700. "")
  1701. (if (string=? "rewrite" decision)
  1702. (string-append "rewrite " (number->string value))
  1703. "")
  1704. "\n")))
  1705. ;; filters elements may be <opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>,
  1706. ;; and lists that look like (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...)
  1707. ;; ...)
  1708. ;; this function converts it to a string.
  1709. ;; Consider if a user passed in a valid <opensmtpd-configuration>, whose total valid filters
  1710. ;; so that (get-opensmtpd-filters (opensmtpd-configuration)) returns
  1711. ;; look like this: (we will call this list "total filters"):
  1712. ;; (list (opensmtpd-filter
  1713. ;; (name "rspamd")
  1714. ;; (proc "rspamd"))
  1715. ;; (list (opensmtpd-filter-phase-configuration ; this is a listen-on, with a filter-chain.
  1716. ;; (name "dkimsign")
  1717. ;; ...)
  1718. ;; (opensmtpd-filter
  1719. ;; (name "rspamd")
  1720. ;; (proc "rspamd"))))
  1721. ;;
  1722. ;; did you notice that filter "rspamd" is listed twice? How do you make sure that it is NOT
  1723. ;; printed twice in smtpd.conf?
  1724. ;; 1st flatten "total filters", then remove its duplicates. Then print all of those filters.
  1725. ;; 2nd now we go through "total filters", and we only print the non-filter-chains.
  1726. (define (opensmtpd-filters->string filters)
  1727. ;; first display the unique <opensmtpd-filter-configuration>s. and <opensmtpd-filter-phase-configuration>s.
  1728. ;; to do this: flatten filters, then remove duplicates.
  1729. (string-append
  1730. (apply string-append
  1731. (map (lambda (filter)
  1732. (cond ((opensmtpd-filter-phase-configuration? filter)
  1733. (opensmtpd-filter-phase-configuration->string filter))
  1734. (else ; you are a <opensmtpd-filter-configuration>
  1735. (string-append "filter "
  1736. "\"" (opensmtpd-filter-configuration-name filter) "\" "
  1737. (if (opensmtpd-filter-exec filter)
  1738. "proc-exec "
  1739. "proc ")
  1740. "\"" (opensmtpd-filter-configuration-proc filter) "\""
  1741. "\n"))))
  1742. (delete-duplicates (flatten filters))))
  1743. ;; now we have to print the filter chains.
  1744. (apply string-append
  1745. (remove boolean?
  1746. (map (lambda (filter)
  1747. (cond ((list? filter)
  1748. (opensmtpd-filter-chain->string filter))
  1749. (else ; you are a <opensmtpd-filter-configuration>
  1750. #f)))
  1751. filters)))))
  1752. (define (opensmtpd-configuration-listen->string string)
  1753. (string-append
  1754. "include \"" string "\"\n"))
  1755. (define (opensmtpd-configuration-srs->string record)
  1756. (let ([key (opensmtpd-srs-configuration-key record)]
  1757. [backup-key (opensmtpd-srs-configuration-backup-key record)]
  1758. [ttl-delay (opensmtpd-srs-configuration-ttl-delay record)])
  1759. (string-append
  1760. (variable->string key #:append "srs key " #:postpend "\n")
  1761. (variable->string backup-key #:append "srs key backup " #:postpend "\n")
  1762. (variable->string ttl-delay #:append "srs ttl " #:postpend "\n")
  1763. "\n")))
  1764. ;; TODO make sure all options here work! I just fixed limit-max-rcpt!
  1765. (define (opensmtpd-smtp-configuration->string record)
  1766. (let ([ciphers (opensmtpd-smtp-configuration-ciphers record)]
  1767. [limit-max-mails (opensmtpd-smtp-configuration-limit-max-mails record)]
  1768. [limit-max-rcpt (opensmtpd-smtp-configuration-limit-max-rcpt record)]
  1769. [max-message-size (opensmtpd-smtp-configuration-max-message-size record)]
  1770. [sub-addr-delim (opensmtpd-smtp-configuration-sub-addr-delim record)])
  1771. (string-append
  1772. (variable->string ciphers #:append "smtp ciphers " #:postpend "\n")
  1773. (variable->string limit-max-mails #:append "smtp limit max-mails " #:postpend "\n")
  1774. (variable->string limit-max-rcpt #:append "smtp limit max-rcpt " #:postpend "\n")
  1775. (variable->string max-message-size #:append "smtp max-message-size " #:postpend "\n")
  1776. (variable->string sub-addr-delim #:append "smtp sub-addr-delim " #:postpend "\n")
  1777. "\n")))
  1778. (define (opensmtpd-configuration-queue->string record)
  1779. (let ([compression (opensmtpd-queue-configuration-compression record)]
  1780. [encryption (opensmtpd-queue-configuration-encryption record)]
  1781. [ttl-delay (opensmtpd-queue-configuration-ttl-delay record)])
  1782. (string-append
  1783. (if compression
  1784. "queue compression\n"
  1785. "")
  1786. (if encryption
  1787. (string-append
  1788. "queue encryption "
  1789. (if (not (boolean? encryption))
  1790. encryption
  1791. "")
  1792. "\n")
  1793. "")
  1794. (if ttl-delay
  1795. (string-append "queue ttl" ttl-delay "\n")
  1796. ""))))
  1797. ;; build a list of <opensmtpd-action> from
  1798. ;; opensmtpd-configuration-matches, which is a list of <opensmtpd-match-configuration>. Each <opensmtpd-match-configuration> has a fieldname
  1799. ;; 'action', which accepts an <opensmtpd-action>.
  1800. (define (get-opensmtpd-actions record)
  1801. (define opensmtpd-actions
  1802. (let loop ([list (opensmtpd-configuration-matches record)])
  1803. (if (null? list)
  1804. '()
  1805. (cons (opensmtpd-match-configuration-action (car list))
  1806. (loop (cdr list))))))
  1807. (delete-duplicates (append opensmtpd-actions)))
  1808. ;; build a list of opensmtpd-pki-configurations from
  1809. ;; opensmtpd-configuration-listen-ons and
  1810. ;; get-opensmtpd-actions
  1811. (define (get-opensmtpd-pki-configurations record)
  1812. ;; TODO/FIXME/maybe/wishlist could get-opensmtpd-actions -> NOT have an opensmtpd-action-relay-configuration?
  1813. ;; I think so. And if it did NOT have a relay configuration, then action-pkis would be '() when
  1814. ;; it needs to be #f. because if the opensmtpd-configuration has NO pkis, then this function will
  1815. ;; return '(), when it should return #f. If it returns '(), then opensmtpd-configuration-fieldname->string will
  1816. ;; print the string "\n" instead of ""
  1817. (define action-pkis
  1818. (let loop1 ([list (get-opensmtpd-actions record)])
  1819. (if (null? list)
  1820. '()
  1821. (if (and (opensmtpd-action-relay-configuration? (car list))
  1822. (opensmtpd-action-relay-configuration-pki (car list)))
  1823. (cons (opensmtpd-action-relay-configuration-pki (car list))
  1824. (loop1 (cdr list)))
  1825. (loop1 (cdr list))))))
  1826. ;; FIXME/TODO/maybe/wishlist
  1827. ;; this could be #f aka left blank. aka there are no listen-ons records with pkis.
  1828. ;; aka there are no lines in the configuration like:
  1829. ;; listen on eth0 tls pki smtp.gnucode.me in that case the smtpd.conf will have an extra "\n"
  1830. (define listen-on-pkis
  1831. (let loop2 ([list (opensmtpd-configuration-listen-ons record)])
  1832. (if (null? list)
  1833. '()
  1834. (if (opensmtpd-listen-on-configuration-pki (car list))
  1835. (cons (opensmtpd-listen-on-configuration-pki (car list))
  1836. (loop2 (cdr list)))
  1837. (loop2 (cdr list))))))
  1838. (delete-duplicates (append action-pkis listen-on-pkis)))
  1839. ;; takes in a <opensmtpd-configuration> and returns a list whose elements are <opensmtpd-filter-configuration>,
  1840. ;; <opensmtpd-filter-phase-configuration>, and a filter-chain.
  1841. ;; It returns a list of <opensmtpd-filter-configuration> and/or <opensmtpd-filter-phase-configuration>
  1842. ;; here's an example of what this procedure might return:
  1843. ;; (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...)
  1844. ;; (openmstpd-filter ...) (opensmtpd-filter-phase-configuration ...)
  1845. ;; ;; this next list is a filter-chain.
  1846. ;; (list (opensmtpd-filter-phase-configuration ...) (opensmtpd-filter-configuration...)))
  1847. ;;
  1848. ;; This procedure handles filter chains a little odd.
  1849. (define (get-opensmtpd-filters record)
  1850. (define list-of-listen-on-records (if (opensmtpd-configuration-listen-ons record)
  1851. (opensmtpd-configuration-listen-ons record)
  1852. '()))
  1853. (define listen-on-socket-filters
  1854. (if (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record))
  1855. (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record))
  1856. '()))
  1857. (delete-duplicates
  1858. (append (remove boolean?
  1859. (map-in-order (lambda (listen-on-record) ; get the filters found in the <listen-on-record>s
  1860. (if (and (opensmtpd-listen-on-configuration-filters listen-on-record)
  1861. (= 1 (length (opensmtpd-listen-on-configuration-filters
  1862. listen-on-record))))
  1863. (car (opensmtpd-listen-on-configuration-filters listen-on-record))
  1864. (opensmtpd-listen-on-configuration-filters listen-on-record)))
  1865. list-of-listen-on-records))
  1866. listen-on-socket-filters)))
  1867. (define (flatten . lst)
  1868. "Return a list that recursively concatenates all sub-lists of LST."
  1869. (define (flatten1 head out)
  1870. (if (list? head)
  1871. (fold-right flatten1 out head)
  1872. (cons head out)))
  1873. (fold-right flatten1 '() lst))
  1874. ;; This function takes in a record, or list, or anything, and returns
  1875. ;; a list of <opensmtpd-table-configuration>s assuming the thing you passed into it had
  1876. ;; any <opensmtpd-table-configuration>s.
  1877. ;;
  1878. ;; is object record? call func on it's fieldnames
  1879. ;; is object list? loop through it's fieldnames calling func on it's records
  1880. ;; is object #f or string? or '()? -> #f
  1881. ;; TODO this function is wasteful. For every value it gets,
  1882. ;; it is calling (delete-duplicates (remove boolean? (flatten))). Some of the
  1883. ;; elements it gets are records. It should only call (delete-duplicates (remove boolean? (flatten)))
  1884. ;; once.
  1885. (define (get-opensmtpd-tables value)
  1886. (delete-duplicates
  1887. (remove boolean? (flatten ;; turn (list '(1) '(2 '(3))) -> '(1 2 3)
  1888. (cond ((opensmtpd-table-configuration? value)
  1889. value)
  1890. ((record? value)
  1891. (let* ([record-type (record-type-descriptor value)]
  1892. [list-of-record-fieldnames (record-type-fields record-type)])
  1893. (map (lambda (fieldname)
  1894. (get-opensmtpd-tables ((record-accessor record-type fieldname) value)))
  1895. list-of-record-fieldnames)))
  1896. ((and (list? value) (not (null? value)))
  1897. (map get-opensmtpd-tables value))
  1898. (else #f))))))
  1899. (define (opensmtpd-configuration-fieldname->string record fieldname-accessor record->string)
  1900. (if (fieldname-accessor record)
  1901. (begin
  1902. (string-append
  1903. (list-of-records->string (fieldname-accessor record) record->string) "\n"))
  1904. ""))
  1905. (define (list-of-records->string list-of-records record->string)
  1906. (string-append
  1907. (cond [(not (list? list-of-records))
  1908. (record->string list-of-records)]
  1909. [else
  1910. (let loop ([list list-of-records])
  1911. (if (null? list)
  1912. ""
  1913. (string-append
  1914. (record->string (car list))
  1915. (loop (cdr list)))))])))
  1916. ;; FIXME/TODO should I use format here srfi-28 ?
  1917. ;; web.scm nginx does a (format #f "string" "another string")
  1918. ;; this could be a list like (list (file-append opensmtpd-dkimsign "/libexec/filter") "-d gnucode.me -s /path/to/selector.cert")
  1919. ;; Then opensmtpd-configuration->mixed-text-file could be rewritten to be something like
  1920. ;; (mixed-text-file (eval `(string-append (opensmtpd-configuration-fieldname->string ...)) (gnu services mail)))
  1921. (define (opensmtpd-configuration->mixed-text-file record)
  1922. ;; should I use this named let, or should I give this a name, or not use it at all...
  1923. ;; eg: (write-all-fieldnames (list (cons fieldname fieldname->string) (cons fieldname2 fieldname->string)))
  1924. ;; (let loop ([list (list (cons opensmtpd-configuration-includes (lambda (string)
  1925. ;; (string-append
  1926. ;; "include \"" string "\"\n")))
  1927. ;; (cons opensmtpd-configuration-smtp opensmtpd-smtp->string)
  1928. ;; (cons opensmtpd-configuration-srs opensmtpd-srs->string))])
  1929. ;; (if (null? list)
  1930. ;; ""
  1931. ;; (string-append (opensmtpd-configuration-fieldname->string record
  1932. ;; (caar list)
  1933. ;; (cdar list))
  1934. ;; (loop (cdr list)))))
  1935. ;;(mixed-text-file "opensmtpd.conf")
  1936. (string-append
  1937. ;; write out the includes
  1938. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-includes
  1939. opensmtpd-configuration-listen->string)
  1940. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-bounce
  1941. (lambda (%bounce)
  1942. (if %bounce
  1943. (list-of-strings->string %bounce)
  1944. "")))
  1945. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-smtp
  1946. opensmtpd-smtp-configuration->string)
  1947. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-srs
  1948. opensmtpd-configuration-srs->string)
  1949. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-queue
  1950. opensmtpd-configuration-queue->string)
  1951. ;; write out the mta-max-deferred
  1952. (opensmtpd-configuration-fieldname->string
  1953. record opensmtpd-configuration-mta-max-deferred
  1954. (lambda (var)
  1955. (string-append "mta max-deferred "
  1956. (number->string (opensmtpd-configuration-mta-max-deferred record)) "\n")))
  1957. ;;write out all the tables
  1958. (opensmtpd-configuration-fieldname->string record get-opensmtpd-tables opensmtpd-table-configuration->string)
  1959. ;; TODO should I change the below line of code into these two lines of code?
  1960. ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string)
  1961. ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string)
  1962. ;; write out all the filters
  1963. (opensmtpd-filters->string (get-opensmtpd-filters record))
  1964. ;; write out all the cas
  1965. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-cas opensmtpd-ca-configuration->string)
  1966. ;; write out all the pkis
  1967. (opensmtpd-configuration-fieldname->string record get-opensmtpd-pki-configurations opensmtpd-pki-configuration->string)
  1968. ;; write all of the listen-on-records
  1969. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-ons
  1970. opensmtpd-listen-on-configuration->string)
  1971. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-on-socket
  1972. opensmtpd-listen-on-socket-configuration->string)
  1973. ;; write all the actions
  1974. (opensmtpd-configuration-fieldname->string record get-opensmtpd-actions
  1975. opensmtpd-action->string)
  1976. ;; write all of the matches
  1977. (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-matches opensmtpd-match-configuration->string)))
  1978. (define (opensmtpd-shepherd-service config)
  1979. (list (shepherd-service
  1980. (provision '(smtpd ;;config
  1981. ))
  1982. (requirement '(loopback))
  1983. (documentation "Run the OpenSMTPD daemon.")
  1984. ;; FIXME/TODO add a config option to show the current smtpd.conf file.
  1985. ;; (config #~(display (or #$(opensmtpd-configuration-config-file config)
  1986. ;; #$(opensmtpd-configuration->mixed-text-file config))))
  1987. (start (let ((smtpd (file-append (opensmtpd-configuration-package config) "/sbin/smtpd")))
  1988. #~(make-forkexec-constructor
  1989. (list #$smtpd "-f" (or #$(opensmtpd-configuration-config-file config)
  1990. #$(opensmtpd-configuration->mixed-text-file config)))
  1991. #:pid-file "/var/run/smtpd.pid")))
  1992. (stop #~(make-kill-destructor)))))
  1993. (define %opensmtpd-accounts
  1994. (list (user-group
  1995. (name "smtpq")
  1996. (system? #t))
  1997. (user-account
  1998. (name "smtpd")
  1999. (group "nogroup")
  2000. (system? #t)
  2001. (comment "SMTP Daemon")
  2002. (home-directory "/var/empty")
  2003. (shell (file-append shadow "/sbin/nologin")))
  2004. (user-account
  2005. (name "smtpq")
  2006. (group "smtpq")
  2007. (system? #t)
  2008. (comment "SMTPD Queue")
  2009. (home-directory "/var/empty")
  2010. (shell (file-append shadow "/sbin/nologin")))))
  2011. (define (opensmtpd-activation config)
  2012. (let ((smtpd (file-append (opensmtpd-configuration-package config) "/sbin/smtpd"))
  2013. (config-file (opensmtpd-configuration-config-file config))
  2014. (configuration (opensmtpd-configuration->mixed-text-file config)))
  2015. #~(begin
  2016. (use-modules (guix build utils))
  2017. ;; Create mbox and spool directories.
  2018. (mkdir-p "/var/mail")
  2019. (mkdir-p "/var/spool/smtpd")
  2020. (chmod "/var/spool/smtpd" #o711)
  2021. (mkdir-p "/var/spool/mail")
  2022. (chmod "/var/spool/mail" #o711)
  2023. (display (string-append "smtpd: checking syntax of "
  2024. (or
  2025. #$config-file
  2026. #$configuration)
  2027. "\n"))
  2028. (system* #$smtpd "-nf"
  2029. (or
  2030. #$config-file
  2031. #$configuration)))))
  2032. (define %opensmtpd-pam-services
  2033. (list (unix-pam-service "smtpd")))
  2034. (define opensmtpd-service-type
  2035. (service-type
  2036. (name 'opensmtpd)
  2037. (extensions
  2038. (list (service-extension account-service-type
  2039. (const %opensmtpd-accounts))
  2040. (service-extension activation-service-type
  2041. opensmtpd-activation)
  2042. (service-extension pam-root-service-type
  2043. (const %opensmtpd-pam-services))
  2044. (service-extension profile-service-type
  2045. (compose list opensmtpd-configuration-package))
  2046. (service-extension shepherd-root-service-type
  2047. opensmtpd-shepherd-service)))
  2048. (default-value (opensmtpd-configuration))
  2049. (description "Run the Opensmtpd email server.")))