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