at 23.05-pre 6.7 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 name = "maddy"; 8 9 cfg = config.services.maddy; 10 11 defaultConfig = '' 12 # Minimal configuration with TLS disabled, adapted from upstream example 13 # configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf 14 # Do not use this in production! 15 16 tls off 17 18 auth.pass_table local_authdb { 19 table sql_table { 20 driver sqlite3 21 dsn credentials.db 22 table_name passwords 23 } 24 } 25 26 storage.imapsql local_mailboxes { 27 driver sqlite3 28 dsn imapsql.db 29 } 30 31 table.chain local_rewrites { 32 optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" 33 optional_step static { 34 entry postmaster postmaster@$(primary_domain) 35 } 36 optional_step file /etc/maddy/aliases 37 } 38 msgpipeline local_routing { 39 destination postmaster $(local_domains) { 40 modify { 41 replace_rcpt &local_rewrites 42 } 43 deliver_to &local_mailboxes 44 } 45 default_destination { 46 reject 550 5.1.1 "User doesn't exist" 47 } 48 } 49 50 smtp tcp://0.0.0.0:25 { 51 limits { 52 all rate 20 1s 53 all concurrency 10 54 } 55 dmarc yes 56 check { 57 require_mx_record 58 dkim 59 spf 60 } 61 source $(local_domains) { 62 reject 501 5.1.8 "Use Submission for outgoing SMTP" 63 } 64 default_source { 65 destination postmaster $(local_domains) { 66 deliver_to &local_routing 67 } 68 default_destination { 69 reject 550 5.1.1 "User doesn't exist" 70 } 71 } 72 } 73 74 submission tcp://0.0.0.0:587 { 75 limits { 76 all rate 50 1s 77 } 78 auth &local_authdb 79 source $(local_domains) { 80 check { 81 authorize_sender { 82 prepare_email &local_rewrites 83 user_to_email identity 84 } 85 } 86 destination postmaster $(local_domains) { 87 deliver_to &local_routing 88 } 89 default_destination { 90 modify { 91 dkim $(primary_domain) $(local_domains) default 92 } 93 deliver_to &remote_queue 94 } 95 } 96 default_source { 97 reject 501 5.1.8 "Non-local sender domain" 98 } 99 } 100 101 target.remote outbound_delivery { 102 limits { 103 destination rate 20 1s 104 destination concurrency 10 105 } 106 mx_auth { 107 dane 108 mtasts { 109 cache fs 110 fs_dir mtasts_cache/ 111 } 112 local_policy { 113 min_tls_level encrypted 114 min_mx_level none 115 } 116 } 117 } 118 119 target.queue remote_queue { 120 target &outbound_delivery 121 autogenerated_msg_domain $(primary_domain) 122 bounce { 123 destination postmaster $(local_domains) { 124 deliver_to &local_routing 125 } 126 default_destination { 127 reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" 128 } 129 } 130 } 131 132 imap tcp://0.0.0.0:143 { 133 auth &local_authdb 134 storage &local_mailboxes 135 } 136 ''; 137 138in { 139 options = { 140 services.maddy = { 141 142 enable = mkEnableOption (lib.mdDoc "Maddy, a free an open source mail server"); 143 144 user = mkOption { 145 default = "maddy"; 146 type = with types; uniq string; 147 description = lib.mdDoc '' 148 User account under which maddy runs. 149 150 ::: {.note} 151 If left as the default value this user will automatically be created 152 on system activation, otherwise the sysadmin is responsible for 153 ensuring the user exists before the maddy service starts. 154 ::: 155 ''; 156 }; 157 158 group = mkOption { 159 default = "maddy"; 160 type = with types; uniq string; 161 description = lib.mdDoc '' 162 Group account under which maddy runs. 163 164 ::: {.note} 165 If left as the default value this group will automatically be created 166 on system activation, otherwise the sysadmin is responsible for 167 ensuring the group exists before the maddy service starts. 168 ::: 169 ''; 170 }; 171 172 hostname = mkOption { 173 default = "localhost"; 174 type = with types; uniq string; 175 example = ''example.com''; 176 description = lib.mdDoc '' 177 Hostname to use. It should be FQDN. 178 ''; 179 }; 180 181 primaryDomain = mkOption { 182 default = "localhost"; 183 type = with types; uniq string; 184 example = ''mail.example.com''; 185 description = lib.mdDoc '' 186 Primary MX domain to use. It should be FQDN. 187 ''; 188 }; 189 190 localDomains = mkOption { 191 type = with types; listOf str; 192 default = ["$(primary_domain)"]; 193 example = [ 194 "$(primary_domain)" 195 "example.com" 196 "other.example.com" 197 ]; 198 description = lib.mdDoc '' 199 Define list of allowed domains. 200 ''; 201 }; 202 203 config = mkOption { 204 type = with types; nullOr lines; 205 default = defaultConfig; 206 description = lib.mdDoc '' 207 Server configuration, see 208 [https://maddy.email](https://maddy.email) for 209 more information. The default configuration of this module will setup 210 minimal maddy instance for mail transfer without TLS encryption. 211 212 ::: {.note} 213 This should not be used in a production environment. 214 ::: 215 ''; 216 }; 217 218 openFirewall = mkOption { 219 type = types.bool; 220 default = false; 221 description = lib.mdDoc '' 222 Open the configured incoming and outgoing mail server ports. 223 ''; 224 }; 225 226 }; 227 }; 228 229 config = mkIf cfg.enable { 230 231 systemd = { 232 packages = [ pkgs.maddy ]; 233 services.maddy = { 234 serviceConfig = { 235 User = cfg.user; 236 Group = cfg.group; 237 StateDirectory = [ "maddy" ]; 238 }; 239 restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ]; 240 wantedBy = [ "multi-user.target" ]; 241 }; 242 }; 243 244 environment.etc."maddy/maddy.conf" = { 245 text = '' 246 $(hostname) = ${cfg.hostname} 247 $(primary_domain) = ${cfg.primaryDomain} 248 $(local_domains) = ${toString cfg.localDomains} 249 hostname ${cfg.hostname} 250 ${cfg.config} 251 ''; 252 }; 253 254 users.users = optionalAttrs (cfg.user == name) { 255 ${name} = { 256 isSystemUser = true; 257 group = cfg.group; 258 description = "Maddy mail transfer agent user"; 259 }; 260 }; 261 262 users.groups = optionalAttrs (cfg.group == name) { 263 ${cfg.group} = { }; 264 }; 265 266 networking.firewall = mkIf cfg.openFirewall { 267 allowedTCPPorts = [ 25 143 587 ]; 268 }; 269 270 environment.systemPackages = [ 271 pkgs.maddy 272 ]; 273 }; 274}