Proftpd + MySQL + Quotas HOWTO
En una red informática compuesta por más de un equipo es interesante habilitar algún servicio de transferencia de ficheros para compartir información entre ordenadores. Existen varios protocolos usables para dicha tarea (NFS, Samba, HTTP...) pero solamente uno se ha creado específicamente con este fin: FTP (file transfer protocol).
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.cUna vez instalado en el sistema tendremos que crear un grupo/usuario que será el dueño en cuanto a permisos de los archivos que se transfieran a cada directorio. Lo explico mejor, cuando un usuario se conecte al servidor ftp tendrá su login, su contraseña, su directorio raíz... como si fuera un usuario real del sistema; sin embargo no tiene uid, por lo que sus archivos quedarían sin propietario. Pues para eso sirve este usuario que creamos a continuación. Os estareis preguntando, según lo que explicas... ¿no podría un usuario machacar los archivos de otro?. Podría, pero como cada uno está enjaulado en su directorio raíz (DefaultRoot ~/), no es capaz de entrar en el directorio de otro usuario. Espero que se haya entendido:
gprs:~# groupadd -g 5500 ftpgroup gprs:~# adduser -u 5500 -s /bin/false -d /bin/null -c "proftpd user" -g ftpgroup ftpuserEl siguiente paso es crear la base de datos y las tablas donde guardaremos usuarios virtuales, límites de cuotas, logs... en MySQL. También vamos a crear un usuario MySQL que será el único que tenga acceso a esta estructura:
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,'','');
Algunas aclaraciones al respecto. La tabla ftpgroup lista los usuarios de cada grupo, como solo habrá un grupo (inicialmente) no necesita más registros para comenzar a usar el servicio. La tabla ftuser guarda los usuarios, estadísticas y es la usada por Proftpd para comparar los datos de autentificación. Usando la directiva 'asdfasf' haremos que Proftpd cree el directorio raíz del usuario si este no existe. La tabla ftpquotalimits define los límites de cuota en disco y ftpquotatalities va guardando los datos de cada usuario para totalizar y comprobar que el límite no sobrepasa lo acordado en ftpquotalimits. Si el valor de algún campo de límite es '0' automáticamente se definirá como ilimitado.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");
Antes de probar que todo funciona debemos decirle a Proftpd que debe tener en cuenta todos estos datos, el archivo principal de configuración se encuentra (en Debian) en /etc/proftpd.conf y quedará de la siguiente forma:
# 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
Arrancamos Proftpd, o lo reiniciamos en caso de tenerlo ya funcionando y ya estamos listos para probar la nueva configuración. Debemos tener a la vista siempre los logs por si hubiera algún error sintáctico a la hora de escribir la configuración (a veces pasa):
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:~#
Funciona, vemos como conecta con MySQL mirando los campos que le hemos indicado en la configuración. Si probamos a subir un archivo de más de 15Mb veremos como justo después de hacer la transferencia lo borra porque excede la cuota.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.
Excelente anotacion para tener en un pdf ;) El dia que tenga internet pienso instalar uno de estos com tienen algunos amiguetes pero ellos usan directamente el fichero /etc/passwd.
Espero que algun dia pongas todas tus anotaciones en ficheros en pdf como hacen en Bulma, podrias utilizar wp2pdf.
Gracias por tus adjetivos ;). En cuanto a lo que dices de las anotaciones, hace tiempo tenía una sección de "Documentos" que decidí suprimir por diversos motivos; hoy en día estoy desarrollando (en rCMS-unstable) de nuevo esa sección para poner pequeños artículos como este.
La mayoría de ellos están escritos en Docbook y SGML, por lo que presumiblemente no habrá problema alguno en colgarlos con distintos formatos (ps, pdf, div, html...). Dame un poco de tiempo, habrá sorpresillas ;).
¿Hay alguna posibilidad de que, en vez de permitir subir los ficheros para luego borrarlos, directamente no deje subirlos?
¿Que clase de cuota permite subir un fichero de 50 gigas cuando hay un limite de 15 megas?
Lo dicho, para pdf :) muy interesante la implementación de mysql y quotas de disco, a ver si ahora que tengo tiempo me dedico a ello ;). Sobre el apartado de Docs que tenias antes... que pasó? que motivos fueron por lo que lo has quitado? estaba genial!! deberias volver a ponerlo :).
P.D.: Felicitaciones por el blog ;) esta currado
Muy bueno y claro, aunque me encuentro ante un problema, cuando le doy a la orden
adduser -u 5500 -s /bin/false -d /bin/null -c "proftpd user" -g ftpgroup ftpuser
Me devuelve
adduser: No más de dos nombres.
En el man adduser no he encontrado nada que me ayude a dilucidar la cuestion.
Que puede ser....?
Saludos
Prueba con useradd, algunas distribuciones les cambian el nombre o meten sus propios scripts para añadir usuario. Supongo que con useradd no tendrás problema. Ya nos contarás ;).
Si, buscando información lo he solucionado con useradd incluso pregunté en la lista en español de debian (uso Ubuntu) y tambien me dijeron lo mismo, pero ya lo tenia solucionado.
Muchas gracias...
amigo, dime donde hay mas guias tuyas, por que la verdad es que esta guia es brillante, va al punto y no pierdes tiempo en explicar gilipolladas.
EXCELENTE!!!!!!!
Gracias por la guia, estupenda.
Muy buena guia, sencilla pero completa. Realmente es todo o que me hacia falta. Muchas Gracias.
Despues de los elogios, los problemas.
-No me actualiza la tabla de totalidades, por lo que me deja seguir metiendo datos. Los bytes en uso se me quedan en 0.
-Podrias explicar que es cada campo de para restringir las cuotas, asi se podria personalizar mejor.
Hola, Muy buena guia!
te hago una pregunta como lo haces trabajar con mysql y radius, mi intencion es que no se conecte el mismo usuario dos veces o mas veces al mismo tiempo
gracias
Borro
mira...
en lo del chroot lo malo es que afecta a todos los usuarios, ya sea local o virtuales...
lo que yo hice para sacar de la jaula a los usuarios locales fue lo siguiente...
en DefaultRoot, le añadi el grupo que queria que estuviese enjaulado (o sea el virtual)...
lo deje de esta manera.
DefautlRoot ~ <tab> ftpgroup
despues
/etc/init.d/proftpd restart
y el resultado fue el siguiente
Connected to localhost.localdomain.
220 ftpd
Name (localhost:j0ker): felipe
331 Password required for felipe.
Password:
230 User felipe logged in.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
257 "/" is current directory.
ftp>
ftp> quit
221 Goodbye.
###############################
Arcangel:~# ftp localhost
Connected to localhost.localdomain.
220 ftpd
Name (localhost:j0ker): j0ker
331 Password required for j0ker.
Password:
230 User j0ker logged in.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
257 "/home/j0ker" is current directory.
ftp>
Hola, tengo un problema al crear la tabla ftpuser, me dice lo siguiente: ERROR 1064: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO `ftpuser` VALUES (1, 'testaccount', 'ftppasswd', 55
no se que puede ser...
Muy bueno.. esta de una manera muy simple y funciona perfecto... lo ùnico que no me funciono es la actualizaciòn de ftpquotatallies no se porque .. me fijo en los logs de mysql y syslog pero no me muestra nada. Lo que no me funciona serìa lo siguiente: QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally porque veo que la tabla esa nunca carga registros ni actualiza.
Martin, a mi me pasa lo mismo respecto a la tabla ftpquotalimits, q no actualiza los datos, con lo que no creo q la cuota la controle, he probado meter a un campo como name como primary key y tampoco.
Si alguien nos puede comentar, yo seguire probando.
Gracias


