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.userlistFile != null) ''
89 userlist_file=${cfg.userlistFile}
90 ''}
91 background=YES
92 listen=YES
93 nopriv_user=vsftpd
94 secure_chroot_dir=/var/empty
95 syslog_enable=YES
96 ${optionalString (pkgs.stdenv.system == "x86_64-linux") ''
97 seccomp_sandbox=NO
98 ''}
99 anon_umask=${cfg.anonymousUmask}
100 '';
101
102in
103
104{
105
106 ###### interface
107
108 options = {
109
110 services.vsftpd = {
111
112 enable = mkOption {
113 default = false;
114 description = "Whether to enable the vsftpd FTP server.";
115 };
116
117 userlist = mkOption {
118 default = [];
119 description = "See <option>userlistFile</option>.";
120 };
121
122 userlistFile = mkOption {
123 type = types.path;
124 default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
125 defaultText = "pkgs.writeText \"userlist\" (concatMapStrings (x: \"\${x}\n\") cfg.userlist)";
126 description = ''
127 Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
128 is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
129
130 The default is a file containing the users from <option>userlist</option>.
131
132 If explicitely set to null userlist_file will not be set in vsftpd's config file.
133 '';
134 };
135
136 anonymousUserHome = mkOption {
137 type = types.path;
138 default = "/home/ftp/";
139 description = ''
140 Directory to consider the HOME of the anonymous user.
141 '';
142 };
143
144 rsaCertFile = mkOption {
145 type = types.nullOr types.path;
146 default = null;
147 description = "RSA certificate file.";
148 };
149
150 anonymousUmask = mkOption {
151 type = types.string;
152 default = "077";
153 example = "002";
154 description = "Anonymous write umask.";
155 };
156
157 } // (listToAttrs (catAttrs "nixosOption" optionDescription));
158
159 };
160
161
162 ###### implementation
163
164 config = mkIf cfg.enable {
165
166 assertions = singleton
167 { assertion =
168 (cfg.forceLocalLoginsSSL -> cfg.rsaCertFile != null)
169 && (cfg.forceLocalDataSSL -> cfg.rsaCertFile != null);
170 message = "vsftpd: If forceLocalLoginsSSL or forceLocalDataSSL is true then a rsaCertFile must be provided!";
171 };
172
173 users.extraUsers =
174 [ { name = "vsftpd";
175 uid = config.ids.uids.vsftpd;
176 description = "VSFTPD user";
177 home = "/homeless-shelter";
178 }
179 ] ++ optional cfg.anonymousUser
180 { name = "ftp";
181 uid = config.ids.uids.ftp;
182 group = "ftp";
183 description = "Anonymous FTP user";
184 home = cfg.anonymousUserHome;
185 };
186
187 users.extraGroups.ftp.gid = config.ids.gids.ftp;
188
189 # If you really have to access root via FTP use mkOverride or userlistDeny
190 # = false and whitelist root
191 services.vsftpd.userlist = if cfg.userlistDeny then ["root"] else [];
192
193 systemd.services.vsftpd =
194 { description = "Vsftpd Server";
195
196 wantedBy = [ "multi-user.target" ];
197
198 preStart =
199 optionalString cfg.anonymousUser
200 ''
201 mkdir -p -m 555 ${cfg.anonymousUserHome}
202 chown -R ftp:ftp ${cfg.anonymousUserHome}
203 '';
204
205 serviceConfig.ExecStart = "@${vsftpd}/sbin/vsftpd vsftpd ${configFile}";
206 serviceConfig.Restart = "always";
207 serviceConfig.Type = "forking";
208 };
209
210 };
211
212}