Gestione utenti FTP con proFTPd e MySQL

Nel mondo GNU/Linux è molto diffuso il server FTP proFTD, considerato affidabile e con buone prestazioni. Purtroppo la gestione degli utenti, così come viene installato di default, non è molto comoda ed obbliga alla creazione di utenti interativi sul server, cosa che potrebbe non essere desiderata quando il servizio da dare prevede esclusivamente l’accesso FTP e WEB.

Il fatto di avere anche gli utenti interattivi non danneggia in sé le prestazioni del servizio FTP o WEB ma può rappresentare un ulteriore potenziale problema di sicurezza a fronte di una caratteristica non utilizzata.

Fortunatamente proFTP ci viene incontro ed ha la possibilità di configurare la gestione degli utenti tramite diversi backend tra cui alcuni database SQL. 

Tra i differenti tipi di database disponibili, prenderemo in considerazione MySQL, che è il più diffuso, ma in linea di massima potete utilizzarne un altro, quale l’ottimo PostgreSQL, Sqlite o ODBC.

La descrizione di alcune operazioni si riferisce a distribuzioni GNU/Linux derivate da Debian per cui se ne utilizzate un tipo differente dovrete apportare le modifiche del caso ai comandi di installazione dei moduli. In particolare, queste note si riferiscono ad unserver con Ubuntu Server 10.4 LTS.

Quello che otterremo alla fine della procedura descritta sarà quindi un server FTP la cui utenza viene gestita su database MySQL, con la creazione automatica della directory utente contenente la struttura base standard per il sito dello stesso.

Lo scopo di questo articolo è mostrare come configurare il server proFTPd, senza entrare nel merito dell’installazione e di come accedere al database, per cui partiamo dal presupposto di avere un server con sia proFTPd che MySQL installati e che sappiate come interagire col sistema e con MySQL per creare e modificare i file interessati e per creare e popolare le tabelle necessarie.

Creazione del database

Ovviamente dovremo per prima cosa creare un database dove mantenere le informazioni relative ai nostri utenti. Nel nostro caso prendiamo in considerazione un database dedicato ma nulla impedisce che sia condiviso per centralizzare la gestione di altri servizi come, ad esempio, la posta elettronica.

Pe gestire il database MySQL ci sono diversi strumenti; uno dei più diffusi e versatili è phpMyAdmin che potete scaricare dal sito ufficiale od installare dai repository della vostra distribuzione; nel caso utilizziate una derivata Debian, normalmente va bene il comando:

Creiamo quindi un database proftpd ed un utente proftpd con accesso in lettura ed aggiornamento ed inserimento di record. Effettueremo le suddette operazioni utilizzando un account amministratore del database, in quanto l’utente proftpd dovrà avere un accesso al database limitato alle operazioni necessarie a leggere le informazioni degli account, aggiornarne alcuni campi ed inserire i record per il log dei trasferimenti.

Tabella gruppi

Iniziamo con la tabella groups che utilizzeremo per memorizzare le informazioni relative ai gruppi di utenti:

