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}