Proftpd + MySQL + Quotas HOWTO

Generalmente cuando tratemos de poner a funcionar un servicio cliente/servidor, debemos hacer mayor hincapié en la parte del servidor y configurarla de forma adecuada al uso que se le pueda dar en un futuro. Al principio es probable que el número de usuarios sea pequeño y no merezca la pena configurar tasas de transferencia o quotas en disco, pero a medida que se ofrece un servicio, las exigencias van cambiando y se hacen cada vez más necesarias. Usaremos Proftpd como servidor con dos particularidades. La primera de ellas es el uso de MySQL para autentificación de usuarios virtuales, esto quiere decir que no habrá cuentas del sistema (con o sin shell) para el acceso ftp, con la consiguiente limpieza de /etc/passwd ;). La segunda particularidad es el uso de cuotas de disco para limitar el espacio que otorgamos a cada usuario.
Antes de empezar decir que la instalación y configuración del servidor se ha realizado en una Debian 3.1, siendo extrapolable a cualquier otra distribución con suma facilidad. Ahora si, comencemos.
El primer paso es la instalación del software necesario, una vez instalado miramos si Proftpd tiene los módulos necesarios para seguir con la configuración. Si tiramos de código fuente hemos de tener en cuenta los parámetros de configure (./configure --with-modules=mod_sql:mod_sql_mysql:mod_quotatab:mod_quotatab_sql --with-includes=/usr/include/mysql):
gprs:~# apt-get install proftpd-mysql mysql-server
...
gprs:~# proftpd -l
Compiled-in modules:
mod_core.c
mod_xfer.c
mod_auth_unix.c
mod_auth_file.c
mod_auth.c
mod_ls.c
mod_log.c
mod_site.c
mod_auth_pam.c
mod_quotatab.c
mod_sql.c
mod_sql_mysql.c
mod_quotatab_sql.c
mod_ratio.c
mod_tls.c
mod_rewrite.c
mod_radius.c
mod_wrap.c
mod_quotatab_file.c
mod_delay.c
mod_readme.c
mod_ifsession.c
mod_cap.c
gprs:~# groupadd -g 5500 ftpgroup
gprs:~# adduser -u 5500 -s /bin/false -d /bin/null -c "proftpd user" -g ftpgroup ftpuser
gprs:~# mysql -uroot -ppassword
grant usage on *.* to 'proftpd'@'localhost' identified by 'password'
create database ftpdb;
grant select, insert, update on ftpdb.* to proftpd@localhost identified by 'password';
use ftpdb;
#
# Table structure for table `ftpgroup`
#
CREATE TABLE `ftpgroup` (
`groupname` varchar(16) NOT NULL default '',
`gid` smallint(6) NOT NULL default '5500',
`members` varchar(16) NOT NULL default '',
KEY `groupname` (`groupname`)
) TYPE=MyISAM COMMENT='ProFTP group table';
INSERT INTO `ftpgroup` VALUES ('ftpgroup', 5500, 'ftpuser');
#
# Table structure for table `ftpquotalimits`
#
CREATE TABLE `ftpquotalimits` (
`name` varchar(30) default NULL,
`quota_type` enum('user','group','class','all') NOT NULL default 'user',
`per_session` enum('false','true') NOT NULL default 'false',
`limit_type` enum('soft','hard') NOT NULL default 'soft',
`bytes_in_avail` float NOT NULL default '0',
`bytes_out_avail` float NOT NULL default '0',
`bytes_xfer_avail` float NOT NULL default '0',
`files_in_avail` int(10) unsigned NOT NULL default '0',
`files_out_avail` int(10) unsigned NOT NULL default '0',
`files_xfer_avail` int(10) unsigned NOT NULL default '0'
) TYPE=MyISAM;
#
# Table structure for table `ftpquotatallies`
#
CREATE TABLE `ftpquotatallies` (
`name` varchar(30) NOT NULL default '',
`quota_type` enum('user','group','class','all') NOT NULL default 'user',
`bytes_in_used` float NOT NULL default '0',
`bytes_out_used` float NOT NULL default '0',
`bytes_xfer_used` float NOT NULL default '0',
`files_in_used` int(10) unsigned NOT NULL default '0',
`files_out_used` int(10) unsigned NOT NULL default '0',
`files_xfer_used` int(10) unsigned NOT NULL default '0'
) TYPE=MyISAM;
#
# Table structure for table `ftpuser`
#
CREATE TABLE `ftpuser` (
`id` int(10) unsigned NOT NULL auto_increment,
`userid` varchar(32) NOT NULL default '',
`passwd` varchar(32) NOT NULL default '',
`uid` smallint(6) NOT NULL default '5500',
`gid` smallint(6) NOT NULL default '5500',
`homedir` varchar(255) NOT NULL default '',
`shell` varchar(16) NOT NULL default '/sbin/nologin',
`count` int(11) NOT NULL default '0',
`accessed` datetime NOT NULL default '0000-00-00 00:00:00',
`modified` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) TYPE=MyISAM COMMENT='ProFTP user table'
INSERT INTO `ftpuser` VALUES (1, 'testaccount', 'ftppasswd', 5500, 5500, '/home/testdomain.com', '/sbin/nologin',0,'','');
Hemos creado un usuario testaccount para probar el servicio. Agreguemos ahora una cuota para ese usuario de 15Mb:
INSERT INTO ftpquotalimits VALUES("testaccount","user","true","hard","15728640","0","0","0","0","0");
# Configuraciones generales
ServerName "Nuestro MegaFTP Server"
ServerType Standalone
ServerAdmin root@localhost
# Ocultamos todo lo posible a usuarios externos
ServerIdent on "Bienvenido a MegaFTP Server..."
DeferWelcome on
DefaultServer on
# Permitimos resumes, configuramos puerto y demás
AllowStoreRestart on
Port 21
Umask 022
MaxInstances 30
User nobody
Group nogroup
# Enjaulamos a nuestros usuarios (chroot)
DefaultRoot ~
AllowOverwrite on
# Para MySQL
SQLAuthTypes Plaintext
SQLConnectInfo ftpdb@localhost proftpd password
SQLUserInfo ftpuser userid passwd uid gid homedir shell
SQLGroupInfo ftpgroup groupname gid members
SQLMinID 500
SQLHomedirOnDemand on
SQLLog PASS updatecount
SQLNamedQuery updatecount UPDATE "count=count+1, accessed=now() WHERE userid='%u'" ftpuser
SQLLog STOR,DELE modified
SQLNamedQuery modified UPDATE "modified=now() WHERE userid='%u'" ftpuser
# Para las cuotas en disco
QuotaEngine on
QuotaDirectoryTally on
QuotaDisplayUnits Mb
QuotaShowQuotas on
SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM ftpquotalimits WHERE name = '%{0}' AND quota_type = '%{1}'"
SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM ftpquotatallies WHERE name = '%{0}' AND quota_type = '%{1}'"
SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = '%{6}' AND quota_type = '%{7}'" ftpquotatallies
SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" ftpquotatallies
QuotaLimitTable sql:/get-quota-limit
QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally
# No permitimos login a root y no hace falta tener shell
RootLogin off
RequireValidShell off
gprs:~# /etc/init.d/proftpd restart
Restarting ProFTPD ftp daemon.proftpd.
..proftpd.
done.
gprs:~# tail -f /var/log/syslog
Feb 16 13:45:25 gprs proftpd[1184]: gprs - ProFTPD killed (signal 15)
Feb 16 13:45:25 gprs proftpd[1184]: gprs - ProFTPD 1.2.10 standalone mode SHUTDOWN
Feb 16 13:45:27 gprs proftpd[2678]: gprs - ProFTPD 1.2.10 (stable) (built do mrt 22 18:28:32 CET 2001) standalone mode STARTUP
gprs:~#
//Entramos con algún cliente de ftp a nuestro servidor
gprs:~# tail -f /var/log/mysql/mysql.log
050216 14:19:02 39 Connect proftpd@localhost on ftpdb
39 Query SELECT userid, passwd, uid, gid, homedir, shell FROM ftpuser WHERE (userid='testaccount') LIMIT 1
39 Query SELECT groupname FROM ftpgroup WHERE (gid = 5500) LIMIT 1
39 Query SELECT groupname, gid, members FROM ftpgroup WHERE (groupname = 'ftpgroup')
39 Query SELECT groupname, gid, members FROM ftpgroup WHERE (members = 'testaccount' OR members LIKE 'testaccount,%' OR members LIKE '%,testaccount' OR members LIKE '%,testaccount,%')
gprs:~#
Ya tenemos todo listo para dar cuentas a nuestros amigos y dejar que usen el servicio sin preocuparnos demasiado por el espacio en disco. Eso sí, recomendaría redactar una política de uso en el motd y mirar de vez en cuando las estadísticas de cada usuario.