CREATE TABLE IF NOT EXISTS `groups` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `groupname` varchar(64) NOT NULL,
  `gid` bigint(20) unsigned NOT NULL,
  `members` varchar(256) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `groupname` (`groupname`),
  UNIQUE KEY `gid` (`gid`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Come si può notare, la tabella è abbastanza semplice. Contiene i campi per

  • groupname: il nome del gruppo

  • gid: (Group ID), l’identificativo del gruppo

  • members: uno o più nomi utente appartenenti al gruppo.

L’accesso viene configurato nel file sql.conf con la direttiva:

  • SQLGroupInfo groups groupname gid members
    che ne descrive la struttura al server

Va notato che utilizzando la direttiva SQLAuthenticate users* groups* ,per ogni gruppo possono essere presenti più record, con differenti utenti nella colonna members ed allo stesso tempo in quella colonna possono essere elencati più utenti (separati da virgole).

Potete approfondire l’argomento visitando la pagina sulla direttiva SQLAuthenticate di proFTPd.

Tabella utenti

Proseguiamo con la creazione della tabella users dove manterremo le informazioni relative agli utenti:

CREATE TABLE IF NOT EXISTS `users` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `passwd` varchar(256) NOT NULL,
  `uid` int(10) UNSIGNED NOT NULL,
  `gid` int(10) UNSIGNED NOT NULL,
  `fullname` varchar(256) DEFAULT NULL,
  `homedir` varchar(256) NOT NULL,
  `shell` varchar(256) DEFAULT '/bin/false',
  `lastlogin` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `lastlogout` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `logincount` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
  `disabled` tinyint(1) DEFAULT '0',
  `expiration` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

la tabella contiene i campi per:

  • username: nome utente

  • passwd: la password dell’utente

  • uid: User ID, identificativo utente

  • gid: Group ID, identificativo gruppo

  • fullname: nome completo dell’utente

  • homedir: il percorso della cartella home

  • shell: la shell per l’accesso interattivo

  • lastlogin: data e ora dell’ultimo accesso

  • lastlogout: data e ora dell’ultimo logout

  • logincount: contatore degli accessi

  • disabled: indica se l’utente è disabilitato

  • expiration: data di termine validità per l’accesso

Per avere un livello di sicurezza più alto, nel campo passwd utilizzeremo la cifratura, compatibile con il meccanismo di autenticazione utilizzato da proFTP.

Vedremo più avanti come i campi disabled e expiration ci permettano di aggiungere due condizioni per convalidare l’accesso dell’utente; tramite essi potremo disabilitare l’utente senza la necessità di cancellara l’account ed impostare una data di scadenza oltre la quale l’utente non potrà più collegarsi.

L’accesso a questa tabella viene configurato nel file sql.conf le righe:

  • SQLUserInfo users username passwd uid gid homedir shel
    che descrive a proFTP la struttura della tabella.

  • SQLUserWhereClause  “disabled=0 AND (NOW()<=expiration OR expiration=’0000-00-00 00:00:00′)”
    che aggiunge una clausola WHERE per tutte le query effettuate per richiedere dati dell’utente; in particolareverifica che l’account non sia disabilitato o scaduto.

  • SQLLog PASS counter
    SQLNamedQuery counter UPDATE “lastlogin=now(), logincount=logincount+1 WHERE `username`=’%u'” users
    che istruiscono il server ad aggiornare il contatore di accessi e la data di ultimo accesso per l’utente ad ogni login.

  • SQLLog BYE,QUIT,EXIT time_logout
    SQLNamedQuery time_logout UPDATE “lastlogout=now() WHERE username=’%u'” users
    che istruiscono il server ad aggiornare la data di ultimo logout per l’utente

  • SQLShowInfo PASS “230” “Last login was: %{login_time}”
    SQLNamedQuery login_time SELECT “lastlogin from users where username=’%u'”
    che istruiscono proFTP a visualizzare la dicitura di ultimo login effettuato alla connessione dell’utente

Tabella storico accessi

Ora aggiungiamo una tabella accesslog per memorizzare le operazioni di accesso e disconnessione dal nostro server FTP dove troveremo le indicazioni di login, logout ed i tentativi di accesso falliti:

CREATE TABLE IF NOT EXISTS `accesslog` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `host` varchar(256) NOT NULL,
  `ip` varchar(32) NOT NULL,
  `action` varchar(15) NOT NULL,
  `accesstime` datetime NOT NULL,
  `success` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

In questa tabella troviamo i campi:

  • username: il nome dell’utente che ha effettuato l’operazione

  • host: il nome host del client da cui è stata effettuata la richiesta

  • ip: l’indirizzo del client

  • action: l’operazione effettuata

  • accesstime: l’orario dell’operazione

  • success: riporta se l’operazione sia andata a buon fine o no (1 = buon fine, 0 = errore)

In generale troveremo le operazioni di login e logout andate a buon fine ed i tentativi di accesso (password errata, utente inesistente) falliti.

La tabella viene configurata per l’utilizzo nel file sql.conf, con le righe:

  • SQLLog PASS accesslog1
    SQLLog BYE,QUIT,EXIT accesslog1
    SQLNamedQuery accesslog1 INSERT “NULL, ‘%u’, ‘%h’, ‘%a’, ‘%m’, now(), ‘1’” accesslog

    che istruiscono proFTP ad inserire una nuova riga per ogni accesso (login) e disconnessione (logout)  effettuati con successo

  • SQLLog ERR_USER,ERR_PASS accesslog2
    SQLNamedQuery accesslog2 INSERT “NULL, ‘%U’, ‘%h’, ‘%a’, ‘%m’, now(), ‘0’” accesslog
    che istruiscono proFTP ad inserire una riga per ogni tentativo di accesso fallito

Tabella storico trasferimenti

Per tenere traccia delle operazioni, creiamo ora la tabella xferlogche ci servirà per memorizzare le operazioni di trasferimento file:

CREATE TABLE IF NOT EXISTS `xferlog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL DEFAULT '',
  `filename` text,
  `size` bigint(20) DEFAULT NULL,
  `host` tinytext,
  `ip` tinytext,
  `action` tinytext,
  `duration` tinytext,
  `localtime` timestamp NULL DEFAULT NULL,
  `success` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_usersucc` (`username`,`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

la tabella contiene i campi:

  • username: il nome dell’utente che ha effettuato il trasferimento

  • filename: il nome del file trasferito

  • size: le dimensioni del file

  • host: il nome host del client che ha effettuato l’operazione

  • ip: l’indirizzo del client che ha effettuato l’operazione

  • action: il comando eseguito, troveremo STOR per gli upload e RETR per i download

  • duration: la durata dell’operazione

  • localtime: l’orario (sul server) dell’operazione

  • success: indica se l’operazione sia andata a buon fine o se sia fallita.

La configurazione per l’utilizzo viene effettuata in sql.conf nelle righe:

  • SQLLog RETR,STOR transfer1
    SQLNamedQuery  transfer1 INSERT “NULL, ‘%u’, ‘%f’, ‘%b’, ‘%h’, ‘%a’, ‘%m’, ‘%T’, now(), ‘1’” xferlog
    che istruisce proFTP ad inserire una riga per ogni trasferimento di file effettuato correttamente

  • SQLLog ERR_RETR,ERR_STOR transfer2
    SQLNamedQuery  transfer2 INSERT “NULL, ‘%u’, ‘%f’, ‘%b’, ‘%h’, ‘%a’, ‘%m’, ‘%T’, now(), ‘0’” xferlog
    che istruisce proFTP ad inserire una riga per ogni operazione di trasferimento andata male

Configurazione accesso al DB

Nel file /etc/proftpd/sql.conf andremo a configurare quanto necessario per indicare a proFTP quale database utilizzare e come interfacciarsi alle tabelle.

Per l’accesso al database assumiamo di avere:

  • username: proftpd

  • host: 127.0.0.1

  • database: proftpd

  • password:FTPD-MYSQL-USER-PSSWORD

quindi dovrete apportare le necessarie modifiche per adattare la configurazione alla vostra situazione nella riga che contiene:

  • SQLConnectInfo proftpd@127.0.0.1 proftpd FTPD-MYSQL-USER-PSSWORD

Abbiamo ancora alcune direttive da prendere in esame:

  • SQLBackend mysql
    indica che va utilizzato MySQL come database; in realtà potrebbe essere omesso se nel file /etc/proftpd/modules.conf viene disabilitato il modulo postgres altrimenti deve comparire e specificare mysql o postgres a seconda del database server utilizzato.

  • SQLAuthenticate users* groups*
    indica la modalità di autenticazione

  • SQLAuthTypes Crypt
    per forzare l’utilizzo di password crittografate nel db; utilizzando phpMyAdmin per inserire gli utenti nel db, bisognerà quindi utilizzare la funzione ENCRYPT() per il campo passwd

  • CreateHome on skel /usr/local/etc/proftpd/skel dirmode 700
    serve ad indicare che all’atto del login, se non esiste ancora la struttura di directory per l’utente, questa verrà creata automativamente ed alla directory principale verranno assegnati i permessi indicati (in questo caso accesso solo al proprietario).
    La struttura di modello deve essere presente nel percorso indicato, verrà creata nel percorso indicato come homedir nella tabella utenti econ il contenuto del modello (quindi nel nostro caso il contenuto di /usr/local/etc/proftpd/skel).
    Questa opzione è molto comoda per avere una struttura standard per i nuovi siti web, con una serie di directory e file di servizio; un utilizzo tipico è in concomitanza delle direttive Error della configurazione dei web server virtuali di Apache, ad esempio, per:

    • ErrorDocument 404 /error/404.html
      creeremo una cartella error ed in essa il file 404.html con il contenuto personalizzato da visualizzare in caso di pagina inesistente

Il file sql.conf risultante sarà quindi simile a quanto segue:

<IfModule mod_sql.c>
#
# Choose a SQL backend among MySQL or PostgreSQL.
# Both modules are loaded in default configuration, so you have to specify the backend
# or comment out the unused module in /etc/proftpd/modules.conf.
# Use 'mysql' or 'postgres' as possible values.
#
SQLBackend mysql
#
SQLEngine on
SQLAuthenticate users* groups*
#
## Use a backend-crypted or a crypted password
## may user MySQL ENCRYPT funtion to encript passwords
#
SQLAuthTypes Crypt
#
## Set minimum GID and UID for SQL-managed accounts
#
SQLMinUserGID 5001
SQLMinUserUID 5001
#
## Database Connection:
# SQLConnectionInfo dbname@hostname username password
#
SQLConnectInfo proftpd@127.0.0.1 proftpd FTPD-MYSQL-USER-PSSWORD
#
##Describes both users/groups tables
#
# SQLGroupInfo    
# SQLUserInfo       
#
SQLGroupInfo groups groupname gid members
SQLUserInfo users username passwd uid gid homedir shell
#
## Create user directory skeleton
## user directory skeleton must be created (not provided with default installation) and must NOT be world-writeable
#
CreateHome on skel /usr/local/etc/proftpd/skel dirmode 700
#
## Add custom WHERE clause to every user query:
## Account must be NOT disabled and NOT expired ('disable' and 'expiration' fields must be added to users table)
##
SQLUserWhereClause "disabled=0 AND (NOW()<=expiration OR expiration='0000-00-00 00:00:00')"
#
## Log the user logging in:
## Increment login counter and update last login date ('lastlogin' and 'logincount' fields must be added to users table)
#
SQLLog PASS counter
SQLNamedQuery counter UPDATE "lastlogin=now(), logincount=logincount+1 WHERE `username`='%u'" users
#
## insert log record for succesful login and logout
#
SQLLog PASS accesslog1
SQLLog BYE,QUIT,EXIT accesslog1
SQLNamedQuery accesslog1 INSERT "NULL, '%u', '%h', '%a', '%m', now(), '1'" accesslog
#
## insert log record for login fail
#
SQLLog ERR_USER,ERR_PASS accesslog2
SQLNamedQuery accesslog2 INSERT "NULL, '%U', '%h', '%a', '%m', now(), '0'" accesslog
#
## logout log
## update logout date ('lastlogout' field must be added to users table)
#
SQLLog BYE,QUIT,EXIT time_logout
SQLNamedQuery time_logout UPDATE "lastlogout=now() WHERE username='%u'" users
#
#
## display last login time when PASS command is given
#
SQLNamedQuery login_time SELECT "lastlogin from users where username='%u'"
SQLShowInfo PASS "230" "Last login was: %{login_time}"
#
## transfers Log in mysql
#
SQLLog RETR,STOR transfer1
SQLNamedQuery transfer1 INSERT "NULL, '%u', '%f', '%b', '%h', '%a', '%m', '%T', now(), '1'" xferlog
#
SQLLog ERR_RETR,ERR_STOR transfer2
SQLNamedQuery transfer2 INSERT "NULL, '%u', '%f', '%b', '%h', '%a', '%m', '%T', now(), '0'" xferlog
#
    </IfModule>

Dobbiamo ancora abilitare il caricamento del modulo per l’autentizazione tramite SQL; per farlo dovremo modificare il file /etc/proftpd/proftpd.conf aggiungendo la riga (o più probabilmente scommentando l’istruzione):

Amministrazione utenti

Non vi resta che creare gli utenti per poter sfruttare l’accesso FTP al server. Potete utilizzare ancora phpMyAdmin per popolare edeguatamente le tabelle ma ovviamente non è il metodo più pratico.

Possono venirvi in aiuto diverse applicazioni dedicate che se non funzionanti direttamente, richiedono solo pochi aggiustamenti, tra le quali vi segnalo:

vi consiglio di consultare la documentazione, scegliere quale applicazione vorrete utilizzare per l’amministrazione e quindi fare l’installazione e configurazione, apportando eventuali le modifiche necessarie alle tabelle, poiché le strutture descritte in questo tutorial non sono degli standard e potrebbero variare leggermente nelle diverse implementazioni.