1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7{
8
9 options = {
10
11 services.nullmailer = {
12 enable = lib.mkOption {
13 type = lib.types.bool;
14 default = false;
15 description = "Whether to enable nullmailer daemon.";
16 };
17
18 user = lib.mkOption {
19 type = lib.types.str;
20 default = "nullmailer";
21 description = ''
22 User to use to run nullmailer-send.
23 '';
24 };
25
26 group = lib.mkOption {
27 type = lib.types.str;
28 default = "nullmailer";
29 description = ''
30 Group to use to run nullmailer-send.
31 '';
32 };
33
34 setSendmail = lib.mkOption {
35 type = lib.types.bool;
36 default = true;
37 description = "Whether to set the system sendmail to nullmailer's.";
38 };
39
40 remotesFile = lib.mkOption {
41 type = lib.types.nullOr lib.types.str;
42 default = null;
43 description = ''
44 Path to the `remotes` control file. This file contains a
45 list of remote servers to which to send each message.
46
47 See `man 8 nullmailer-send` for syntax and available
48 options.
49 '';
50 };
51
52 config = {
53 adminaddr = lib.mkOption {
54 type = lib.types.nullOr lib.types.str;
55 default = null;
56 description = ''
57 If set, all recipients to users at either "localhost" (the literal string)
58 or the canonical host name (from the me control attribute) are remapped to this address.
59 This is provided to allow local daemons to be able to send email to
60 "somebody@localhost" and have it go somewhere sensible instead of being bounced
61 by your relay host. To send to multiple addresses,
62 put them all on one line separated by a comma.
63 '';
64 };
65
66 allmailfrom = lib.mkOption {
67 type = lib.types.nullOr lib.types.str;
68 default = null;
69 description = ''
70 If set, content will override the envelope sender on all messages.
71 '';
72 };
73
74 defaultdomain = lib.mkOption {
75 type = lib.types.nullOr lib.types.str;
76 default = null;
77 description = ''
78 The content of this attribute is appended to any host name that
79 does not contain a period (except localhost), including defaulthost
80 and idhost. Defaults to the value of the me attribute, if it exists,
81 otherwise the literal name defauldomain.
82 '';
83 };
84
85 defaulthost = lib.mkOption {
86 type = lib.types.nullOr lib.types.str;
87 default = null;
88 description = ''
89 The content of this attribute is appended to any address that
90 is missing a host name. Defaults to the value of the me control
91 attribute, if it exists, otherwise the literal name defaulthost.
92 '';
93 };
94
95 doublebounceto = lib.mkOption {
96 type = lib.types.nullOr lib.types.str;
97 default = null;
98 description = ''
99 If the original sender was empty (the original message was a
100 delivery status or disposition notification), the double bounce
101 is sent to the address in this attribute.
102 '';
103 };
104
105 helohost = lib.mkOption {
106 type = lib.types.nullOr lib.types.str;
107 default = null;
108 description = ''
109 Sets the environment variable $HELOHOST which is used by the
110 SMTP protocol module to set the parameter given to the HELO command.
111 Defaults to the value of the me configuration attribute.
112 '';
113 };
114
115 idhost = lib.mkOption {
116 type = lib.types.nullOr lib.types.str;
117 default = null;
118 description = ''
119 The content of this attribute is used when building the message-id
120 string for the message. Defaults to the canonicalized value of defaulthost.
121 '';
122 };
123
124 maxpause = lib.mkOption {
125 type =
126 with lib.types;
127 nullOr (oneOf [
128 str
129 int
130 ]);
131 default = null;
132 description = ''
133 The maximum time to pause between successive queue runs, in seconds.
134 Defaults to 24 hours (86400).
135 '';
136 };
137
138 me = lib.mkOption {
139 type = lib.types.nullOr lib.types.str;
140 default = null;
141 description = ''
142 The fully-qualifiled host name of the computer running nullmailer.
143 Defaults to the literal name me.
144 '';
145 };
146
147 pausetime = lib.mkOption {
148 type =
149 with lib.types;
150 nullOr (oneOf [
151 str
152 int
153 ]);
154 default = null;
155 description = ''
156 The minimum time to pause between successive queue runs when there
157 are messages in the queue, in seconds. Defaults to 1 minute (60).
158 Each time this timeout is reached, the timeout is doubled to a
159 maximum of maxpause. After new messages are injected, the timeout
160 is reset. If this is set to 0, nullmailer-send will exit
161 immediately after going through the queue once (one-shot mode).
162 '';
163 };
164
165 remotes = lib.mkOption {
166 type = lib.types.nullOr lib.types.str;
167 default = null;
168 description = ''
169 A list of remote servers to which to send each message. Each line
170 contains a remote host name or address followed by an optional
171 protocol string, separated by white space.
172
173 See `man 8 nullmailer-send` for syntax and available
174 options.
175
176 WARNING: This is stored world-readable in the nix store. If you need
177 to specify any secret credentials here, consider using the
178 `remotesFile` option instead.
179 '';
180 };
181
182 sendtimeout = lib.mkOption {
183 type =
184 with lib.types;
185 nullOr (oneOf [
186 str
187 int
188 ]);
189 default = null;
190 description = ''
191 The time to wait for a remote module listed above to complete sending
192 a message before killing it and trying again, in seconds.
193 Defaults to 1 hour (3600). If this is set to 0, nullmailer-send
194 will wait forever for messages to complete sending.
195 '';
196 };
197 };
198 };
199 };
200
201 config =
202 let
203 cfg = config.services.nullmailer;
204 in
205 lib.mkIf cfg.enable {
206
207 assertions = [
208 {
209 assertion = cfg.config.remotes == null || cfg.remotesFile == null;
210 message = "Only one of `remotesFile` or `config.remotes` may be used at a time.";
211 }
212 ];
213
214 environment = {
215 systemPackages = [ pkgs.nullmailer ];
216 etc =
217 let
218 validAttrs = lib.mapAttrs (_: toString) (lib.filterAttrs (_: value: value != null) cfg.config);
219 in
220 (lib.foldl' (as: name: as // { "nullmailer/${name}".text = validAttrs.${name}; }) { } (
221 lib.attrNames validAttrs
222 ))
223 // lib.optionalAttrs (cfg.remotesFile != null) { "nullmailer/remotes".source = cfg.remotesFile; };
224 };
225
226 users = {
227 users.${cfg.user} = {
228 description = "Nullmailer relay-only mta user";
229 inherit (cfg) group;
230 isSystemUser = true;
231 };
232
233 groups.${cfg.group} = { };
234 };
235
236 systemd.tmpfiles.rules = [
237 "d /var/spool/nullmailer - ${cfg.user} ${cfg.group} - -"
238 "d /var/spool/nullmailer/failed 770 ${cfg.user} ${cfg.group} - -"
239 "d /var/spool/nullmailer/queue 770 ${cfg.user} ${cfg.group} - -"
240 "d /var/spool/nullmailer/tmp 770 ${cfg.user} ${cfg.group} - -"
241 ];
242
243 systemd.services.nullmailer = {
244 description = "nullmailer";
245 wantedBy = [ "multi-user.target" ];
246 after = [ "network.target" ];
247
248 preStart = ''
249 rm -f /var/spool/nullmailer/trigger && mkfifo -m 660 /var/spool/nullmailer/trigger
250 '';
251
252 serviceConfig = {
253 User = cfg.user;
254 Group = cfg.group;
255 ExecStart = "${pkgs.nullmailer}/bin/nullmailer-send";
256 Restart = "always";
257 };
258 };
259
260 services.mail.sendmailSetuidWrapper = lib.mkIf cfg.setSendmail {
261 program = "sendmail";
262 source = "${pkgs.nullmailer}/bin/sendmail";
263 owner = cfg.user;
264 inherit (cfg) group;
265 setuid = true;
266 setgid = true;
267 };
268 };
269}