at 25.11-pre 7.2 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9let 10 11 cfg = config.services.postgrey; 12 13 natural = with types; addCheck int (x: x >= 0); 14 natural' = with types; addCheck int (x: x > 0); 15 16 socket = 17 with types; 18 addCheck (either (submodule unixSocket) (submodule inetSocket)) (x: x ? path || x ? port); 19 20 inetSocket = with types; { 21 options = { 22 addr = mkOption { 23 type = nullOr str; 24 default = null; 25 example = "127.0.0.1"; 26 description = "The address to bind to. Localhost if null"; 27 }; 28 port = mkOption { 29 type = natural'; 30 default = 10030; 31 description = "Tcp port to bind to"; 32 }; 33 }; 34 }; 35 36 unixSocket = with types; { 37 options = { 38 path = mkOption { 39 type = path; 40 default = "/run/postgrey.sock"; 41 description = "Path of the unix socket"; 42 }; 43 44 mode = mkOption { 45 type = str; 46 default = "0777"; 47 description = "Mode of the unix socket"; 48 }; 49 }; 50 }; 51 52in 53{ 54 imports = [ 55 (mkMergedOptionModule 56 [ 57 [ 58 "services" 59 "postgrey" 60 "inetAddr" 61 ] 62 [ 63 "services" 64 "postgrey" 65 "inetPort" 66 ] 67 ] 68 [ "services" "postgrey" "socket" ] 69 ( 70 config: 71 let 72 value = p: getAttrFromPath p config; 73 inetAddr = [ 74 "services" 75 "postgrey" 76 "inetAddr" 77 ]; 78 inetPort = [ 79 "services" 80 "postgrey" 81 "inetPort" 82 ]; 83 in 84 if value inetAddr == null then 85 { path = "/run/postgrey.sock"; } 86 else 87 { 88 addr = value inetAddr; 89 port = value inetPort; 90 } 91 ) 92 ) 93 ]; 94 95 options = { 96 services.postgrey = with types; { 97 enable = mkOption { 98 type = bool; 99 default = false; 100 description = "Whether to run the Postgrey daemon"; 101 }; 102 socket = mkOption { 103 type = socket; 104 default = { 105 path = "/run/postgrey.sock"; 106 mode = "0777"; 107 }; 108 example = { 109 addr = "127.0.0.1"; 110 port = 10030; 111 }; 112 description = "Socket to bind to"; 113 }; 114 greylistText = mkOption { 115 type = str; 116 default = "Greylisted for %%s seconds"; 117 description = "Response status text for greylisted messages; use %%s for seconds left until greylisting is over and %%r for mail domain of recipient"; 118 }; 119 greylistAction = mkOption { 120 type = str; 121 default = "DEFER_IF_PERMIT"; 122 description = "Response status for greylisted messages (see {manpage}`access(5)`)"; 123 }; 124 greylistHeader = mkOption { 125 type = str; 126 default = "X-Greylist: delayed %%t seconds by postgrey-%%v at %%h; %%d"; 127 description = "Prepend header to greylisted mails; use %%t for seconds delayed due to greylisting, %%v for the version of postgrey, %%d for the date, and %%h for the host"; 128 }; 129 delay = mkOption { 130 type = natural; 131 default = 300; 132 description = "Greylist for N seconds"; 133 }; 134 maxAge = mkOption { 135 type = natural; 136 default = 35; 137 description = "Delete entries from whitelist if they haven't been seen for N days"; 138 }; 139 retryWindow = mkOption { 140 type = either str natural; 141 default = 2; 142 example = "12h"; 143 description = "Allow N days for the first retry. Use string with appended 'h' to specify time in hours"; 144 }; 145 lookupBySubnet = mkOption { 146 type = bool; 147 default = true; 148 description = "Strip the last N bits from IP addresses, determined by IPv4CIDR and IPv6CIDR"; 149 }; 150 IPv4CIDR = mkOption { 151 type = natural; 152 default = 24; 153 description = "Strip N bits from IPv4 addresses if lookupBySubnet is true"; 154 }; 155 IPv6CIDR = mkOption { 156 type = natural; 157 default = 64; 158 description = "Strip N bits from IPv6 addresses if lookupBySubnet is true"; 159 }; 160 privacy = mkOption { 161 type = bool; 162 default = true; 163 description = "Store data using one-way hash functions (SHA1)"; 164 }; 165 autoWhitelist = mkOption { 166 type = nullOr natural'; 167 default = 5; 168 description = "Whitelist clients after successful delivery of N messages"; 169 }; 170 whitelistClients = mkOption { 171 type = listOf path; 172 default = [ ]; 173 description = "Client address whitelist files (see {manpage}`postgrey(8)`)"; 174 }; 175 whitelistRecipients = mkOption { 176 type = listOf path; 177 default = [ ]; 178 description = "Recipient address whitelist files (see {manpage}`postgrey(8)`)"; 179 }; 180 }; 181 }; 182 183 config = mkIf cfg.enable { 184 185 environment.systemPackages = [ pkgs.postgrey ]; 186 187 users = { 188 users = { 189 postgrey = { 190 description = "Postgrey Daemon"; 191 uid = config.ids.uids.postgrey; 192 group = "postgrey"; 193 }; 194 }; 195 groups = { 196 postgrey = { 197 gid = config.ids.gids.postgrey; 198 }; 199 }; 200 }; 201 202 systemd.services.postgrey = 203 let 204 bind-flag = 205 if cfg.socket ? path then 206 "--unix=${cfg.socket.path} --socketmode=${cfg.socket.mode}" 207 else 208 ''--inet=${ 209 optionalString (cfg.socket.addr != null) (cfg.socket.addr + ":") 210 }${toString cfg.socket.port}''; 211 in 212 { 213 description = "Postfix Greylisting Service"; 214 wantedBy = [ "multi-user.target" ]; 215 before = [ "postfix.service" ]; 216 preStart = '' 217 mkdir -p /var/postgrey 218 chown postgrey:postgrey /var/postgrey 219 chmod 0770 /var/postgrey 220 ''; 221 serviceConfig = { 222 Type = "simple"; 223 ExecStart = '' 224 ${pkgs.postgrey}/bin/postgrey \ 225 ${bind-flag} \ 226 --group=postgrey --user=postgrey \ 227 --dbdir=/var/postgrey \ 228 --delay=${toString cfg.delay} \ 229 --max-age=${toString cfg.maxAge} \ 230 --retry-window=${toString cfg.retryWindow} \ 231 ${if cfg.lookupBySubnet then "--lookup-by-subnet" else "--lookup-by-host"} \ 232 --ipv4cidr=${toString cfg.IPv4CIDR} --ipv6cidr=${toString cfg.IPv6CIDR} \ 233 ${optionalString cfg.privacy "--privacy"} \ 234 --auto-whitelist-clients=${ 235 toString (if cfg.autoWhitelist == null then 0 else cfg.autoWhitelist) 236 } \ 237 --greylist-action=${cfg.greylistAction} \ 238 --greylist-text="${cfg.greylistText}" \ 239 --x-greylist-header="${cfg.greylistHeader}" \ 240 ${concatMapStringsSep " " (x: "--whitelist-clients=" + x) cfg.whitelistClients} \ 241 ${concatMapStringsSep " " (x: "--whitelist-recipients=" + x) cfg.whitelistRecipients} 242 ''; 243 Restart = "always"; 244 RestartSec = 5; 245 TimeoutSec = 10; 246 }; 247 }; 248 249 }; 250 251}