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