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}