Using FTP actually should be avoided whenever that’s possible but sometimes it’s just the most handy and convenient way of transferring files. In most cases, your FTP-users will be able to upload files to the FTP-server. To avoid that some users would fill up the complete machine, you can use quotas. In this post, I’ll describe how to setup a basic proftpd FTP-server with quotas on RHEL or CentOS 6 and 7.
Suprisingly, there isn’t a lot of documentation to find on how to setup proftpd with quotas on CentOS or RHEL. The documentation that I found was mainly for LDAP or MySQL integration and while that’s a good idea, I was looking for something more simple, using a simple file containing the quotas for the users.
The steps in this post are fairly equal for el6 and el7 installations so I’ll only point out version-specific actions. If nothing’s written, it means that an action can be executed fine on both RHEL/CentOS 6 and RHEL/CentOS 7.
Install proftpd
The first thing to do is to install the proftpd package from the repositories. The package can be found in the EPEL repository. To be able to use the EPEL-repository, we need to make it available first:
[jensd@cen ~]$ sudo yum -y install epel-release ... Complete !
Now that we have access to packages in the EPEL repository, we can go ahead and install proftpd itself. To be able to test the ftp-server on the same machine, let’s also install the cli ftp-client:
[jensd@cen ~]$ sudo yum -y install proftpd ftp ... Complete !
At the time of writing, for CentOS 7, the latest version was: 1.3.5-2 and for CentOS 6, the latest version was: 1.3.3g-4. Because of the difference in versions, there are some differences regarding quota configuration but more about that later.
Firewall and SELinux
Before we will start with the actual configuration, we need to allow FTP to pass trough our firewall and trough SELinux.
By default, iptables will block incoming connections so we need to open up TCP port 21 to allow incoming FTP-connections. Because we would like to support passive FTP, since most FTP-clients use that by default, we’ll also need to load the ip_conntrack_ftp kernel module for iptables.
For RHEL 6 or CentOS 6:
On el6, we need to configure iptables directly:
Add the ip_conntrack_ftp module in /etc/sysconfig/iptables-config:
# Load additional iptables modules (nat helpers) # Default: -none- # Space separated list of nat helpers (e.g. 'ip_nat_ftp ip_nat_irc'), which # are loaded after the firewall rules are applied. Options for the helpers are # stored in /etc/modprobe.conf. IPTABLES_MODULES="ip_conntrack_ftp" ...
Open up TCP port 21 for incoming traffic, save the rules and restart iptables to load the kernel module:
[jensd@cen ~]# vi /etc/sysconfig/iptables-config [jensd@cen ~]# sudo iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 21 -j ACCEPT [jensd@cen ~]# sudo service iptables save iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ] [jensd@cen ~]# sudo /etc/init.d/iptables restart iptables: Setting chains to policy ACCEPT: filter [ OK ] iptables: Flushing firewall rules: [ OK ] iptables: Unloading modules: [ OK ] iptables: Applying firewall rules: [ OK ] iptables: Loading additional modules: ip_conntrack_ftp [ OK ]
FOR RHEL 7 OR CENTOS 7:
On el7 we can use firewalld to make things a little simpler:
[jensd@cen ~]$ sudo firewall-cmd --permanent --add-port=21/tcp success [jensd@cen ~]$ sudo firewall-cmd --permanent --add-service=ftp success [jensd@cen ~]$ sudo systemctl restart firewalld [jensd@cen ~]$
To make sure that the conntrack_ftp module is loaded, let’s check which kernel modules are loaded (on both el6 and el7):
[jensd@cen ~]$ lsmod|grep nf_conntrack_ftp nf_conntrack_ftp 18638 0 nf_conntrack 101024 9 nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,ip6table_nat,nf_conntrack_ftp,iptable_nat,nf_conntrack_ipv4,nf_conntrack_ipv6
Be default, SELinux won’t allow the FTP-users to write to their home directory which would immediately take away the need to set up a quota :) Let’s allow read and write access for the ftp-users to their home directory by setting SELinux boolean ftp_home_dir to 1:
[jensd@cen ~]$ sudo setsebool -P ftp_home_dir=1
Basic proftpd configuration
To get familiar with proftpd and it’s configuration file, let’s start with a basic FTP-server configuration so we can be sure that our FTP-server works fine before we start with quotas.
The configuration of proftpd is done in /etc/proftpd.conf. Change the configuration file to have something like this. I removed commented lines to prevent the page to be too long but it’s a good idea to keep them in your file.
ServerName "ProFTPD server" ServerIdent on "FTP Server ready." ServerAdmin root@localhost DefaultServer on # Cause every FTP user except adm to be chrooted into their home directory DefaultRoot ~ !adm # Use pam to authenticate (default) and be authoritative AuthPAMConfig proftpd AuthOrder mod_auth_pam.c* mod_auth_unix.c # Don't do reverse DNS lookups (hangs on DNS problems) UseReverseDNS off # Set the user and group that the server runs as User nobody Group nobody # To prevent DoS attacks, set the maximum number of child processes # to 20. MaxInstances 20 # Disable sendfile by default since it breaks displaying the download speeds in UseSendfile off # Define the log formats LogFormat default "%h %l %u %t \"%r\" %s %b" LogFormat auth "%v [%P] %h %t \"%r\" %s" # Allow only user root to load and unload modules, but allow everyone # to see which modules have been loaded # (http://www.proftpd.org/docs/modules/mod_dso.html#ModuleControlsACLs) ModuleControlsACLs insmod,rmmod allow user root ModuleControlsACLs lsmod allow user * # Global Config - config common to Server Config and all virtual hosts # See: http://www.proftpd.org/docs/howto/Vhost.html <Global> Umask 022 AllowOverwrite yes <Limit ALL SITE_CHMOD> DenyAll AllowGroup ftp </Limit> </Global>
Most of the configuration in the file is standard. The only real change is the denial of all users and to allow only users that are in the group ftp. Be default, all users are allowed to connect to the FTP-server, except the ones listed in /etc/ftpusers.
After the change in the configuration, to allow my own user (or any other existing user) to have access to FTP, I need to add it to the group ftp:
[jensd@cen ~]$ sudo usermod -a -G ftp jensd
To create a new user that will only be used for FTP:
[jensd@cen ~]$ sudo useradd ftpuser1 -G ftp -s /sbin/nologin [jensd@cen ~]$ sudo passwd ftpuser1 Changing password for user ftpuser1. New password: Retype new password: passwd: all authentication tokens updated successfully.
All preparation for the FTP-server should be done so it’s time to start the service and test it.
For RHEL 6 or CentOS 6:
[root@localhost ~]# sudo /etc/init.d/proftpd start Starting proftpd: [ OK ]
For RHEL 7 or CentOS 7:
[jensd@cen ~]$ sudo systemctl start proftpd
In case you would get an error that contains something like this:
Feb 25 14:04:24 localhost systemd: Starting ProFTPD FTP Server... Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,531 cen proftpd[2335]: warning: unable to determine IP address of 'cen' Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,531 cen proftpd[2335]: error: no valid servers configured Feb 25 14:04:24 localhost proftpd: 2015-02-25 14:04:24,532 cen proftpd[2335]: fatal: error processing configuration file '/etc/proftpd.conf' Feb 25 14:04:24 localhost systemd: proftpd.service: control process exited, code=exited status=1 Feb 25 14:04:24 localhost systemd: Failed to start ProFTPD FTP Server. Feb 25 14:04:24 localhost systemd: Unit proftpd.service entered failed state.
Then, make sure that your system can resolve it’s own hostname by adding it to the /etc/hosts file:
[jensd@cen ~]$ cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.202.101 cen [jensd@cen ~]$ ping $(hostname) -c 1 PING cen (192.168.202.101) 56(84) bytes of data. 64 bytes from cen (192.168.202.101): icmp_seq=1 ttl=64 time=0.046 ms --- cen ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.046/0.046/0.046/0.000 ms
Once the server is started, it’s time to test the connection and see if we can upload a file. To keep things visible and easy to follow, I used the cli ftp-client but you can use any client (like Filezilla) to test the connection.
[jensd@cen ~]$ ftp 192.168.202.101 Connected to 192.168.202.101 (192.168.202.101). 220 FTP Server ready. Name (192.168.202.101:jensd): ftpuser1 331 Password required for ftpuser1 Password: 230 User ftpuser1 logged in Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 227 Entering Passive Mode (192,168,202,101,181,136). 150 Opening ASCII mode data connection for file list 226 Transfer complete ftp> put /etc/issue testfile local: /etc/issue remote: testfile 227 Entering Passive Mode (192,168,202,101,140,175). 150 Opening BINARY mode data connection for testfile 226 Transfer complete 23 bytes sent in 9.9e-05 secs (232.32 Kbytes/sec) ftp> ls 227 Entering Passive Mode (192,168,202,101,180,56). 150 Opening ASCII mode data connection for file list -rw-r--r-- 1 ftpuser1 ftpuser1 23 Feb 25 13:37 testfile 226 Transfer complete ftp>
Configuration with quotas
Now that we’ve got a working FTP-server, let’s add a quota for the FTP-users. Change the configuration file in /etc/proftpd.conf to include some quota configuration:
ServerName "ProFTPD server" ServerIdent on "FTP Server ready." ServerAdmin root@localhost DefaultServer on # Cause every FTP user except adm to be chrooted into their home directory DefaultRoot ~ !adm # Use pam to authenticate (default) and be authoritative AuthPAMConfig proftpd AuthOrder mod_auth_pam.c* mod_auth_unix.c # Don't do reverse DNS lookups (hangs on DNS problems) UseReverseDNS off # Set the user and group that the server runs as User nobody Group nobody # To prevent DoS attacks, set the maximum number of child processes # to 20. MaxInstances 20 # Disable sendfile by default since it breaks displaying the download speeds in UseSendfile off # Define the log formats LogFormat default "%h %l %u %t \"%r\" %s %b" LogFormat auth "%v [%P] %h %t \"%r\" %s" # Allow only user root to load and unload modules, but allow everyone # to see which modules have been loaded # (http://www.proftpd.org/docs/modules/mod_dso.html#ModuleControlsACLs) ModuleControlsACLs insmod,rmmod allow user root ModuleControlsACLs lsmod allow user * # Global Config - config common to Server Config and all virtual hosts # See: http://www.proftpd.org/docs/howto/Vhost.html <Global> Umask 022 AllowOverwrite yes <Limit ALL SITE_CHMOD> DenyAll AllowGroup ftp </Limit> </Global> # Quota support (http://www.proftpd.org/docs/contrib/mod_quotatab.html) LoadModule mod_quotatab.c LoadModule mod_quotatab_file.c <IfModule mod_quotatab.c> QuotaEngine on QuotaLog /var/log/proftpd/quota.log #QuotaDefault is not supported on the proftp-version on CentOS 6 QuotaDefault user false hard 5242880 0 0 0 0 0 QuotaDisplayUnits Mb QuotaOptions ScanOnLogin QuotaDirectoryTally off <IfModule mod_quotatab_file.c> QuotaLimitTable file:/etc/proftpd/ftpquota.limittab QuotaTallyTable file:/etc/proftpd/ftpquota.tallytab </IfModule> </IfModule>
As you can see, only the last lines were added. These lines load the modules that will be responsible for watching and enforcing the quotas and on line 55, we configure that every user will have a default quota of 5MB (5*1024*1024).
FOR RHEL 6 OR CENTOS 6:
Comment out line 55 in the above file and add quotas for every user with FTP-access (see further).
Before we can reload the configuration, we need to initialize the limit table and tally table that we mentioned in our configuration file. This needs to be done with the ftpquota script. Unfortunately, for el6, this script isn’t in any standard package so you’ll have to install it manually.
FOR RHEL 6 OR CENTOS 6:
Download the script here: http://jensd.be/download/ftpquota and install it as follows:
[jensd@cen ~]$ curl -O http://jensd.be/download/ftpquota [jensd@cen ~]$ sudo cp ~/ftpquota /usr/bin/ [jensd@cen ~]$ sudo chmod +x /usr/bin/ftpquota [jensd@cen ~]$ sudo yum -y install perl ... Complete !
FOR RHEL 7 OR CENTOS 7:
Install the proftpd-utils package, it contains the script:
[jensd@cen ~]$ sudo yum -y install proftpd-utils ... Complete !
After installing the ftpquota-script, use it to create the location for the tables and to initialize them:
[jensd@cen ~]$ sudo mkdir /etc/proftpd [jensd@cen ~]$ cd /etc/proftpd [jensd@cen proftpd]$ sudo ftpquota --create-table --type limit [jensd@cen proftpd]$ sudo ftpquota --create-table --type tally [jensd@cen proftpd]$ ll total 8 -rw-r--r--. 1 root root 4 Feb 25 14:28 ftpquota.limittab -rw-r--r--. 1 root root 4 Feb 25 14:28 ftpquota.tallytab
In case you don’t want tot use the default quota (defined on line 55 in the configuration file), you can manually add a different quota for every user. This is mandatory on RHEL 6 or CentOS 6. For example to set a quota of 10MB on user jensd:
[jensd@cen ~]$ cd /etc/proftpd [jensd@cen ~]$ sudo ftpquota --add-record --type=limit --name=jensd --quota-type=user --bytes-upload=10485760
To let our changes take effect, we need to restart the FTP-server:
FOR RHEL 6 OR CENTOS 6:
[jensd@cen ~] sudo /etc/init.d/proftpd restart Shutting down proftpd: [ OK ] Starting proftpd: [ OK ]
FOR RHEL 7 OR CENTOS 7:
[jensd@cen proftpd]$ sudo systemctl restart proftpd
To test the quota, let’s create a file of 2MB to be uploaded:
[jensd@cen ~]$ dd if=/dev/zero of=~/2MB bs=1024 count=2048 2048+0 records in 2048+0 records out 2097152 bytes (2.1 MB) copied, 0.00903008 s, 232 MB/s
Now let’s test the FTP again and see if we’re limited in quota:
[jensd@cen ~]$ ftp 192.168.202.101 Connected to 192.168.202.101 (192.168.202.101). 220 FTP Server ready. Name (192.168.202.101:jensd): ftpuser1 331 Password required for ftpuser1 Password: 230 User ftpuser1 logged in Remote system type is UNIX. Using binary mode to transfer files. ftp> ls 227 Entering Passive Mode (192,168,202,101,159,215). 150 Opening ASCII mode data connection for file list -rw-r--r-- 1 ftpuser1 ftpuser1 23 Feb 25 13:37 testfile 226 Transfer complete ftp> put 2MB test2 local: 2MB remote: test2 227 Entering Passive Mode (192,168,202,101,148,192). 150 Opening BINARY mode data connection for test2 226 Transfer complete 2097152 bytes sent in 0.00537 secs (390385.70 Kbytes/sec) ftp> put 2MB test3 local: 2MB remote: test3 227 Entering Passive Mode (192,168,202,101,237,213). 150 Opening BINARY mode data connection for test3 226 Transfer complete 2097152 bytes sent in 0.00323 secs (649273.09 Kbytes/sec) ftp> put 2MB test4 local: 2MB remote: test4 227 Entering Passive Mode (192,168,202,101,196,180). 150 Opening BINARY mode data connection for test4 552 Transfer aborted. Disk quota exceeded 2097152 bytes sent in 0.00504 secs (416266.75 Kbytes/sec) ftp> ls 227 Entering Passive Mode (192,168,202,101,231,37). 150 Opening ASCII mode data connection for file list -rw-r--r-- 1 ftpuser1 ftpuser1 2097152 Feb 25 13:40 test2 -rw-r--r-- 1 ftpuser1 ftpuser1 2097152 Feb 25 13:40 test3 -rw-r--r-- 1 ftpuser1 ftpuser1 23 Feb 25 13:37 testfile 226 Transfer complete ftp>
As you can see, the last file, test4, didn’t get uploaded since that would get the total used space higher that our quota. The transfer log shows the following message to make this clear to the user: 552 Transfer aborted. Disk quota exceeded.
On the server itself we can monitor the quotas from the logfile in /var/log/proftpd/quota.log (as we definded in our configuration file):
[jensd@cen ~]$ sudo tail /var/log/proftpd/quota.log 2015-02-25 14:37:23,447 mod_quotatab/1.3.1[2663]: updating tally (442.00 bytes, 3 files difference) 2015-02-25 14:37:23,447 mod_quotatab/1.3.1[2663]: quotatab fs registered 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: using default limit from QuotaDefault directive 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found limit entry for user 'ftpuser1' 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found tally entry for user 'ftpuser1' 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: ScanOnLogin enabled, scanning current directory '/' for files owned by user 'ftpuser1' 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: found 465.00 bytes in 4 files for user 'ftpuser1' in 0 secs 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: updating tally (0.00 bytes, 4 files difference) 2015-02-25 14:40:35,646 mod_quotatab/1.3.1[2675]: quotatab fs registered 2015-02-25 14:40:49,299 mod_quotatab/1.3.1[2675]: quotatab write(): limit exceeded, returning Disk quota exceeded
As you can see, it’s rather simple and easy to set up quotas for your FTP-server.
i have a problem with quota, in quota log i get this: mai 01 15:39:37 mod_quotatab/1.3.0[3739]: error: unable to open QuotaTallyTable: Opération non permise
mai 01 15:39:46 mod_quotatab/1.3.0[3739]: turning QuotaEngine off
do you know how to repair this problem?
thanks
Did you create the tables with ftpquota?
You could check the permsisions on /etc/proftpd/ftpquota.*
Also, check if SELinux isn’t causing you troubles. The easies way to test it, is to turn it of temporarily (setenforce 0) and check for messages with AVC in /var/log/audit/audit.log
Thanks for this post!