1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 /* minimal secure setup:
8
9 enable = true;
10 forceLocalLoginsSSL = true;
11 forceLocalDataSSL = true;
12 userlistDeny = false;
13 localUsers = true;
14 userlist = ["non-root-user" "other-non-root-user"];
15 rsaCertFile = "/var/vsftpd/vsftpd.pem";
16
17 */
18
19 cfg = config.services.vsftpd;
20
21 inherit (pkgs) vsftpd;
22
23 yesNoOption = nixosName: vsftpdName: default: description: {
24 cfgText = "${vsftpdName}=${if getAttr nixosName cfg then "YES" else "NO"}";
25
26 nixosOption = {
27 type = types.bool;
28 name = nixosName;
29 value = mkOption {
30 inherit description default;
31 type = types.bool;
32 };
33 };
34 };
35
36 optionDescription = [
37 (yesNoOption "anonymousUser" "anonymous_enable" false ''
38 Whether to enable the anonymous FTP user.
39 '')
40 (yesNoOption "anonymousUserNoPassword" "no_anon_password" false ''
41 Whether to disable the password for the anonymous FTP user.
42 '')
43 (yesNoOption "localUsers" "local_enable" false ''
44 Whether to enable FTP for local users.
45 '')
46 (yesNoOption "writeEnable" "write_enable" false ''
47 Whether any write activity is permitted to users.
48 '')
49 (yesNoOption "anonymousUploadEnable" "anon_upload_enable" false ''
50 Whether any uploads are permitted to anonymous users.
51 '')
52 (yesNoOption "anonymousMkdirEnable" "anon_mkdir_write_enable" false ''
53 Whether any uploads are permitted to anonymous users.
54 '')
55 (yesNoOption "chrootlocalUser" "chroot_local_user" false ''
56 Whether local users are confined to their home directory.
57 '')
58 (yesNoOption "userlistEnable" "userlist_enable" false ''
59 Whether users are included.
60 '')
61 (yesNoOption "userlistDeny" "userlist_deny" false ''
62 Specifies whether <option>userlistFile</option> is a list of user
63 names to allow or deny access.
64 The default <literal>false</literal> means whitelist/allow.
65 '')
66 (yesNoOption "forceLocalLoginsSSL" "force_local_logins_ssl" false ''
67 Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
68 must use a secure SSL connection to send a password.
69 '')
70 (yesNoOption "forceLocalDataSSL" "force_local_data_ssl" false ''
71 Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
72 must use a secure SSL connection for sending/receiving data on data connection.
73 '')
74 (yesNoOption "portPromiscuous" "port_promiscuous" false ''
75 Set to YES if you want to disable the PORT security check that ensures that
76 outgoing data connections can only connect to the client. Only enable if you
77 know what you are doing!
78 '')
79 (yesNoOption "ssl_tlsv1" "ssl_tlsv1" true '' '')
80 (yesNoOption "ssl_sslv2" "ssl_sslv2" false '' '')
81 (yesNoOption "ssl_sslv3" "ssl_sslv3" false '' '')
82 ];
83
84 configFile = pkgs.writeText "vsftpd.conf"
85 ''
86 ${concatMapStrings (x: "${x.cfgText}\n") optionDescription}
87 ${optionalString (cfg.rsaCertFile != null) ''
88 ssl_enable=YES
89 rsa_cert_file=${cfg.rsaCertFile}
90 ''}
91 ${optionalString (cfg.rsaKeyFile != null) ''
92 rsa_private_key_file=${cfg.rsaKeyFile}
93 ''}
94 ${optionalString (cfg.userlistFile != null) ''
95 userlist_file=${cfg.userlistFile}
96 ''}
97 background=YES
98 listen=YES
99 nopriv_user=vsftpd
100 secure_chroot_dir=/var/empty
101 syslog_enable=YES
102 ${optionalString (pkgs.stdenv.system == "x86_64-linux") ''
103 seccomp_sandbox=NO
104 ''}
105 anon_umask=${cfg.anonymousUmask}
106 ${optionalString cfg.anonymousUser ''
107 anon_root=${cfg.anonymousUserHome}
108 ''}
109 ${cfg.extraConfig}
110 '';
111
112in
113
114{
115
116 ###### interface
117
118 options = {
119
120 services.vsftpd = {
121
122 enable = mkOption {
123 default = false;
124 description = "Whether to enable the vsftpd FTP server.";
125 };
126
127 userlist = mkOption {
128 default = [];
129 description = "See <option>userlistFile</option>.";
130 };
131
132 userlistFile = mkOption {
133 type = types.path;
134 default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
135 defaultText = "pkgs.writeText \"userlist\" (concatMapStrings (x: \"\${x}\n\") cfg.userlist)";
136 description = ''
137 Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
138 is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
139
140 The default is a file containing the users from <option>userlist</option>.
141
142 If explicitely set to null userlist_file will not be set in vsftpd's config file.
143 '';
144 };
145
146 anonymousUserHome = mkOption {
147 type = types.path;
148 default = "/home/ftp/";
149 description = ''
150 Directory to consider the HOME of the anonymous user.
151 '';
152 };
153
154 rsaCertFile = mkOption {
155 type = types.nullOr types.path;
156 default = null;
157 description = "RSA certificate file.";
158 };
159
160 rsaKeyFile = mkOption {
161 type = types.nullOr types.path;
162 default = null;
163 description = "RSA private key file.";
164 };
165
166 anonymousUmask = mkOption {
167 type = types.string;
168 default = "077";
169 example = "002";
170 description = "Anonymous write umask.";
171 };
172
173 extraConfig = mkOption {
174 type = types.lines;
175 default = "";
176 example = "ftpd_banner=Hello";
177 description = "Extra configuration to add at the bottom of the generated configuration file.";
178 };
179
180 } // (listToAttrs (catAttrs "nixosOption" optionDescription));
181
182 };
183
184
185 ###### implementation
186
187 config = mkIf cfg.enable {
188
189 assertions = singleton
190 { assertion =
191 (cfg.forceLocalLoginsSSL -> cfg.rsaCertFile != null)
192 && (cfg.forceLocalDataSSL -> cfg.rsaCertFile != null);
193 message = "vsftpd: If forceLocalLoginsSSL or forceLocalDataSSL is true then a rsaCertFile must be provided!";
194 };
195
196 users.extraUsers =
197 [ { name = "vsftpd";
198 uid = config.ids.uids.vsftpd;
199 description = "VSFTPD user";
200 home = "/homeless-shelter";
201 }
202 ] ++ optional cfg.anonymousUser
203 { name = "ftp";
204 uid = config.ids.uids.ftp;
205 group = "ftp";
206 description = "Anonymous FTP user";
207 home = cfg.anonymousUserHome;
208 };
209
210 users.extraGroups.ftp.gid = config.ids.gids.ftp;
211
212 # If you really have to access root via FTP use mkOverride or userlistDeny
213 # = false and whitelist root
214 services.vsftpd.userlist = if cfg.userlistDeny then ["root"] else [];
215
216 systemd.services.vsftpd =
217 { description = "Vsftpd Server";
218
219 wantedBy = [ "multi-user.target" ];
220
221 preStart =
222 optionalString cfg.anonymousUser
223 ''
224 mkdir -p -m 555 ${cfg.anonymousUserHome}
225 chown -R ftp:ftp ${cfg.anonymousUserHome}
226 '';
227
228 serviceConfig.ExecStart = "@${vsftpd}/sbin/vsftpd vsftpd ${configFile}";
229 serviceConfig.Restart = "always";
230 serviceConfig.Type = "forking";
231 };
232
233 };
234
235}