1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.postfix; 8 user = cfg.user; 9 group = cfg.group; 10 setgidGroup = cfg.setgidGroup; 11 12 haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != "" || cfg.extraAliases != ""; 13 haveTransport = cfg.transport != ""; 14 haveVirtual = cfg.virtual != ""; 15 16 clientAccess = 17 if (cfg.dnsBlacklistOverrides != "") 18 then [ "check_client_access hash:/etc/postfix/client_access" ] 19 else []; 20 21 dnsBl = 22 if (cfg.dnsBlacklists != []) 23 then [ (concatStringsSep ", " (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists)) ] 24 else []; 25 26 clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl); 27 28 mainCf = 29 '' 30 compatibility_level = 2 31 32 mail_owner = ${user} 33 default_privs = nobody 34 35 # NixOS specific locations 36 data_directory = /var/lib/postfix/data 37 queue_directory = /var/lib/postfix/queue 38 39 # Default location of everything in package 40 meta_directory = ${pkgs.postfix}/etc/postfix 41 command_directory = ${pkgs.postfix}/bin 42 sample_directory = /etc/postfix 43 newaliases_path = ${pkgs.postfix}/bin/newaliases 44 mailq_path = ${pkgs.postfix}/bin/mailq 45 readme_directory = no 46 sendmail_path = ${pkgs.postfix}/bin/sendmail 47 daemon_directory = ${pkgs.postfix}/libexec/postfix 48 manpage_directory = ${pkgs.postfix}/share/man 49 html_directory = ${pkgs.postfix}/share/postfix/doc/html 50 shlib_directory = no 51 52 '' 53 + optionalString config.networking.enableIPv6 '' 54 inet_protocols = all 55 '' 56 + (if cfg.networks != null then 57 '' 58 mynetworks = ${concatStringsSep ", " cfg.networks} 59 '' 60 else if cfg.networksStyle != "" then 61 '' 62 mynetworks_style = ${cfg.networksStyle} 63 '' 64 else 65 "") 66 + optionalString (cfg.hostname != "") '' 67 myhostname = ${cfg.hostname} 68 '' 69 + optionalString (cfg.domain != "") '' 70 mydomain = ${cfg.domain} 71 '' 72 + optionalString (cfg.origin != "") '' 73 myorigin = ${cfg.origin} 74 '' 75 + optionalString (cfg.destination != null) '' 76 mydestination = ${concatStringsSep ", " cfg.destination} 77 '' 78 + optionalString (cfg.relayDomains != null) '' 79 relay_domains = ${concatStringsSep ", " cfg.relayDomains} 80 '' 81 + '' 82 local_recipient_maps = 83 84 relayhost = ${if cfg.lookupMX || cfg.relayHost == "" then 85 cfg.relayHost 86 else 87 "[" + cfg.relayHost + "]"} 88 89 mail_spool_directory = /var/spool/mail/ 90 91 setgid_group = ${setgidGroup} 92 '' 93 + optionalString (cfg.sslCert != "") '' 94 95 smtp_tls_CAfile = ${cfg.sslCACert} 96 smtp_tls_cert_file = ${cfg.sslCert} 97 smtp_tls_key_file = ${cfg.sslKey} 98 99 smtp_use_tls = yes 100 101 smtpd_tls_CAfile = ${cfg.sslCACert} 102 smtpd_tls_cert_file = ${cfg.sslCert} 103 smtpd_tls_key_file = ${cfg.sslKey} 104 105 smtpd_use_tls = yes 106 '' 107 + optionalString (cfg.recipientDelimiter != "") '' 108 recipient_delimiter = ${cfg.recipientDelimiter} 109 '' 110 + optionalString haveAliases '' 111 alias_maps = hash:/etc/postfix/aliases 112 '' 113 + optionalString haveTransport '' 114 transport_maps = hash:/etc/postfix/transport 115 '' 116 + optionalString haveVirtual '' 117 virtual_alias_maps = hash:/etc/postfix/virtual 118 '' 119 + optionalString (cfg.dnsBlacklists != []) '' 120 smtpd_client_restrictions = ${clientRestrictions} 121 '' 122 + cfg.extraConfig; 123 124 masterCf = '' 125 # ========================================================================== 126 # service type private unpriv chroot wakeup maxproc command + args 127 # (yes) (yes) (no) (never) (100) 128 # ========================================================================== 129 smtp inet n - n - - smtpd 130 #submission inet n - n - - smtpd 131 # -o smtpd_tls_security_level=encrypt 132 # -o smtpd_sasl_auth_enable=yes 133 # -o smtpd_client_restrictions=permit_sasl_authenticated,reject 134 # -o milter_macro_daemon_name=ORIGINATING 135 pickup unix n - n 60 1 pickup 136 cleanup unix n - n - 0 cleanup 137 qmgr unix n - n 300 1 qmgr 138 tlsmgr unix - - n 1000? 1 tlsmgr 139 rewrite unix - - n - - trivial-rewrite 140 bounce unix - - n - 0 bounce 141 defer unix - - n - 0 bounce 142 trace unix - - n - 0 bounce 143 verify unix - - n - 1 verify 144 flush unix n - n 1000? 0 flush 145 proxymap unix - - n - - proxymap 146 proxywrite unix - - n - 1 proxymap 147 '' 148 + optionalString cfg.enableSmtp '' 149 smtp unix - - n - - smtp 150 relay unix - - n - - smtp 151 -o smtp_fallback_relay= 152 # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 153 '' 154 + '' 155 showq unix n - n - - showq 156 error unix - - n - - error 157 retry unix - - n - - error 158 discard unix - - n - - discard 159 local unix - n n - - local 160 virtual unix - n n - - virtual 161 lmtp unix - - n - - lmtp 162 anvil unix - - n - 1 anvil 163 scache unix - - n - 1 scache 164 ${cfg.extraMasterConf} 165 ''; 166 167 aliases = 168 optionalString (cfg.postmasterAlias != "") '' 169 postmaster: ${cfg.postmasterAlias} 170 '' 171 + optionalString (cfg.rootAlias != "") '' 172 root: ${cfg.rootAlias} 173 '' 174 + cfg.extraAliases 175 ; 176 177 aliasesFile = pkgs.writeText "postfix-aliases" aliases; 178 virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual; 179 checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides; 180 mainCfFile = pkgs.writeText "postfix-main.cf" mainCf; 181 masterCfFile = pkgs.writeText "postfix-master.cf" masterCf; 182 transportFile = pkgs.writeText "postfix-transport" cfg.transport; 183 184in 185 186{ 187 188 ###### interface 189 190 options = { 191 192 services.postfix = { 193 194 enable = mkOption { 195 type = types.bool; 196 default = false; 197 description = "Whether to run the Postfix mail server."; 198 }; 199 200 enableSmtp = mkOption { 201 default = true; 202 description = "Whether to enable smtp in master.cf."; 203 }; 204 205 setSendmail = mkOption { 206 type = types.bool; 207 default = true; 208 description = "Whether to set the system sendmail to postfix's."; 209 }; 210 211 user = mkOption { 212 type = types.str; 213 default = "postfix"; 214 description = "What to call the Postfix user (must be used only for postfix)."; 215 }; 216 217 group = mkOption { 218 type = types.str; 219 default = "postfix"; 220 description = "What to call the Postfix group (must be used only for postfix)."; 221 }; 222 223 setgidGroup = mkOption { 224 type = types.str; 225 default = "postdrop"; 226 description = " 227 How to call postfix setgid group (for postdrop). Should 228 be uniquely used group. 229 "; 230 }; 231 232 networks = mkOption { 233 type = types.nullOr (types.listOf types.str); 234 default = null; 235 example = ["192.168.0.1/24"]; 236 description = " 237 Net masks for trusted - allowed to relay mail to third parties - 238 hosts. Leave empty to use mynetworks_style configuration or use 239 default (localhost-only). 240 "; 241 }; 242 243 networksStyle = mkOption { 244 type = types.str; 245 default = ""; 246 description = " 247 Name of standard way of trusted network specification to use, 248 leave blank if you specify it explicitly or if you want to use 249 default (localhost-only). 250 "; 251 }; 252 253 hostname = mkOption { 254 type = types.str; 255 default = ""; 256 description =" 257 Hostname to use. Leave blank to use just the hostname of machine. 258 It should be FQDN. 259 "; 260 }; 261 262 domain = mkOption { 263 type = types.str; 264 default = ""; 265 description =" 266 Domain to use. Leave blank to use hostname minus first component. 267 "; 268 }; 269 270 origin = mkOption { 271 type = types.str; 272 default = ""; 273 description =" 274 Origin to use in outgoing e-mail. Leave blank to use hostname. 275 "; 276 }; 277 278 destination = mkOption { 279 type = types.nullOr (types.listOf types.str); 280 default = null; 281 example = ["localhost"]; 282 description = " 283 Full (!) list of domains we deliver locally. Leave blank for 284 acceptable Postfix default. 285 "; 286 }; 287 288 relayDomains = mkOption { 289 type = types.nullOr (types.listOf types.str); 290 default = null; 291 example = ["localdomain"]; 292 description = " 293 List of domains we agree to relay to. Default is empty. 294 "; 295 }; 296 297 relayHost = mkOption { 298 type = types.str; 299 default = ""; 300 description = " 301 Mail relay for outbound mail. 302 "; 303 }; 304 305 lookupMX = mkOption { 306 type = types.bool; 307 default = false; 308 description = " 309 Whether relay specified is just domain whose MX must be used. 310 "; 311 }; 312 313 postmasterAlias = mkOption { 314 type = types.str; 315 default = "root"; 316 description = "Who should receive postmaster e-mail."; 317 }; 318 319 rootAlias = mkOption { 320 type = types.str; 321 default = ""; 322 description = " 323 Who should receive root e-mail. Blank for no redirection. 324 "; 325 }; 326 327 extraAliases = mkOption { 328 type = types.lines; 329 default = ""; 330 description = " 331 Additional entries to put verbatim into aliases file, cf. man-page aliases(8). 332 "; 333 }; 334 335 extraConfig = mkOption { 336 type = types.lines; 337 default = ""; 338 description = " 339 Extra lines to be added verbatim to the main.cf configuration file. 340 "; 341 }; 342 343 sslCert = mkOption { 344 type = types.str; 345 default = ""; 346 description = "SSL certificate to use."; 347 }; 348 349 sslCACert = mkOption { 350 type = types.str; 351 default = ""; 352 description = "SSL certificate of CA."; 353 }; 354 355 sslKey = mkOption { 356 type = types.str; 357 default = ""; 358 description = "SSL key to use."; 359 }; 360 361 recipientDelimiter = mkOption { 362 type = types.str; 363 default = ""; 364 example = "+"; 365 description = " 366 Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test 367 "; 368 }; 369 370 virtual = mkOption { 371 type = types.lines; 372 default = ""; 373 description = " 374 Entries for the virtual alias map, cf. man-page virtual(8). 375 "; 376 }; 377 378 transport = mkOption { 379 default = ""; 380 description = " 381 Entries for the transport map, cf. man-page transport(8). 382 "; 383 }; 384 385 dnsBlacklists = mkOption { 386 default = []; 387 type = with types; listOf string; 388 description = "dns blacklist servers to use with smtpd_client_restrictions"; 389 }; 390 391 dnsBlacklistOverrides = mkOption { 392 default = ""; 393 description = "contents of check_client_access for overriding dnsBlacklists"; 394 }; 395 396 extraMasterConf = mkOption { 397 type = types.lines; 398 default = ""; 399 example = "submission inet n - n - - smtpd"; 400 description = "Extra lines to append to the generated master.cf file."; 401 }; 402 403 aliasFiles = mkOption { 404 type = types.attrsOf types.path; 405 default = {}; 406 description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf."; 407 }; 408 409 mapFiles = mkOption { 410 type = types.attrsOf types.path; 411 default = {}; 412 description = "Maps to be compiled and placed into /var/lib/postfix/conf."; 413 }; 414 415 }; 416 417 }; 418 419 420 ###### implementation 421 422 config = mkIf config.services.postfix.enable (mkMerge [ 423 { 424 425 environment = { 426 etc = singleton 427 { source = "/var/lib/postfix/conf"; 428 target = "postfix"; 429 }; 430 431 # This makes comfortable for root to run 'postqueue' for example. 432 systemPackages = [ pkgs.postfix ]; 433 }; 434 435 services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail { 436 program = "sendmail"; 437 source = "${pkgs.postfix}/bin/sendmail"; 438 group = setgidGroup; 439 setuid = false; 440 setgid = true; 441 }; 442 443 users.extraUsers = optional (user == "postfix") 444 { name = "postfix"; 445 description = "Postfix mail server user"; 446 uid = config.ids.uids.postfix; 447 group = group; 448 }; 449 450 users.extraGroups = 451 optional (group == "postfix") 452 { name = group; 453 gid = config.ids.gids.postfix; 454 } 455 ++ optional (setgidGroup == "postdrop") 456 { name = setgidGroup; 457 gid = config.ids.gids.postdrop; 458 }; 459 460 systemd.services.postfix = 461 { description = "Postfix mail server"; 462 463 wantedBy = [ "multi-user.target" ]; 464 after = [ "network.target" ]; 465 path = [ pkgs.postfix ]; 466 467 serviceConfig = { 468 Type = "forking"; 469 Restart = "always"; 470 PIDFile = "/var/lib/postfix/queue/pid/master.pid"; 471 ExecStart = "${pkgs.postfix}/bin/postfix start"; 472 ExecStop = "${pkgs.postfix}/bin/postfix stop"; 473 ExecReload = "${pkgs.postfix}/bin/postfix reload"; 474 }; 475 476 preStart = '' 477 # Backwards compatibility 478 if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then 479 mkdir -p /var/lib 480 mv /var/postfix /var/lib/postfix 481 fi 482 483 # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script 484 mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop} 485 chmod 0755 /var/lib/postfix 486 chown root:root /var/lib/postfix 487 488 rm -rf /var/lib/postfix/conf 489 mkdir -p /var/lib/postfix/conf 490 chmod 0755 /var/lib/postfix/conf 491 ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files 492 ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf 493 ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf 494 495 ${concatStringsSep "\n" (mapAttrsToList (to: from: '' 496 ln -sf ${from} /var/lib/postfix/conf/${to} 497 ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to} 498 '') cfg.aliasFiles)} 499 ${concatStringsSep "\n" (mapAttrsToList (to: from: '' 500 ln -sf ${from} /var/lib/postfix/conf/${to} 501 ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to} 502 '') cfg.mapFiles)} 503 504 mkdir -p /var/spool/mail 505 chown root:root /var/spool/mail 506 chmod a+rwxt /var/spool/mail 507 ln -sf /var/spool/mail /var/ 508 509 #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them 510 ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf 511 ''; 512 }; 513 } 514 515 (mkIf haveAliases { 516 services.postfix.aliasFiles."aliases" = aliasesFile; 517 }) 518 (mkIf haveTransport { 519 services.postfix.mapFiles."transport" = transportFile; 520 }) 521 (mkIf haveVirtual { 522 services.postfix.mapFiles."virtual" = virtualFile; 523 }) 524 (mkIf (cfg.dnsBlacklists != []) { 525 services.postfix.mapFiles."client_access" = checkClientAccessFile; 526 }) 527 ]); 528 529}