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