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